Auf Wiedersehen Redux, MobX, Apollo! Die Linie zwischen Backend und Frontend ist unterbrochen! Ein innovativer Schritt in der Entwicklung staatlicher Manager.

Eine der schwierigsten Aufgaben bei der Entwicklung von Web- und Mobilanwendungen besteht darin, Daten zwischen Geräten zu synchronisieren und Offline-Vorgänge auszuführen. Wenn das Gerät offline ist, sollten Ihre Kunden Ihre Anwendung im Idealfall weiterhin verwenden können, um nicht nur auf Daten zuzugreifen, sondern diese auch zu erstellen und zu ändern. Wenn das Gerät wieder online ist, muss die Anwendung erneut eine Verbindung zum Backend herstellen, die Daten synchronisieren und etwaige Konflikte lösen. Die korrekte Behandlung aller Extremfälle erfordert viel undifferenzierten Code, selbst wenn der AWS AppSync SDK-Cache auf einem Gerät mit autonomen Mutationen und Delta-Synchronisation verwendet wird.
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 }
Erstellen Sie die Anwendung und testen Sie sie
Erledigt
Verweise
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