数据存储-CRUD(创建读取更新删除)

再见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


All Articles