Представлено в предыдущей статьеСоздание среды разработки React Native, мы можем успешно запустить приложение helloword локально. В этом разделе начнется подробный анализ того, как построить архитектуру приложения React Native App и обеспечить полную предварительную версию локального запуска.
Добро пожаловать в мой личный блог
предисловие
Уже есть много инструментов для строительных лесов, таких какignite, поддерживает создание структуры проекта React Native App в один клик, что очень удобно, но, пользуясь удобством, он также лишается возможности полностью изучить структуру проекта и стек технологий, и обычно архитектура технологии приложения, созданная с помощью скаффолдинга, не может полностью соответствуют нашим требованиям бизнеса, нам нужно изменять и улучшать себя, поэтому, если вы хотите иметь более глубокий контроль над структурой проекта, лучше всего понять проект от 0 до 1.
Структура проекта и стек технологий
первое использованиеreact-native-cli
Инструменты для создания приложения React Native:
react-native init fuc
Сгенерированная структура проекта выглядит следующим образом:
- Каталоги android и ios хранят соответствующий собственный код платформы соответственно;
-
package.json
Управление файлами для зависимостей проекта; -
index.ios.js
Входной файл для платформы ios,index.android.js
Файл входа для платформы Android, обычно используемый для регистрации корневого компонента React Native App; -
.babelrc
файл, файл конфигурации babel, React Native по умолчанию использует babel для компиляции кода JavaScript; -
__tests__
Каталог тестов проекта.
Мы видим, что нет каталога для хранения нативного JavaScript-кода React Native, который нужно создавать самим, обычно создаваяsrc
Каталог используется в качестве корневого каталога всех кодов и ресурсов в части Javascript приложения App.src/constants
каталог для хранения глобально общих постоянных данных,src/config
Каталог содержит глобальную конфигурацию,src/helpers
Храните глобальный помощник, методы служебного класса,src/app.js
Как часть файла записи RN обычно необходимо создать каталог для сохранения redux каждого модуля, каталог для промежуточного ПО redux и так далее.
стек технологий
Построение архитектуры проекта во многом зависит от стека технологий проекта, поэтому сначала проанализируйте весь стек технологий и подведите итоги:
- реагировать родной + реактивная библиотека является предпосылкой проекта
- Навигация по приложению (отличается от концепции маршрутизации приложения React)
- Контейнер управления состоянием приложения
- Вам нужны неизменяемые данные
- Постоянство состояния приложения
- Асинхронное управление задачами
- Тесты и вспомогательные инструменты или функции
- Инструменты разработки и отладки
В соответствии с приведенным выше разделением для формирования полного стека технологий проекта выбираются следующие сторонние библиотеки и инструменты:
- реактивная библиотека + реакционная библиотека классов;
- react-navigation управляет навигацией по приложению;
- Redux действует как контейнер состояния JavaScript, а react-redux соединяет приложения React Native с redux;
- Immutable.js поддерживает неизменяемое состояние, redux-immutable делает неизменяемым все дерево состояний хранилища redux;
- Используйте redux-persist для поддержки постоянства дерева состояний redux и добавьте расширение redux-persist-immutable для поддержки постоянства дерева состояний Immutable;
- Используйте redux-saga для управления асинхронными задачами в приложении, такими как сетевые запросы, асинхронное чтение локальных данных и т. д.;
- Используйте Jest Integrated Application Test, используйте Lodash, RAMDA и т. д. Дополнительный вторичный класс, библиотеки классов инструментов;
- Используйте инструмент отладки Reactotron
С учетом приведенного выше анализа улучшенная структура проекта показана на рисунке:
Как показано выше, создайте его в корневом каталоге проекта.src
каталог и создайте 12 каталогов и 1 js-файл частичной записи React Native по очереди в каталоге src.
Инструменты разработки и отладки
Разработка приложений React Native в настоящее время имеет множество инструментов отладки, обычно используемых, таких как атом иNuclide, инструмент отладки, поставляемый с мобильным симулятором,ReactronЖдать.
Nuclide
Nuclideпредставляет собой интегрированную среду разработки на основе атома, предоставляемую Facebook, которую можно использовать для написания,бегатьа такжеотладкаПриложение React Native.
Средство отладки симулятора
После того, как эмулятор запустится и запустит приложение, автоматически откроется браузер.http://localhost:8081/debugger-ui
страницы, вы можете выполнять вывод отладки js и удаленную отладку точки останова js в консоли; используйте сочетания клавиш в терминале симулятораcommand
ДобавитьD
ключ для открытия инструментов отладки, включая перезагрузку приложения, включение горячей перезагрузки, переключение инспекторов DOM и т. д.:
Reactotron
ReactotronЭто настольное приложение для кросс-платформенной отладки приложений React и React Native. Оно может динамически отслеживать и выводить информацию, такую как избыточность, действие и асинхронные запросы саги для приложений React, в режиме реального времени, как показано на рисунке:
Сначала инициализируйте конфигурацию, связанную с Reactotron:
import Config from './DebugConfig';
import Immutable from 'immutable';
import Reactotron from 'reactotron-react-native';
import { reactotronRedux as reduxPlugin } from 'reactotron-redux';
import sagaPlugin from 'reactotron-redux-saga';
if (Config.useReactotron) {
// refer to https://github.com/infinitered/reactotron for more options!
Reactotron
.configure({ name: 'Os App' })
.useReactNative()
.use(reduxPlugin({ onRestore: Immutable }))
.use(sagaPlugin())
.connect();
// Let's clear Reactotron on every time we load the app
Reactotron.clear();
// Totally hacky, but this allows you to not both importing reactotron-react-native
// on every file. This is just DEV mode, so no big deal.
console.tron = Reactotron;
}
затем включитеconsole.tron.overlay
Метод расширяет входной компонент:
import './config/ReactotronConfig';
import DebugConfig from './config/DebugConfig';
class App extends Component {
render () {
return (
<Provider store={store}>
<AppContainer />
</Provider>
)
}
}
// allow reactotron overlay for fast design in dev mode
export default DebugConfig.useReactotron
? console.tron.overlay(App)
: App
На этом этапе вы можете использовать клиент Reactotron для захвата всех избыточных данных и действий, инициированных в приложении.
Разделение компонентов
Приложения React Native по-прежнему следуют принципу разработки компонентов React. Компоненты отвечают за визуализацию пользовательского интерфейса. Разные состояния компонентов соответствуют разным пользовательским интерфейсам. Обычно следуют следующим идеям дизайна компонентов:
- Компоненты макета: компоненты, которые включают только структуру интерфейса пользовательского интерфейса приложения, не включают какую-либо бизнес-логику, запросы данных и операции;
- Компонент-контейнер: отвечает за получение данных, обработку бизнес-логики и обычно возвращает презентационные компоненты в функции render();
- Компонент отображения: отвечает за отображение пользовательского интерфейса приложения;
- Компоненты пользовательского интерфейса: относятся к абстрактным многократно используемым независимым компонентам пользовательского интерфейса, обычно компонентам без состояния;
Компоненты дисплея | компонент контейнера | |
---|---|---|
Цель | Представление пользовательского интерфейса (структура и стиль HTML) | Бизнес-логика (получение данных, обновление статуса) |
Осведомлен о Редукс | без | имеют |
Источники данных | props | Подпишитесь на магазин Redux |
изменить данные | Вызовите функцию обратного вызова, переданную реквизитами | Dispatch Redux actions |
многоразовый | Сильная независимость | Высокая степень связанности бизнеса |
Кроссплатформенная адаптация
При создании кроссплатформенного приложения, хотя в React Native была проделана большая работа по кроссплатформенной совместимости, все еще бывают ситуации, когда для разных платформ необходимо разрабатывать разный код, и в это время требуется дополнительная обработка.
Кросс-платформенный каталог
Мы можем различать разные файлы кода платформы в разных каталогах, например:
/common/components/
/android/components/
/ios/components/
common
Общие файлы хранятся в каталоге,android
В каталоге хранится код файла Android,ios
Сохраните код файла ios, но обычно выбирайте лучший метод, предоставляемый React Native, который будет представлен позже.
Платформенный модуль
В React Native есть встроенный модуль Platform, позволяющий различать текущую запущенную платформу приложения.Platform.OS
значениеios
, работает на платформе Androidandroid
, вы можете использовать этот модуль для загрузки соответствующего файла платформы:
var StatusBar = Platform.select({
ios: () => require('ios/components/StatusBar'),
android: () => require('android/components/StatusBar'),
})();
Затем используйте компонент StatusBar как обычно.
Обнаружение платформы React Native
Когда на компонент ссылаются, React Native проверит, существует ли файл.android
или.ios
Суффикс, если он существует, загрузите соответствующий файловый компонент в соответствии с текущей платформой, например:
StatusBar.ios.js
StatusBar.indroid.js
Если указанные выше два файла существуют в одном и том же каталоге, на них можно ссылаться следующими способами:
import StatusBar from './components/StatusBar';
React загрузит соответствующий файл суффикса в соответствии с текущей платформой.Рекомендуется использовать этот метод для адаптации кода на уровне компонентов платформы, а для небольшой части кода, который необходимо адаптировать к платформе, вы можете использоватьPlatform.OS
Значение выглядит следующим образом, если вам нужно только добавить более высокое значение margin-top для платформы ios, и это не общедоступный стиль:
var styles = StyleSheet.create({
marginTop: (Platform.OS === 'ios') ? 20 : 10,
});
Навигация по приложениям и маршрутизация
В отличие от одностраничной маршрутизации приложений React, React Native обычно существует в виде нескольких страниц, переключаясь между разными страницами и компонентами с помощью навигации, а не управляя отображением различных компонентов с помощью маршрутизации. навигационная библиотека навигации.
Навигация и маршрутизация
В веб-приложениях React отображение и переключение компонентов пользовательского интерфейса страницы полностью контролируется маршрутизацией, и каждый маршрут имеет соответствующий URL-адрес и информацию о маршрутизации.В приложениях React Native он отображается не компонентами, управляемыми маршрутизацией, а компонентами, управляемыми навигацией отображение экрана переключения.Каждый экран имеет свою собственную информацию о маршрутизации.
Возможно, вы уже полагались на конфигурацию маршрутизации одностраничного приложения react-router и хотите создать кроссплатформенное приложение на основе URL, TOEFL активен в сообществе с открытым исходным кодом, вы можете использоватьreact-router-native, но не рекомендуется, так как для Приложения с учетом взаимодействия и опыта больше подходит использование многостраничной (экранной) формы.
react-navigation
Используя реагирующую навигацию, вы можете определить структуру навигации кроссплатформенного приложения, а также поддерживать настройку и визуализацию кроссплатформенных панелей навигации, панелей вкладок и других компонентов.
Встроенный навигационный модуль.
react-navigation предоставляет следующие методы для поддержки создания различных типов навигации:
- StackNavigator: Создать стек (стек) навигационных экранов, все экраны (экраны) существуют в виде стеков, отображать по одному экрану за раз, улучшать анимацию преобразования при переключении экранов и размещать экран на вершине стека при экран открыт;
- TabNavigator: создайте навигацию в стиле вкладок, визуализируйте строку меню вкладок, чтобы пользователи могли переключаться между различными экранами;
- DrawerNavigator: создание навигации по ящикам, выдвижение экрана из левой части экрана;
StackNavigator
StackNavigatorПоддерживайте кросс-платформенное переключение между различными экранами преобразующим образом и размещайте текущий экран на вершине стека.Метод вызова выглядит следующим образом:
StackNavigator(RouteConfigs, StackNavigatorConfig)
RouteConfigs
Объект конфигурации маршрутизации (маршрута) стека навигации, определите имя маршрута и объект маршрута, объект маршрута определяет компонент отображения, соответствующий текущему маршруту, например:
// routes为路由信息对象
StackNavigator({
[routes.Main.name]: Main,
[routes.Login.name]: {
path: routes.Login.path,
screen: LoginContainer,
title: routes.Login.title
}
}
Как и выше, указывая, что когда приложение переходит к маршрутуroutes.Login.name
при рендерингеLoginContainer
Компонент, заданный свойством экрана объекта, при переходе к маршрутуroutes.Main.name
значение, соответствующее рендерингу MainStack, в кодеMain
Объект:
{
path: routes.Main.path,
screen: MainStack,
navigationOptions: {
gesturesEnabled: false,
},
}
А MainStack — это Stacknavigator:
const MainStack = StackNavigator({
Home: HomeTabs
})
HomeTabs — это TabNavigator:
{
name: 'Home Tabs',
description: 'Tabs following Home Tabs',
headerMode: 'none',
screen: HomeTabs
};
StackNavigatorConfig
Объект конфигурации маршрутизации, вы можете дополнительно настроить дополнительные атрибуты, такие как:
-
initialRouteName
, экран по умолчанию начального стека навигации должен быть именем ключа в объекте конфигурации маршрутизации; -
initialRouteParams
, параметры исходного маршрута по умолчанию; -
navigationOptions
, установите конфигурацию экрана навигации по умолчанию;- заголовок: заголовок в верхней части навигационного экрана;
-
headerMode
, показывать ли верхнюю панель навигации:- нет: не отображать панель навигации;
- float: отображает независимую панель навигации вверху и сопровождается анимацией при переключении экранов, обычно это режим отображения ios;
- экран: привяжите панель навигации для каждого экрана, а также постепенное появление и исчезновение при переключении экрана, обычно в режиме отображения Android;
-
mode
, стиль и эффект трансформации при навигации по экрану переключения:-
card
: режим по умолчанию, стандартное преобразование экрана; -
modal
: действует только на платформе ios, сделайте так, чтобы нижняя часть экрана выдвигалась из нового экрана;
-
{
initialRouteName: routes.Login.name,
headerMode: 'none', // 去除顶部导航栏
/**
* Use modal on iOS because the card mode comes from the right,
* which conflicts with the drawer example gesture
*/
mode: Platform.OS === 'ios' ? 'modal' : 'card'
}
TabNavigator
использоватьTabNavigatorВы можете создать экран, с помощью TabRouter вы можете переключаться между различными вкладками, метод вызова следующий:
TabNavigator(RouteConfigs, TabNavigatorConfig)
RouteConfigs
Объект конфигурации маршрутизации Tab, формат аналогичен StackNavigator.
TabNavigatorConfig
Объекты конфигурации, связанные с навигацией по вкладкам, такие как:
- tabBarComponent: компонент, используемый строкой меню вкладок, который используется по умолчанию на платформе ios.
TabBarBottom
Компонент, используемый по умолчанию на платформе Android.TabBarTop
компоненты; - tabBarPosition: положение строки меню вкладки,
top
илиbottom
; - tabBarOptions: конфигурация строки меню вкладки:
- activeTintColor: цвет шрифта и значка элемента строки меню активной вкладки.
- InitialRouteName: routeName маршрута tabRoute по умолчанию во время начальной загрузки, что соответствует имени ключа объекта конфигурации маршрутизации.
- порядок: сортировка вкладок, массив, состоящий из routeName;
const HomeTabs = TabNavigator(
{
Notification: {
screen: NotificationTabContainer,
path: 'notification',
navigationOptions: {
title: '消息通知'
}
},
Myself: {
screen: MyselfTabContainer,
path: 'myself',
navigationOptions: {
title: '我的'
}
}
},
{
tabBarOptions: {
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
},
swipeEnabled: true
}
);
DrawerNavigator
использоватьDrawerNavigatorВы можете создать экран навигации по ящикам, который называется следующим образом:
DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)
const MyDrawer = DrawerNavigator({
Home: {
screen: MyHomeDrawerScreen,
},
Notifications: {
screen: MyNotificationsDrawerScreen,
},
});
RouteConfigs
Объект конфигурации маршрутизации навигации по выдвижным ящикам, формат аналогичен StackNavigator.
DrawerNavigatorConfig
Объекты конфигурации навигационного ящика, такие как:
- drawerWidth: ширина экрана ящика;
- drawerPosition: положение экрана ящика,
left
илиright
; - contentComponent: компонент содержимого экрана Drawer, такой как встроенный
DrawerItems
; - initialRouteName: имя исходного маршрута;
import { DrawerItems } from 'react-navigation';
const CustomDrawerContentComponent = (props) => (
<View style={styles.container}>
<DrawerItems {...props} />
</View>
);
const DrawerNavi = DrawerNavigator({}, {
drawerWidth: 200,
drawerPosition: 'right',
contentComponent: props => <CustomDrawerContentComponent {...props}/>,
drawerBackgroundColor: 'transparent'
})
Navigation prop
Каждый экран приложения RN будет принимать свойство навигации, содержащее следующие методы и свойства:
- navigation: вспомогательный метод для перехода к другим экранам;
- setParams: метод изменения параметров маршрутизации;
- goBack: закрыть текущий экран и вернуться назад;
- состояние: текущее состояние экрана или информация о маршрутизации;
- рассылка: опубликовать действие;
navigate
Используйте метод навигации для перехода к другим экранам:
navigate(routeName, params, action)
- routeName: имя целевого маршрута, имя ключа маршрута, зарегистрированное в маршруте навигации приложения;
- params: параметры целевого маршрута;
- действие: если у целевого маршрута есть подмаршрут, выполните это действие на подмаршруте;
setParams
Измените текущую информацию о маршруте навигации, например, задайте и измените заголовок навигации и другую информацию:
class ProfileScreen extends React.Component {
render() {
const { setParams } = this.props.navigation;
return (
<Button
onPress={() => setParams({name: 'Jh'})}
title="Set title"
/>
)
}
}
goBack
Перейти назад от текущего экрана (параметр пуст) или указанного экрана (параметр является именем ключа маршрутизации экрана) к предыдущему экрану экрана и закрыть экран; если переданоnull
параметра исходный экран не указывается, то есть экран не будет выключен.
state
Каждый экран имеет свою собственную информацию о маршрутизации, доступ к которой можно получить черезthis.props.navigation.state
доступ, формат возвращаемых данных следующий:
{
// the name of the route config in the router
routeName: 'Login',
//a unique identifier used to sort routes
key: 'login',
//an optional object of string options for this screen
params: { user: 'jh' }
}
dispatch
Этот метод используется для распределения действий навигации по маршрутам для реализации навигации.react-navigation
Функция создания действий предусмотрена по умолчаниюNavigationActions
, следующим образом, чтобы распределить действие экрана переключателя навигации:
import { NavigationActions } from 'react-navigation'
const navigateAction = NavigationActions.navigate({
routeName: routeName || routes.Login.name,
params: {},
// navigate can have a nested navigate action that will be run inside the child router
action: NavigationActions.navigate({ routeName: 'Notification'})
});
// dispatch the action
this.props.navigation.dispatch(navigateAction);
Навигация и Редукс
После использования Redux нужно следовать принципу redux: единый доверенный источник данных, то есть все источники данных могут быть только reudx store, и состояние маршрутизации Navigation не должно быть исключением, поэтому нужно подключить состояние Navigation с состоянием хранилища вы можете создать редуктор навигации, чтобы объединить состояние навигации в хранилище:
import AppNavigation from '../routes';
const NavigationReducer = (state = initialState, action) => {
const newState = Object.assign({}, state, AppNavigation.router.getStateForAction(action, state));
return newState || state;
};
export const NavigationReducers = {
nav: NavigationReducer
};
Все, что делает этот редьюсер, — это объединяет состояние маршрутизации навигации приложения в хранилище.
Redux
Если в каком-либо современном крупномасштабном веб-приложении отсутствует контейнер управления состоянием, то приложению не хватает характеристик времени.Дополнительные библиотеки, такие как mobx, redux и т. д., на самом деле похожи, и каждая берет то, что им нужно. например, redux является наиболее часто используемой библиотекой контейнеров состояния приложения React, которая также подходит для приложений React Native.
react-redux
Как и в случае с приложением React, вам необходимо соединить Redux и приложение для унифицированного управления состоянием приложения с помощью redux и использовать официально предоставленныйreact-reduxбиблиотека.
class App extends Component {
render () {
return (
<Provider store={store}>
<AppContainer />
</Provider>
)
}
}
createStore
используя предоставленный редуксcreateStore
метод для создания хранилища избыточности, но в реальных проектах нам часто нужно расширить избыточность, чтобы добавить некоторые пользовательские функции или службы, такие как добавление промежуточного программного обеспечения избыточности, добавление саги об асинхронном управлении задачами, улучшение избыточности и т. д.:
// creates the store
export default (rootReducer, rootSaga, initialState) => {
/* ------------- Redux Configuration ------------- */
const middleware = [];
const enhancers = [];
/* ------------- Analytics Middleware ------------- */
middleware.push(ScreenTracking);
/* ------------- Saga Middleware ------------- */
const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middleware.push(sagaMiddleware);
/* ------------- Assemble Middleware ------------- */
enhancers.push(applyMiddleware(...middleware));
/* ------------- AutoRehydrate Enhancer ------------- */
// add the autoRehydrate enhancer
if (ReduxPersist.active) {
enhancers.push(autoRehydrate());
}
// if Reactotron is enabled (default for __DEV__),
// we'll create the store through Reactotron
const createAppropriateStore = Config.useReactotron ? console.tron.createStore : createStore;
const store = createAppropriateStore(rootReducer, initialState, compose(...enhancers));
// configure persistStore and check reducer version number
if (ReduxPersist.active) {
RehydrationServices.updateReducers(store);
}
// kick off root saga
sagaMiddleware.run(rootSaga);
return store;
}
редукс и неизменяемый
redux предоставляет по умолчаниюcombineReducers
Метод интегрирует редюсеры в избыточность, однако метод по умолчанию предполагает принимать собственные объекты JavaScript и обрабатывает состояние как собственные объекты, поэтому, когда мы используемcreateStore
метод и принимает неизменяемый объект в качестве начального состояния приложения,reducer
Будет возвращена ошибка, исходный код выглядит следующим образом:
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` + ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
".Expected argument to be an object with the following +
`keys:"${reducerKeys.join('", "')}"`
)
}
Как показано выше, параметр состояния, принимаемый редуктором примитивного типа, должен быть нативным объектом JavaScript, нам нужноcombineReducers
Он улучшен, чтобы он мог обрабатывать неизменяемые объекты,redux-immutable
т. е. обеспечивает возможность созданияImmutable.jsСовместный РедуксcombineReducers.
import { combineReducers } from 'redux-immutable';
import Immutable from 'immutable';
import configureStore from './CreateStore';
// use Immutable.Map to create the store state tree
const initialState = Immutable.Map();
export default () => {
// Assemble The Reducers
const rootReducer = combineReducers({
...NavigationReducers,
...LoginReducers
});
return configureStore(rootReducer, rootSaga, initialState);
}
Как и в приведенном выше коде, вы можете видеть, что мы передалиinitialState
ЯвляетсяImmutable.Map
Тип данных, мы неизменяем корень всего дерева состояний redux и передаем редюсеры и саги, которые могут обрабатывать неизменяемое состояние.
Кроме того, данные каждого узла дерева состояний представляют собой неизменяемую структуру, напримерNavigationReducer
:
const initialState = Immutable.fromJS({
index: 0,
routes: [{
routeName: routes.Login.name,
key: routes.Login.name
}]
});
const NavigationReducer = (state = initialState, action) => {
const newState = state.merge(AppNavigation.router.getStateForAction(action, state.toJS()));
return newState || state;
};
Узел состояния редуктора по умолчанию использует метод Immutable.fromJS() для преобразования его в структуру Immutable и использует метод Immutable при обновлении состояния.state.merge()
, чтобы гарантировать, что состояние является однородным и предсказуемым.
избыточное постоянство
Мы знаем, что браузер имеет функцию кэширования ресурсов по умолчанию и предоставляет методы локального постоянного хранения, такие как localStorage, indexDb, webSQL и т. д., обычно некоторые данные могут храниться локально, и в определенный период, когда пользователь снова обращается, они прямое восстановление из локальных данных может значительно повысить скорость запуска приложения и повысить удобство работы пользователей.Для приложений-приложений общим требованием является сохранение некоторых данных запуска локально или даже в автономных приложениях.Мы можем использоватьAsyncStorage(Похоже на локальное хранилище в Интернете) для хранения некоторых данных, если это большой объем хранилища данных, вы можете использовать SQLite.
Кроме того, в отличие от предыдущего прямого хранения данных, при запуске приложения данные считываются локально, а затем восстанавливаются.Для redux-приложений, если хранятся только данные, то мы должны расширять для каждого редюсера, а читать и сохранять, когда приложение запускается заново.Это громоздкий и неэффективный способ.Можете ли вы попробовать сохранить ключ редьюсера, а затем восстановить соответствующие постоянные данные по ключу?Сначала зарегистрируйте редуктор Rehydrate, при срабатывании действия восстановите данные в соответствии с его ключом редуктора, а затем нужно только распространять действия при запуске приложения, что также легко абстрагируется в настраиваемую службу расширения.redux-persistВсе это сделано для нас.
redux-persist
Чтобы реализовать персистентность избыточности, включая два процесса локального постоянного хранилища хранилища избыточности и восстановления и запуска, если вы пишете свою собственную реализацию, объем кода усложняется, вы можете использовать библиотеки с открытым исходным кодом.redux-persist
, который обеспечиваетpersistStore
а такжеautoRehydrate
Методы соответственно сохраняют локальное хранилище и восстанавливают стартовое хранилище, а также поддерживают трансформацию и расширение состояния хранилища при настройке входящего сохраняемости и восстановлении хранилища.
постоянное хранилище
Службы, связанные с persistStore, вызываются при создании хранилища следующим образом:RehydrationServices.updateReducers()
:
// configure persistStore and check reducer version number
if (ReduxPersist.active) {
RehydrationServices.updateReducers(store);
}
Постоянное хранилище хранилища реализовано в этом методе:
// Check to ensure latest reducer version
AsyncStorage.getItem('reducerVersion').then((localVersion) => {
if (localVersion !== reducerVersion) {
if (DebugConfig.useReactotron) {
console.tron.display({
name: 'PURGE',
value: {
'Old Version:': localVersion,
'New Version:': reducerVersion
},
preview: 'Reducer Version Change Detected',
important: true
});
}
// Purge store
persistStore(store, config, startApp).purge();
AsyncStorage.setItem('reducerVersion', reducerVersion);
} else {
persistStore(store, config, startApp);
}
}).catch(() => {
persistStore(store, config, startApp);
AsyncStorage.setItem('reducerVersion', reducerVersion);
})
Номер версии редуктора будет храниться в AsyncStorage, который можно настроить в файле конфигурации приложения. Номер версии и хранилище будут сохранены при первом выполнении персистентности. Если номер версии редуктора изменится, исходное сохраненное хранилище будет сохранено. очищен, иначе хранилище будет передано для метода сохраненияpersistStore
Вот и все.
persistStore(store, [config, callback])
Этот метод в основном реализует персистентность хранилища и распределяет действие регидратации:
- Подпишитесь на хранилище избыточности и запускайте операции хранения хранилища при его изменении;
- Получите данные из указанного StorageEngine (например, AsyncStorage), преобразуйте их, а затем инициируйте процесс REHYDRATE, отправив действие REHYDRATE;
Параметры приема в основном следующие:
- магазин: постоянный магазин;
- config: объект конфигурации
- хранилище: механизм сохранения, такой как LocalStorage и AsyncStorage;
- transforms: Преобразователи, вызываемые на этапах регидратации и хранения;
- черный список: массив черных списков, указывающий ключи редукторов, которые игнорируются постоянством;
- обратный вызов: обратный вызов после завершения операции обезвоживания;
возобновить загрузку
Как и persisStore, расширение rehydrate по-прежнему инициализируется и регистрируется при создании хранилища избыточности:
// add the autoRehydrate enhancer
if (ReduxPersist.active) {
enhancers.push(autoRehydrate());
}
Функция, реализованная этим методом, очень проста, то есть, используя постоянные данные для регидратации данных в хранилище, она фактически регистрирует редюсер autoRehydarte, который получит действие регидратации, распределенное предыдущим методом persistStore, а затем объединит состояние.
Конечно, autoRehydrate не нужен, мы можем настроить способ восстановления магазина:
import {REHYDRATE} from 'redux-persist/constants';
//...
case REHYDRATE:
const incoming = action.payload.reducer
if (incoming) {
return {
...state,
...incoming
}
}
return state;
обновление новой версии
Следует отметить, что библиотека redux-persist была выпущена до версии 5.x, аv4.xпреобладают, в новой версии есть некоторые обновления,Подробнее, пожалуйста, нажмите для просмотра.
Постоянство и неизменность
Интеграция Redux и Immutable упоминалась ранее.Использованный выше redux-persist по умолчанию может обрабатывать только состояние хранилища нативных объектов JavaScript, поэтому его необходимо расширить, чтобы он был совместим с Immutable.
redux-persist-immutable
использоватьredux-persist-immutableБиблиотеки могут легко добиться совместимости, все, что они делают, это используют предоставленныйpersistStore
Метод заменяет метод, предоставляемый redux-persist:
import { persistStore } from 'redux-persist-immutable';
transform
Мы знаем, что при сохранении хранилища лучше ориентироваться на нативные объекты JavaScript, потому что обычно неизменяемые структурированные данные имеют много вспомогательной информации и их непросто хранить, поэтому необходимо определить операции преобразования при сохранении и восстановлении данных:
import R from 'ramda';
import Immutable, { Iterable } from 'immutable';
// change this Immutable object into a JS object
const convertToJs = (state) => state.toJS();
// optionally convert this object into a JS object if it is Immutable
const fromImmutable = R.when(Iterable.isIterable, convertToJs);
// convert this JS object into an Immutable object
const toImmutable = (raw) => Immutable.fromJS(raw);
// the transform interface that redux-persist is expecting
export default {
out: (state) => {
return toImmutable(state);
},
in: (raw) => {
return fromImmutable(raw);
}
};
Как и выше, вход и выход в выходном объекте соответствуют операциям преобразования при сохранении и восстановлении данных соответственно.fromJS()
а такжеtoJS()
Преобразуйте Js и неизменяемые структуры данных, используйте их следующим образом:
import immutablePersistenceTransform from '../services/ImmutablePersistenceTransform'
persistStore(store, {
transforms: [immutablePersistenceTransform]
}, startApp);
Immutable
После внедрения Immutable в проект нужно максимально обеспечить следующие моменты:
- Unified Immutable всего дерева состояний хранилища избыточности;
- Постоянство Redux совместимо с неизменяемыми данными;
- Навигация по приложениям совместима с Immutable;
Неизменяемость и навигация по приложениям
Первые два пункта были объяснены в предыдущих двух разделах. Третий пункт заключается в том, что Навигация совместима с неизменяемым. На самом деле, это нужно сделать состояние маршрутизации навигации совместимым с неизменяемым. В разделе Навигация и маршрутизация приложений это было представил, как подключить состояние маршрутизации навигации к хранилищу Redux.Если в приложении используется неизменяемая библиотека, требуется дополнительная обработка, преобразование состояния маршрутизатора навигации в неизменяемое и изменение вышеупомянутого NavigationReducer:
const initialState = Immutable.fromJS({
index: 0,
routes: [{
routeName: routes.Login.name,
key: routes.Login.name
}]
});
const NavigationReducer = (state = initialState, action) => {
const newState = state.merge(AppNavigation.router.getStateForAction(action, state.toJS()));
return newState || state;
};
Преобразуйте начальное состояние по умолчанию в Immutable и используйте при слиянии состояний.merge()
метод.
Асинхронное управление потоком задач
Последний модуль, который будет представлен, - это управление асинхронными задачами.В процессе разработки приложений наиболее важной асинхронной задачей является HTTP-запрос данных, поэтому мы говорим об асинхронном управлении задачами, в основном фокусируясь на управлении процессом HTTP-запроса данных.
axios
используется в этом проектеaxiosВ качестве библиотеки HTTP-запросов axios представляет собой HTTP-клиент в формате Promise.Причины выбора этой библиотеки следующие:
- XMLHttpRequest может быть инициирован в браузере, а HTTP-запрос также может быть инициирован на стороне node.js;
- обещания поддержки;
- Возможность перехвата запросов и ответов;
- Может отменить запрос;
- Автоматически преобразовывать данные JSON;
redux-saga
redux-saga — это сторонняя библиотека, предназначенная для упрощения управления асинхронными задачами, такими как сбор данных и доступ к локальному кешу в приложениях, их эффективное выполнение, облегчение тестирования и улучшенная обработка исключений.
Redux-saga - это сокращение промежуточного программного обеспечения, это похоже на приложение в отдельном процессе, оно отвечает только за управление асинхронными задачами, оно может принимать действие основного процесса приложения, чтобы принять решение о запуске, приостановке или отмене задачи процесса, он также может получить доступ к избыточности состояние хранилища приложений, а затем распространить действие.
инициализировать сагу
redux-saga — это промежуточное программное обеспечение, поэтому сначала позвонитеcreateSagaMiddleware
метод для создания промежуточного программного обеспечения, а затем использовать избыточностьapplyMiddleware
Этот метод включает ПО промежуточного слоя, а затем использует вспомогательный метод компоновки, чтобы передать его вcreateStore
Создайте магазин и, наконец, позвонитеrun
способ начать корневую сагу:
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/'
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middleware.push(sagaMiddleware);
enhancers.push(applyMiddleware(...middleware));
const store = createStore(rootReducer, initialState, compose(...enhancers));
// kick off root saga
sagaMiddleware.run(rootSaga);
диверсия саги
В проекте обычно много параллельных модулей, и поток саги каждого модуля тоже должен быть параллельным, который должен быть параллельным в виде мультиветки, предоставляемой redux-sagafork
Метод заключается в том, чтобы запустить текущий поток саги в виде вновь открытой ветки:
import { fork, takeEvery } from 'redux-saga/effects';
import LoginSagas from './LoginSagas';
const sagas = [
...LoginSagas,
...StartAppSagas
];
export default function * root() {
yield sagas.map(saga => fork(saga));
};
Как и выше, сначала соберите всю корневую сагу модуля, затем пройдитесь по массиву и запустите каждую корневую сагу потока саги.
экземпляр саги
Взяв в качестве примера LoginSagas, для операции входа в систему могут потребоваться некоторые асинхронные запросы после того, как пользователь начнет вход в систему и вход будет успешным, поэтому в списке указаны loginSaga и loginSuccessSaga.Кроме того, когда пользователь выходит из учетной записи, Также может потребоваться HTTP-запрос, поэтому logoutSaga находится здесь:
...
// process login actions
export function * loginSaga () {
yield takeLatest(LoginTypes.LOGIN, login);
}
export function * loginSuccessSaga () {
yield takeLatest(LoginTypes.LOGIN_SUCCESS, loginSuccess);
}
export function * logoutSaga () {
yield takeLatest(LogoutTypes.LOGOUT, logout);
}
const sagas = [
loginSaga,
loginSuccessSaga,
logoutSaga
];
export default sagas;
Использовать в loginSagatakeLatest
слушатель методаLoginTypes.LOGIN
действие, когда действие получено, вызовитеlogin
, вход в систему — это по сути сага, в которой обрабатываются асинхронные задачи:
function * login (action) {
const { username, password } = action.payload || {};
if (username && password) {
const res = yield requestLogin({
username,
password
});
const { data } = res || {};
if (data && data.success) {
yield put(LoginActions.loginSuccess({
username,
password,
isLogin: true
}));
} else {
yield put(LoginActions.loginFail({
username,
password,
isLogin: false
}));
}
} else {
yield put(LoginActions.loginFail({
username,
password,
isLogin: false
}));
}
}
requestLogin
Метод представляет собой HTTP-запрос входа в систему с параметрами имени пользователя и пароля изLoginTypes.LOGIN
Полезная нагрузка, переданная действием, получена,yield
Оператор извлекает ответ на запрос, присваивает его res, а затем оценивает успешность входа в систему по содержимому ответа:
- Войти успешно, раздать
LoginActions.loginSuccess
действие, которое затем выполнит редьюсер, прослушивающий это действие, иloginSuccessSaga
saga; - Не удалось войти, распространить
LoginActions.loginFail
action;
put — это диспетчерский метод действия, предоставляемый redux-saga.
сага и Реактотрон
Ранее было настроено, что вы можете использовать Reactotron для захвата всех редуксов и действий приложения, а редукс-сага — это тип промежуточного программного обеспечения редукса, поэтому захват саг требует дополнительной настройки.При создании хранилища добавьте сервис sagaMonitor в сагу промежуточное ПО для мониторинга саги:
const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middleware.push(sagaMiddleware);
...
Суммировать
В этой статье подробно описан процесс построения архитектуры проекта от 0 до 1, а также более глубокое понимание и размышления о приложениях React, React Native, Redux и методах разработки проектов, а также продолжение продвижения по пути большого внешнего интерфейса. рост.