再见Redux,MobX,阿波罗!后端和前端之间的线断了!国家管理人员发展的一个创新步骤。

开发Web和移动应用程序中最困难的任务之一是在设备之间同步数据并执行脱机操作。理想情况下,当设备离线时,您的客户应该能够继续使用您的应用程序不仅可以访问数据,还可以创建和修改它。当设备返回在线模式时,应用程序必须重新连接到后端,同步数据并解决冲突(如果有)。正确处理所有极端情况都需要大量未区分的代码,即使在具有自动突变和增量同步的设备上使用AWS AppSync SDK缓存时也是如此。
Amplify DataStore , , , .
Amplify DataStore , , -.
Amplify DataStore « » AWS AWS AppSync Amazon DynamoDB.
DataStore Delta Sync GraphQL .
DataStore AWS Amplify Redux, MobX, Apollo, Relay, , :
AWS Amplify Redux, MobX , AWS Amplify -, -, - Apollo Relay.
1. Real time
, , web sockets.
web sockets?
, , real time .
, fullStack serverless AWS Amplify, real time , , , , GraphQL . , , , AWS Amplify Store:
type Job
  @model
  @auth(
    rules: [
      {allow: owner, ownerField: "owner", operations: [create, update, delete]},
    ])
{
  id: ID!
  position: String!
  rate: String!
  description: String!
  owner: String
}
, , . . , , , vs Redux, MobX, Apollo, Relay.
Redux, MobX, Apollo , . AWS Amplify DataStore
!!!
, .
Serverless — , , , real time.
2.
, , .
Old school?
fetch axios?
API, Redux, MobX, Apollo, Relay.
!
API, .
, , AWS Amplify DataStore , GraphQL . :
npm run amplify-modelgen
models .
graphql , Flow, TS JavaScript.
3. Offline data & cloud sync
, , .
, , .
Apollo apollo-link-retry . ( ) GraphQL , .
Redux, MobX , , REST . GraphQL vs REST.
AWS Amplify DataStore apollo-link-retry, , .
AWS Amplify , Apollo c loading error , open source , .
c Amplify DataStore:
!
, DataStore . , , .
AWS Amplify: Telegram
Github.
, 5
git clone https://github.com/fullstackserverless/startup.git
cd startup
Install dependencies
yarn
npm install
AWS account
, AWS
5 .
!!!
, 1$
Amplify Command Line Interface (CLI)
AWS Amplify React Native
React Native AWS Amplify
amplify init
:
, , , .
amplify add auth
. . auth ampify/backend/auth
, . default. Enter . Email( SMS ).
amplify push
All resources are updated in the cloud
.
ampify-app
c DataStore — npx- ampify-app.
npx amplify-app@latest
React Native Cli,
yarn add @aws-amplify/datastore @react-native-community/netinfo @react-native-community/async-storage
React Native> 0.60, iOS:
cd ios && pod install && cd ..
API(App Sync)
, .
, API
amplify add api
GraphQL amplify/backend/api/<datasourcename>/schema.graphql :
type Job
  @model
  @auth(
    rules: [
      {allow: owner, ownerField: "owner", operations: [create, update, delete]},
    ])
{
  id: ID!
  position: String!
  rate: String!
  description: String!
  owner: String
}
, DataStore, — . GraphQL JavaScript, iOS Android , . GraphQL , Automerge AppSync. NPX Amplify CLI.
AWS DataStore, , , Amplify CLI .
,
npm run amplify-modelgen
src/models
API
DataStore API
amplify update api
amplify push
All resources are updated in the cloud
READ
JobsMain src/screens/Jobs/JobsMain.js
Query, , useQuery , Flatlist.
import React, { useEffect, useState } from 'react'
import { FlatList } from 'react-native'
import { Auth } from 'aws-amplify'
import { AppContainer, CardVacancies, Space, Header } from 'react-native-unicorn-uikit'
import { DataStore } from '@aws-amplify/datastore'
import { Job } from '../../models'
import { goBack, onScreen } from '../../constants'
const JobsMain = ({ navigation }) => {
  const [data, updateJobs] = useState([])
  const fetchJobs = async () => {
    const mess = await DataStore.query(Job)
    updateJobs(mess)
  }
  useEffect(() => {
    fetchJobs()
    const subscription = DataStore.observe(Job).subscribe(() => fetchJobs())
    return () => {
      subscription.unsubscribe()
    }
  }, [data])
  const _renderItem = ({ item }) => {
    const owner = Auth.user.attributes.sub
    const check = owner === item.owner
    return (
      <>
        <CardVacancies obj={item} onPress={onScreen(check ? 'JOB_ADD' : 'JOB_DETAIL', navigation, item)} />
        <Space height={20} />
      </>
    )
  }
  const _keyExtractor = (obj) => obj.id.toString()
  return (
    <AppContainer onPress={goBack(navigation)} flatlist>
      <FlatList
        scrollEventThrottle={16}
        data={data}
        renderItem={_renderItem}
        keyExtractor={_keyExtractor}
        onEndReachedThreshold={0.5}
        ListHeaderComponent={
          <Header
            onPress={goBack(navigation)}
            onPressRight={onScreen('JOB_ADD', navigation)}
            iconLeft="angle-dobule-left"
            iconRight="plus-a"
          />
        }
        stickyHeaderIndices={[0]}
      />
    </AppContainer>
  )
}
export { JobsMain }
JobDetail src/screens/Jobs/JobDetail.js
import React from 'react'
import { Platform } from 'react-native'
import { AppContainer, CardVacancies, Space, Header } from 'react-native-unicorn-uikit'
import { goBack } from '../../constants'
const JobDetail = ({ route, navigation }) => {
  return (
    <AppContainer>
      <Header onPress={goBack(navigation)} iconLeft="angle-dobule-left" />
      <CardVacancies obj={route.params} detail />
      <Space height={Platform.OS === 'ios' ? 100 : 30} />
    </AppContainer>
  )
}
export { JobDetail }
CREATE UPDATE DELETE
JobAdd src/screens/Jobs/JobAdd.js, CREATE UPDATE DELETE
import React, { useState, useEffect, useRef } from 'react'
import { AppContainer, Input, Space, Button, Header, ButtonLink } from 'react-native-unicorn-uikit'
import { DataStore } from '@aws-amplify/datastore'
import { Formik } from 'formik'
import * as Yup from 'yup'
import { Job } from '../../models'
import { goBack } from '../../constants'
const JobAdd = ({ route, navigation }) => {
  const [loading, setLoading] = useState(false)
  const [check, setOwner] = useState(false)
  const [error, setError] = useState('')
  const [input, setJob] = useState({
    id: '',
    position: '',
    rate: '',
    description: ''
  })
  const formikRef = useRef()
  useEffect(() => {
    const obj = route.params
    if (typeof obj !== 'undefined') {
      setOwner(true)
      setJob(obj)
      const { setFieldValue } = formikRef.current
      const { position, rate, description } = obj
      setFieldValue('position', position)
      setFieldValue('rate', rate)
      setFieldValue('description', description)
    }
  }, [route.params])
  const createJob = async (values) => (await DataStore.save(new Job({ ...values }))) && goBack(navigation)()
  const updateJob = async ({ position, rate, description }) => {
    try {
      setLoading(true)
      const original = await DataStore.query(Job, input.id)
      const update = await DataStore.save(
        Job.copyOf(original, (updated) => {
          updated.position = position
          updated.rate = rate
          updated.description = description
        })
      )
      update && goBack(navigation)()
      setLoading(false)
    } catch (err) {
      setError(err)
    }
  }
  const deleteJob = async () => {
    try {
      setLoading(true)
      const job = await DataStore.query(Job, input.id)
      const del = await DataStore.delete(job)
      del && goBack(navigation)()
      setLoading(false)
    } catch (err) {
      setError(err)
    }
  }
  return (
    <AppContainer onPress={goBack(navigation)} loading={loading} error={error}>
      <Header onPress={goBack(navigation)} iconLeft="angle-dobule-left" />
      <Space height={20} />
      <Formik
        innerRef={formikRef}
        initialValues={input}
        onSubmit={(values) => (check ? updateJob(values) : createJob(values))}
        validationSchema={Yup.object().shape({
          position: Yup.string().min(3).required(),
          rate: Yup.string().min(3).required(),
          description: Yup.string().min(3).required()
        })}
      >
        {({ values, handleChange, errors, setFieldTouched, touched, isValid, handleSubmit }) => (
          <>
            <Input
              name="position"
              value={values.position}
              onChangeText={handleChange('position')}
              onBlur={() => setFieldTouched('position')}
              placeholder="Position"
              touched={touched}
              errors={errors}
            />
            <Input
              name="rate"
              keyboardType="numeric"
              value={`${values.rate}`}
              onChangeText={handleChange('rate')}
              onBlur={() => setFieldTouched('rate')}
              placeholder="Rate"
              touched={touched}
              errors={errors}
            />
            <Input
              name="description"
              value={values.description}
              onChangeText={handleChange('description')}
              onBlur={() => setFieldTouched('description')}
              placeholder="Description"
              touched={touched}
              errors={errors}
              multiline
              numberOfLines={5}
            />
            <Space height={40} />
            <Button title={check ? 'Update' : 'Create'} disabled={!isValid} onPress={handleSubmit} formik />
            {check && (
              <>
                <Space height={10} />
                <ButtonLink title="or" textStyle={{ alignSelf: 'center' }} />
                <Space height={15} />
                <Button title="DELETE" onPress={deleteJob} cancel />
              </>
            )}
          </>
        )}
      </Formik>
      <Space height={100} />
    </AppContainer>
  )
}
export { JobAdd }
screens/Jobs/index.js
export * from './JobsMain'
export * from './JobDetail'
export * from './JobAdd'
Jobs StackNavigator
import * as React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import { enableScreens } from 'react-native-screens' // eslint-disable-line
import { Hello, SignUp, SignIn, ConfirmSignUp, User, Forgot, ForgotPassSubmit } from './screens/Authenticator'
import { JobsMain, JobDetail, JobAdd } from './screens/Jobs'
enableScreens()
const Stack = createStackNavigator()
const AppNavigator = () => {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false
      }}
      initialRouteName="HELLO"
    >
      <Stack.Screen name="HELLO" component={Hello} />
      <Stack.Screen name="SIGN_UP" component={SignUp} />
      <Stack.Screen name="SIGN_IN" component={SignIn} />
      <Stack.Screen name="FORGOT" component={Forgot} />
      <Stack.Screen name="FORGOT_PASSWORD_SUBMIT" component={ForgotPassSubmit} />
      <Stack.Screen name="CONFIRM_SIGN_UP" component={ConfirmSignUp} />
      <Stack.Screen name="USER" component={User} />
      <Stack.Screen name="JOBS_MAIN" component={JobsMain} />
      <Stack.Screen name="JOB_DETAIL" component={JobDetail} />
      <Stack.Screen name="JOB_ADD" component={JobAdd} />
    </Stack.Navigator>
  )
}
export default AppNavigator
Jobs
User screens/Authenticator/User/index.js
import React, { useState, useEffect } from 'react'
import { Auth } from 'aws-amplify'
import * as Keychain from 'react-native-keychain'
import { AppContainer, Button } from 'react-native-unicorn-uikit'
import { goHome, onScreen } from '../../../constants'
const User = ({ navigation }) => {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  useEffect(() => {
    const checkUser = async () => {
      await Auth.currentAuthenticatedUser()
    }
    checkUser()
  })
  const _onPress = async () => {
    setLoading(true)
    try {
      await Auth.signOut()
      await Keychain.resetInternetCredentials('auth')
      goHome(navigation)()
    } catch (err) {
      setError(err.message)
    }
  }
  const _onPressJob = () => onScreen('JOBS_MAIN', navigation)() //    JOBS_MAIN
  return (
    <AppContainer message={error} loading={loading}>
      <Button title="Sign Out" onPress={_onPress} />
      <Button title="Jobs" onPress={_onPressJob} />
    </AppContainer>
  )
}
export { User }
构建应用程序并进行测试
完成了
参考文献
https://aws-amplify.imtqy.com
https://learning.oreilly.com/library/view/full-stack-serverless/9781492059882/
https://www.altexsoft.com/blog/engineering/graphql-core-features-architecture-pros-and-cons/
https://engineering.fb.com/core-data/graphql-a-data-query-language/
https://graphql.org/learn