Руководство по проектированию архитектуры приложения React

Архитектура React.js Redux

В предыдущей статье мы представилиWebpack автоматизирует создание приложений React, наш локальный сервер разработки может лучше помочь нам писать приложения React и поддерживать горячее обновление кода. В этом разделе мы начнем подробно анализировать, как построить архитектуру приложения React.

Полный код проекта смотрите на github.

личный блог

предисловие

Уже есть много инструментов для строительных лесов, таких какcreate-react-app, который поддерживает создание структуры проекта приложения React одним кликом, что очень удобно, но наслаждаясь удобством, он также лишается возможности полностью изучить архитектуру проекта и стек технологий, и обычно архитектуру технологии приложения, созданную с помощью скаффолдинга не может полностью удовлетворить наши бизнес-потребности, нам нужно модифицировать и улучшать его самим, поэтому, если вы хотите иметь более глубокий контроль над структурой проекта, лучше всего понять проект от 0 до 1.

Структура проекта и стек технологий

Наша практика на этот раз не будет использовать какие-либо строительные леса, поэтому нам нужно создать каждый файл самостоятельно, внедрить каждую технологию и стороннюю библиотеку и, наконец, сформировать законченное приложение, включая полный стек технологий, который мы выберем.

Первым шагом, конечно же, является создание каталога, что мы и сделали в предыдущей статье.Если у вас еще нет кода, вы можете получить его на Github:

git clone https://github.com/codingplayboy/react-blog.git
cd react-blog

Сгенерированная структура проекта выглядит следующим образом:

React项目初始结构

  1. srcДля каталога исходного кода приложения;
  2. webpackнастроить каталог для веб-пакета;
  3. webpack.config.jsНастройте входные файлы для веб-пакета;
  4. package.jsonУправление файлами для зависимостей проекта;
  5. yarn.lockФайлы блокировки версий для зависимостей проекта;
  6. .babelrcфайл, файл конфигурации babel, используйте babel для компиляции кода React и JavaScript;
  7. eslintrcиeslintignoreСоответственно настройте определение синтаксиса eslint и содержимое или файлы, которые необходимо игнорировать;
  8. postcss.config.jsКонфигурационный файл посткомпилятора CSS postcss;
  9. API.mdЭто запись документа API;
  10. docsдля каталога документов;
  11. README.mdДокументация по проекту;

Следующая задача – обогатитьsrcКаталог, включая построение архитектуры проекта, разработку функций приложения, а также автоматизацию, модульное тестирование и т. д. В этой статье основное внимание уделяется построению архитектуры проекта, а затем используется практика стека технологий для разработки нескольких модулей.

стек технологий

Построение архитектуры проекта во многом зависит от стека технологий проекта, поэтому сначала проанализируйте весь стек технологий и подведите итоги:

  1. Библиотеки react и react-dom необходимы для проекта;
  2. маршрут реакции;
  3. Контейнер управления состоянием приложения;
  4. Требуются ли неизменяемые данные;
  5. Постоянство состояния приложения;
  6. Асинхронное управление задачами;
  7. тесты и вспомогательные инструменты или функции;
  8. Разработать инструменты отладки;

В соответствии с приведенным выше разделением для формирования полного стека технологий проекта выбираются следующие сторонние библиотеки и инструменты:

  1. реагировать, реагировать-дом;
  2. react-router управляет маршрутизацией приложений;
  3. Redux действует как контейнер состояний JavaScript, а react-redux соединяет приложения React с redux;
  4. Immutable.js поддерживает неизменяемое состояние, redux-immutable делает неизменяемым все дерево состояний хранилища redux;
  5. Используйте redux-persist для поддержки постоянства дерева состояний redux и добавьте расширение redux-persist-immutable для поддержки постоянства дерева состояний Immutable;
  6. Используйте redux-saga для управления асинхронными задачами в приложении, такими как сетевые запросы, асинхронное чтение локальных данных и т. д.;
  7. Используйте jest для интеграции тестирования приложений, используйте необязательные вспомогательные классы, такие как lodash и ramda, и библиотеки классов инструментов;
  8. При желании используйте инструмент отладки Reactotron

С учетом приведенного выше анализа улучшенная структура проекта показана на рисунке:

React-Redux项目结构

Инструменты разработки и отладки

Разработка приложений React в настоящее время имеет множество инструментов отладки, таких как redux-devtools,ReactronЖдать.

redux-devtools

redux-devtools — это инструмент разработки Redux, который поддерживает горячую перезагрузку, действие воспроизведения и настраиваемый пользовательский интерфейс.

Во-первых, вам нужно следовать соответствующему подключаемому модулю браузера, а затем добавить соответствующую конфигурацию в приложение Redux, вы можете просмотреть панель инструментов redux в консоли браузера,Щелкните здесь для получения подробной документации.

Затем установите библиотеку зависимостей проекта:

yarn add --dev redux-devtools

Затем передайте его в качестве редуктора при создании редукционного хранилища.createStoreметод:

import { applyMiddleware, compose, createStore, combineReducers } from 'redux'
// 默认为redux提供的组合函数
let composeEnhancers = compose

if (__DEV__) {
  // 开发环境,开启redux-devtools
  const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  if (typeof composeWithDevToolsExtension === 'function') {
    // 支持redux开发工具拓展的组合函数
    composeEnhancers = composeWithDevToolsExtension
  }
}

// create store
const store = createStore(
  combineReducers(...),
  initialState,
  // 组合redux中间价和加强器,强化redux
  composeEnhancers(
    applyMiddleware(...middleware),
    ...enhancers
  )
)
  1. Получите расширенную комбинированную функцию, предоставляемую redux-devtools в среде разработки;
  2. При создании хранилища используйте расширенную комбинированную функцию для объединения промежуточного программного обеспечения и энхансеров redux, а инструменты redux-dev-tools могут получить соответствующую информацию о применении redux;

Reactotron

ReactotronЭто настольное приложение для кросс-платформенной отладки приложений React и React Native. Оно может динамически отслеживать и выводить информацию, такую ​​​​как избыточность, действие и асинхронные запросы саги для приложений React, в режиме реального времени, как показано на рисунке:

Reactotron

Первая установка:

yarn add --dev reactotron-react-js

Затем инициализируйте конфигурацию, связанную с Reactotron:

import Reactotron from 'reactotron-react-js';
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: 'React Blog' })
    .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-js
  // 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 заключается в том, что компонент отвечает за отрисовку пользовательского интерфейса, а разные состояния компонента соответствуют разным пользовательским интерфейсам.Обычно следуют следующим идеям дизайна компонента:

  1. Компоненты макета: компоненты, которые включают только структуру интерфейса пользовательского интерфейса приложения, не включают какую-либо бизнес-логику, запросы данных и операции;
  2. Компонент-контейнер: отвечает за получение данных, обработку бизнес-логики и обычно возвращает презентационные компоненты в функции render();
  3. Компонент отображения: отвечает за отображение пользовательского интерфейса приложения;
  4. Компоненты пользовательского интерфейса: относятся к абстрактным многократно используемым независимым компонентам пользовательского интерфейса, обычно компонентам без состояния;
Компоненты дисплея компонент контейнера
Цель Представление пользовательского интерфейса (структура и стиль HTML) Бизнес-логика (получение данных, обновление статуса)
Осведомлен о Редукс никто имеют
Источники данных props Подпишитесь на магазин Redux
изменить данные Вызовите функцию обратного вызова, переданную реквизитами Dispatch Redux actions
многоразовый Сильная независимость Высокая степень связанности бизнеса

Redux

Если какое-либо крупномасштабное веб-приложение сегодня не имеет контейнера управления состоянием, то приложению не хватает характеристик того времени.Дополнительные библиотеки, такие как mobx, redux и т. д., на самом деле похожи, и каждая берет то, что им нужно. Например, redux — это наиболее часто используемая библиотека контейнеров состояний приложений React, которая также подходит для приложений React Native.

Redux – это контейнер управления предсказуемым состоянием для приложений JavaScript. Он не зависит от конкретных фреймворков или библиотек классов, поэтому имеет последовательный метод разработки и эффективность при разработке мультиплатформенных приложений. Кроме того, он может помочь нам легко реализовать путешествие во времени, то есть воспроизведение действия.

redux-flow

  1. Принцип единого источника данных: используйте Redux в качестве контейнера управления состоянием приложения для унифицированного управления деревом состояний приложения. также обрабатывается редуксом;
  2. Дерево состояния хранилища Redux: Redux централизованно управляет состоянием приложения.Форма организации и управления похожа на дерево DOM и дерево компонентов React.Он организован в виде дерева, что является простым и эффективным;
  3. redux и store: redux — это реализация Flux, поэтому было создано хранилище терминов, которое похоже на store, централизованно управляет состоянием приложения и поддерживает распространение каждого опубликованного действия на все редюсеры;
  4. действие: существует в формате данных объекта, обычно с как минимум атрибутами типа и полезной нагрузки, это описание задачи, определенной в редукции;
  5. редьюсер: обычно существует в виде функции, получает два параметра, состояние (локальное состояние приложения) и объект действия, выполняет разные задачи в соответствии с action.type (типом действия) и следует идее функционального программирования;
  6. диспетчеризация: функциональный метод диспетчеризации действия, предоставляемый хранилищем, с передачей параметра объекта действия;
  7. createStore: метод для создания хранилища, получения редуктора, начального состояния приложения, промежуточного программного обеспечения и энхансера, инициализации хранилища, начала прослушивания действий;

Промежуточное ПО (промежуточное ПО Redux)

Промежуточное ПО Redux, как и промежуточное ПО Node, может выполнять некоторую дополнительную работу, прежде чем действие будет отправлено в редюсер обработки задачи.Экшен, опубликованный отправкой, будет передан всем промежуточным программам по очереди и, наконец, достигнет редюсера, поэтому мы можем использовать промежуточное ПО для расширить такие вещи, как ведение журналов, добавление мониторинга, переключение маршрутов и другие функции, поэтому промежуточное программное обеспечение, по сути, просто расширяется.store.dispatchметод.

redux-middleware-enhancer

Enhancer (улучшитель магазина)

Иногда мы не можем быть удовлетворены расширениемdispatchметода, а также надеются улучшить магазин, redux предоставляет улучшения для всех аспектов магазина в виде энхансеров и даже полностью настраивает все интерфейсы на объекте магазина, а не толькоstore.dispatchметод.

const logEnhancer = (createStore) => (reducer, preloadedState, enhancer) => {
  const store = createStore(reducer, preloadedState, enhancer)
  const originalDispatch = store.dispatch
  store.dispatch = (action) => {
    console.log(action)
    originalDispatch(action)
  }
  
  return store
}

Простейший пример кода приведен выше: новая функция получает метод redux createStore и параметры, необходимые для создания хранилища, а затем сохраняет ссылку на метод в объекте хранилища внутри функции, повторно реализует метод и вызывает исходный метод после обработки в нем логики расширения.Исходная функция гарантированно будет выполняться должным образом, тем самым улучшая метод отправки хранилища.

Видно, что энхансер может полностью реализовать функции мидлвара, фактически мидлвар реализован в виде энхансера, который обеспечиваетcomposeЭтот метод можно комбинировать, чтобы расширить передаваемый нами энхансер в хранилище, и если мы передаем промежуточное программное обеспечение, нам нужно сначала вызватьapplyMiddlewareУпаковка методов, которая внутренне расширяет функции промежуточного программного обеспечения в виде энхансеров дляstore.dispatchметод

react-redux

Redux — это независимая библиотека-контейнер для управления состоянием приложений JavaScript.Его можно использовать с приложениями React, Angular, Ember, jQuery и даже с собственными приложениями JavaScript.Поэтому при разработке приложений React вам необходимо подключить Redux и приложения React для управления приложениями с помощью Redux. статус, используйте официальныйreact-reduxбиблиотека.

class App extends Component {
  render () {
    const { store } = this.props
    return (
      <Provider store={store}>
        <div>
          <Routes />
        </div>
      </Provider>
    )
  }
}

библиотека react-redux предоставляетProviderКомпонент внедряет хранилище в приложение через контекст, а затем может использоватьconnectВысокоуровневый метод получает и отслеживает хранилище, затем вычисляет новые реквизиты на основе состояния хранилища и собственных реквизитов компонента, внедряет их в компонент и сравнивает рассчитанные новые реквизиты, наблюдая за хранилищем, чтобы определить, нужно ли компоненту быть обновленным. обновлено.

Чтобы узнать больше о react-redux, вы можете прочитать предыдущие статьи:React-редукционный анализ.

createStore

используя предоставленный редуксcreateStoreметод для создания хранилища избыточности, но в реальных проектах нам часто нужно расширить избыточность, чтобы добавить некоторые пользовательские функции или службы, такие как добавление промежуточного программного обеспечения избыточности, добавление саги об асинхронном управлении задачами, улучшение избыточности и т. д .:

// creates the store
export default (rootReducer, rootSaga, initialState) => {
  /* ------------- Redux Configuration ------------- */
  // Middlewares
  // Build the middleware for intercepting and dispatching navigation actions
  const blogRouteMiddleware = routerMiddleware(history)
  const sagaMiddleware = createSagaMiddleware()
  const middleware = [blogRouteMiddleware, sagaMiddleware]

  // enhancers
  const enhancers = []
  let composeEnhancers = compose

  // create store
  const store = createStore(
    combineReducers({
      router: routerReducer,
      ...reducers
    }),
    initialState,
    composeEnhancers(
      applyMiddleware(...middleware),
      ...enhancers
    )
  )
  sagaMiddleware.run(saga)

  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({
    ...RouterReducer,
    ...AppReducer
  });

  return configureStore(rootReducer, rootSaga, initialState);
}

Как и в приведенном выше коде, вы можете видеть, что мы передалиinitialStateЯвляетсяImmutable.MapТип данных, мы делаем корень всего дерева состояний редукса Immutable, а также передаем редьюсеры и саги, которые могут обрабатывать состояние Immutable.

Кроме того, данные каждого узла дерева состояний представляют собой неизменяемую структуру, напримерAppReducer:

const initialState = Immutable.fromJS({
  ids: [],
  posts: {
    list: [],
    total: 0,
    totalPages: 0
  }
})

const AppReducer = (state = initialState, action) => {
  case 'RECEIVE_POST_LIST':
    const newState = state.merge(action.payload)
    return newState || state
  default:
    return state
}

По умолчанию метод Immutable.fromJS() используется для преобразования объекта узла дерева состояний в структуру Immutable, а метод Immutable используется при обновлении состояния.state.merge(), чтобы гарантировать, что состояние является однородным и предсказуемым.

Реагировать на маршрутизацию

В одностраничных веб-приложениях React отображение и переключение компонентов пользовательского интерфейса на уровне страницы полностью контролируется маршрутизацией, и каждый маршрут имеет соответствующий URL-адрес и информацию о маршрутизации.Мы можем единообразно и эффективно управлять переключением наших компонентов с помощью маршрутизации, сохраняя пользовательский интерфейс и URL. Синхронизация обеспечивает стабильность приложения и удобство использования.

react-router

React Router — это комплексное решение маршрутизации React и наиболее часто используемая библиотека управления маршрутизацией для разработки приложений React. Пока вы ее используете, вам обязательно понравится ее дизайн. Она предоставляет простой API и реализует мощные функции маршрутизации в декларативной манере. , такие как загрузка по запросу, динамическая маршрутизация и т. д.

  1. Декларативный: синтаксис лаконичный и понятный;
  2. Загрузка по запросу: ленивая загрузка, решите, нужно ли ее загружать в соответствии с потребностями использования;
  3. Динамическая маршрутизация: динамическая структура маршрутизации комбинированных приложений, более гибкая, более соответствующая режиму разработки компонентов;

Динамическая маршрутизация и статическая маршрутизация

С помощью версии react-router v4 можно определить структуру динамической маршрутизации кросс-платформенного приложения.Так называемая динамическая маршрутизация (Dynamic Routing) означает, что переключение маршрутизации происходит в процессе рендеринга, и его не нужно настраивать перед созданием В отличие от статической маршрутизации (статической маршрутизации), динамическая маршрутизация улучшает более гибкую организацию маршрутизации, а код для реализации маршрутизации для загрузки компонентов по требованию более удобен.

В react-router v2 и v3 разработка приложения React требует определения полной структуры маршрутизации приложения перед началом рендеринга.Все маршруты должны быть инициализированы одновременно, чтобы они вступили в силу после рендеринга приложения, что приводит к множеству вложенных маршрутов. гибкость динамической маршрутизации и простота загрузки кода по требованию.

react-router v4.x

В react-router 2.x и 3.x определение структуры маршрутизации приложения обычно выглядит так:

import React from 'react'
import ReactDOM from 'react-dom'
import { browserHistory, Router, Route, IndexRoute } from 'react-router'

import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features'

ReactDOM.render(
  <Router history={browserHistory}>
    <Route path='/' component={App}>
      <IndexRoute component={Home} />
      <Route path='about' component={About} />
      <Route path='features' component={Features} />
    </Route>
  </Router>,
  document.getElementById('app')
)

Это очень просто, но все структуры маршрутизации должны быть единообразно определены и вложены слой за слоем перед рендерингом приложения; и если вы хотите добиться асинхронной загрузки по требованию, вам нужно изменить здесь объект конфигурации маршрутизации, используяgetComponentAPI, а также вторгаться и преобразовывать компонент, а также сотрудничать с API асинхронной упаковки и загрузки веб-пакета для достижения загрузки по требованию:

  1. Маршруты вложены слой за слоем и должны быть объявлены единообразно перед рендерингом приложения;
  2. API отличается и должен использоватьсяgetComponent, увеличение сложности объекта конфигурации маршрутизации;
  3. <Route>Это просто вспомогательная метка для объявления маршрута, которая сама по себе бессмысленна;

А использование react-router v4.x выглядит следующим образом:

// react-dom (what we'll use here)
import { BrowserRouter } from 'react-router-dom'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), el)

const App = () => (
  <div>
    <nav>
      <Link to="/about">Dashboard</Link>
    </nav>
    <Home />
    <div>
      <Route path="/about" component={About}/>
      <Route path="/features" component={Features}/>
    </div>
  </div>
)

По сравнению с предыдущей версией следы конфигурации уменьшены, компонентная организация более заметна, и эта часть маршрутизации реализуется только при рендеринге компонента.Если вы хотите загружать компонент по требованию, вы можете реализовать пакет который поддерживает асинхронную загрузку. Компонент более высокого порядка компонента, компонент, возвращаемый после обработки компонентом более высокого порядка, передается в<Route>То есть по-прежнему следовать компонентной форме:

  1. Гибкость: маршруты могут быть объявлены в компоненте рендеринга, не зависят от других маршрутов и не требуют централизованной настройки;
  2. Кратко: единый входящийcomponent, чтобы обеспечить простоту объявлений маршрутизации;
  3. Компонентизация:<Route>Создайте маршрут как реальный компонент, который можно визуализировать;

метод перехвата маршрута

Также обратите внимание, что по сравнению с предыдущими версиямиonEnter, onUpdate, onLeaveAPI метода ловушки в определенной степени улучшает управляемость маршрутизации, но суть заключается только в том, чтобы покрыть метод жизненного цикла компонента рендеринга.Теперь мы можем напрямую управлять маршрутизацией через метод жизненного цикла компонента рендеринга маршрутизации, например с использованиемcomponentDidMountилиcomponentWillMountзаменятьonEnter.

Маршрутизация и Redux

При одновременном использовании React-Router и Redux в большинстве случаев это нормально, но также могут быть случаи, когда компонент изменения маршрута не обновляется, например:

  1. мы используем редуксconnectспособ подключения компонента к редуксу:connect(Home);
  2. Компонент не является компонентом рендеринга маршрута, т.е. не используетRoute>Форма компонента:<Route component={Home} />объявлено оказанным;

Почему это? , потому что Redux реализуетshouldComponentUpdateметод, когда маршрут изменяется, компонент не получает реквизиты, чтобы указать, что изменение произошло и компонент необходимо обновить.

Итак, как решить проблему? , чтобы решить эту проблему, просто используйтеreact-router-domкоторый предоставилwithRouterМетод оборачивает компонент:

import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps)(Home))

Интеграция с Redux

После использования Redux нужно следовать принципу редукса: единый доверенный источник данных, то есть все источники данных могут быть только хранилищем reudx, и состояние роутинга реакции не должно быть исключением, поэтому нужно подключить состояние роутинга с состоянием магазина.

react-router-redux

Чтобы подключить React Router с Redux, вам нужно использоватьreact-router-reduxбиблиотека, и необходимо установить версию react-router v4@nextверсия иhsitoryБиблиотеки:

yarn add react-router-redux@next
yarn add history

Затем при создании магазина необходимо реализовать следующую конфигурацию:

  1. Создайте объект истории.Для веб-приложений мы выбираем browserHisotry, что соответствуетhistory/createBrowserHistoryимпорт модуляcreateHistoryметод создания объекта истории;

    Нажмите здесь, чтобы просмотреть больше материалов, связанных с историей

  2. Добавить кrouterReducerиrouterMiddlewareпромежуточное ПО», котороеrouterMiddlewareПромежуточное ПО получает параметр объекта истории и связывает хранилище и историю, что эквивалентно старой версии.syncHistoryWithStore;

import createHistory from 'history/createBrowserHistory'
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
// Create a history of your choosing (we're using a browser history in this case)
export const history = createHistory()

// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history)

// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
)

return store

При рендеринге корневого компонента мы абстрагируем два компонента:

  1. Инициализируйте и визуализируйте корневой компонент, подключите его к корневому компоненту DOM,<Provider>Пакет компонентов, введенный в хранилище;
  2. Компонент конфигурации маршрутизации в корневом компоненте объявляет компонент конфигурации маршрутизации и инициализирует необходимые определения маршрутизации приложений и объекты маршрутизации;
import createStore from './store/'
import Routes from './routes/'
import appReducer from './store/appRedux'

const store = createStore({}, {
  app: appReducer
})

/**
 * 项目根组件
 * @class App
 * @extends Component
 */
class App extends Component {
  render () {
    const { store } = this.props

    return (
      <Provider store={store}>
        <div>
          <Routes />
        </div>
      </Provider>
    )
  }
}

// 渲染根组件
ReactDOM.render(
  <App store={store} />,
  document.getElementById('app')
)

выше<Routes>Компонент является компонентом маршрутизации проекта:

import { history } from '../store/'
import { ConnectedRouter } from 'react-router-redux'
import { Route } from 'react-router'

class Routes extends Component {
  render () {
    return (
      <ConnectedRouter history={history}>
        <div>
          <BlogHeader />
          <div>
            <Route exact path='/' component={Home} />
            <Route exact path='/posts/:id' component={Article} />
          </div>
        </div>
      </ConnectedRouter>
    )
  }
}

первое использованиеreact-router-reduxкоторый предоставилConnectedRouterКомпонент оборачивает конфигурацию маршрутизации, которая будет использоваться автоматически.<Provider>впрыскиваемый компонентstore, что нам нужно сделать, это вручную передатьhistoryсвойство, которое будет вызываться внутри компонентаhistory.listenспособ прослушивания браузераLOCATION_CHANGEсобытие, наконец, возвращениеreact-routerиз<Router >компоненты, обрабатываемые какthis.props.childrenконфигурация входящей маршрутизации,Доставка контента компонента ConnectedRouter.

маршрут диспетчерского переключения

После настройки приведенного выше кода вы можете инициировать переключение маршрута и обновление компонента в виде действия отправки:

import { push } from 'react-router-redux'
// Now you can dispatch navigation actions from anywhere!
store.dispatch(push('/about'))

Все, что делает этот редьюсер, — это объединяет состояние маршрутизации навигации приложения в хранилище.

избыточное постоянство

Мы знаем, что браузер имеет функцию кэширования ресурсов по умолчанию и предоставляет методы локального постоянного хранения, такие как localStorage, indexDb, webSQL и т. д., обычно некоторые данные могут храниться локально, и в определенный период, когда пользователь снова обращается, они прямое восстановление из локальных данных может значительно повысить скорость запуска приложения, а пользовательский интерфейс более выгоден Мы можем использовать localStorage для хранения некоторых данных, а если это большой объем хранилища данных, мы можем использовать webSQL.

Кроме того, в отличие от предыдущего прямого хранения данных, при запуске приложения данные считываются локально, а затем восстанавливаются.Для redux-приложений, если хранятся только данные, то мы должны расширяться для каждого редюсера, а читать и сохранять, когда приложение запускается заново.Это громоздкий и неэффективный способ.Можете ли вы попробовать сохранить ключ редьюсера, а затем восстановить соответствующие постоянные данные по ключу?Сначала зарегистрируйте редуктор регидрата, при срабатывании действия восстановите данные в соответствии с его ключом редуктора, а затем нужно только распространять действия при запуске приложения, что также легко абстрагируется в настраиваемую службу расширения.redux-persistВсе это сделано для нас.

redux-persist

Чтобы реализовать персистентность избыточности, включая два процесса локального постоянного хранилища хранилища избыточности и восстановления и запуска, если вы пишете свою собственную реализацию, объем кода усложняется, вы можете использовать библиотеки с открытым исходным кодом.redux-persist, который обеспечиваетpersistStoreиautoRehydrateМетоды соответственно сохраняют локальное хранилище и восстанавливают стартовое хранилище, а также поддерживают трансформацию и расширение состояния хранилища при настройке входящего сохраняемости и восстановлении хранилища.

yarn add redux-persist

постоянное хранилище

Службы, связанные с persistStore, вызываются при создании хранилища следующим образом:RehydrationServices.updateReducers():

// configure persistStore and check reducer version number
if (ReduxPersistConfig.active) {
  RehydrationServices.updateReducers(store);
}

Постоянное хранилище хранилища реализовано в этом методе:

// Check to ensure latest reducer version
storage.getItem('reducerVersion').then((localVersion) => {
  if (localVersion !== reducerVersion) {
    // 清空 store
    persistStore(store, null, startApp).purge();
    storage.setItem('reducerVersion', reducerVersion);
  } else {
    persistStore(store, null, startApp);
  }
}).catch(() => {
  persistStore(store, null, startApp);
  storage.setItem('reducerVersion', reducerVersion);
})

Номер версии редуктора будет храниться в localStorage, который можно настроить в файле конфигурации приложения. Номер версии и хранилище сохраняются при первом выполнении персистентности. Если номер версии редуктора изменится, исходное сохраненное хранилище будет очищено. , В противном случае хранилище будет передано для сохранения.persistStoreВот и все.

persistStore(store, [config], [callback])

Этот метод в основном реализует персистентность хранилища и распределяет действие регидратации:

  1. Подпишитесь на хранилище избыточности и запускайте операции хранения хранилища при его изменении;
  2. Получите данные из указанного StorageEngine (например, localStorage), преобразуйте их, а затем инициируйте процесс REHYDRATE, отправив действие REHYDRATE;

Параметры приема в основном следующие:

  1. магазин: постоянный магазин;
  2. config: объект конфигурации
    1. хранилище: механизм сохранения, такой как LocalStorage и AsyncStorage;
    2. transforms: Преобразователи, вызываемые на этапах регидратации и хранения;
    3. черный список: массив черных списков, указывающий ключи редукторов, которые игнорируются постоянством;
  3. обратный вызов: обратный вызов после завершения операции обезвоживания;

возобновить загрузку

Как и 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, и в этой статье в качестве примера представлена ​​версия 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 в проект нужно максимально обеспечить следующие моменты:

  1. Unified Immutable всего дерева состояний хранилища избыточности;
  2. Постоянство Redux совместимо с неизменяемыми данными;
  3. Маршрутизация React совместима с Immutable;

Для практических тестов Immutable, Redux, Reselect и т. д. ознакомьтесь со статьей, написанной ранее:Практика Immutable.js с React, Redux и повторным выбором.

Неизменная и React-маршрутизация

Первые два пункта были объяснены в предыдущих двух разделах. Третий пункт заключается в том, что react-router совместим с Immutable. На самом деле, он должен сделать состояние маршрутизации приложения совместимым с Immutable. В разделе маршрутизации React это было представил, как подключить состояние маршрутизации React к хранилищу Redux, но если приложение использует Если установлена ​​библиотека Immutable, требуется дополнительная обработка для преобразования состояния реактивного маршрутизатора в формат Immutable. routeReducer не может обрабатывать Immutable. Нам нужно настроить новый RouterReducer:

import Immutable from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';

const initialState = Immutable.fromJS({
  location: null
});

export default (state = initialState, action) => {
  if (action.type === LOCATION_CHANGE) {
    return state.set('location', action.payload);
  }
  
  return state;
};

Преобразуйте начальное состояние маршрута по умолчанию в Immutable и используйте Immutable API для управления состоянием при изменении маршрута.

seamless-Immutable

После введения Immutable.js API для использования структуры данных состояния приложения должен следовать Immutable API и больше не может использовать API операций собственных объектов JavaScript, массивов и т. д., таких как деструктуризация массива ([a, b ] = [b, c]), расширитель объектов (...) и т. д., есть некоторые проблемы:

  1. Неизменяемые данные имеют больше вспомогательных узлов и большие данные:
  2. Должен использоваться неизменяемый синтаксис, который отличается от синтаксиса JavaScript и не может быть полностью совместимым;
  3. При написании и взаимодействии с библиотеками JavaScript, такими как Redux и react-router, необходимо вводить дополнительные совместимые библиотеки обработки;

В ответ на эти вопросы сообществоseamless-immutableАльтернативные варианты:

  1. Легче: по сравнению с Immutable.jsseamless-immutableБиблиотека легче и меньше;
  2. Синтаксис: синтаксис операций с объектами и массивами ближе к родному JavaScript;
  3. Удобнее сотрудничать с другими библиотеками JavaScript;

Асинхронное управление потоком задач

Последний модуль, который будет представлен, - это управление асинхронными задачами.В процессе разработки приложений наиболее важной асинхронной задачей является HTTP-запрос данных, поэтому мы говорим об асинхронном управлении задачами, в основном сосредоточив внимание на управлении процессом HTTP-запроса данных.

axios

используется в этом проектеaxiosВ качестве библиотеки HTTP-запросов axios представляет собой HTTP-клиент в формате Promise.Причины выбора этой библиотеки следующие:

  1. XMLHttpRequest может быть инициирован в браузере, а HTTP-запрос также может быть инициирован на стороне node.js;
  2. обещания поддержки;
  3. Возможность перехвата запросов и ответов;
  4. Может отменить запрос;
  5. Автоматически преобразовывать данные 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 { HomeSaga } from './Home/flux.js'
import { AppSaga } from './Appflux.js'

const sagas = [
  ...AppSaga,
  ...HomeSaga
]

export default function * root() {
  yield sagas.map(saga => fork(saga))
}

Как и выше, сначала соберите всю корневую сагу модуля, затем пройдитесь по массиву и запустите каждую корневую сагу потока саги.

экземпляр саги

Взяв в качестве примера AppSaga, мы ожидаем инициировать некоторые асинхронные запросы при запуске приложения, такие как получение данных списка статей и заполнение их в хранилище избыточности, вместо того, чтобы ждать, пока компоненты, использующие данные, будут обработаны перед началом запроса данных. для улучшения скорости отклика:

const REQUEST_POST_LIST = 'REQUEST_POST_LIST'
const RECEIVE_POST_LIST = 'RECEIVE_POST_LIST'

/**
 * 请求文章列表ActionCreator
 * @param {object} payload
 */
function requestPostList (payload) {
  return {
    type: REQUEST_POST_LIST,
    payload: payload
  }
}

/**
 * 接收文章列表ActionCreator
 * @param {*} payload
 */
function receivePostList (payload) {
  return {
    type: RECEIVE_POST_LIST,
    payload: payload
  }
}

/**
 * 处理请求文章列表Saga
 * @param {*} payload 请求参数负载
 */
function * getPostListSaga ({ payload }) {
  const data = yield call(getPostList)
  yield put(receivePostList(data))
}

// 定义AppSaga
export function * AppSaga (action) {
  // 接收最近一次请求,然后调用getPostListSaga子Saga
  yield takeLatest(REQUEST_POST_LIST, getPostListSaga)
}
  1. takeLatest:существуетAppSagaвнутреннее использованиеtakeLatestслушатель методаREQUEST_POST_LISTдействие, если за короткий промежуток времени инициировано несколько действий, предыдущее неотвечающее действие будет отменено, и будет инициировано только последнее действие;
  2. getPostListSagaДетская сага: Когда действие получено, позвонитеgetPostListSaga, и передать ему полезную нагрузку,getPostListSagaЭто дочерняя сага AppSaga, которая обрабатывает определенные асинхронные задачи;
  3. getPostList:getPostListSagaпозвонюgetPostListметод, инициируйте асинхронный запрос, после получения данных ответа вызовитеreceivePostListActionCreator создает и распределяет действия, а затем редюсер обрабатывает соответствующую логику;

getPostListСодержание метода следующее:

/**
 * 请求文章列表方法
 * @param {*} payload 请求参数
 *  eg: {
 *    page: Num,
 *    per_page: Num
 *  }
 */
function getPostList (payload) {
  return fetch({
    ...API.getPostList,
    data: payload
  }).then(res => {
    if (res) {
      let data = formatPostListData(res.data)
      return {
        total: parseInt(res.headers['X-WP-Total'.toLowerCase()], 10),
        totalPages: parseInt(res.headers['X-WP-TotalPages'.toLowerCase()], 10),
        ...data
      }
    }
  })
}

putЭто распространяемый метод действия, предоставляемый redux-saga, берите, звоните и т. Д.redux-sagaПредоставляется API, большеПосмотреть документацию по API.

После этого вы можете внедрить ActionCreator в корневой компонент маршрутизации проекта, создать действие, и тогда saga получит его на обработку.

сага и Реактотрон

Ранее было настроено, что вы можете использовать Reactotron для захвата всех редуксов и действий приложения, а редукс-сага — это тип промежуточного программного обеспечения редукса, поэтому захват саг требует дополнительной настройки.При создании хранилища добавьте сервис sagaMonitor в сагу промежуточное ПО для мониторинга саги:

const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middleware.push(sagaMiddleware);
...

Суммировать

В этой статье подробно описан процесс построения архитектуры проекта от 0 до 1, а также более глубокое понимание и размышления о приложениях React, Redux и методах разработки проектов, а также продолжение продвижения по пути большого роста внешнего интерфейса.

Примечание: для всех стеков технологий, перечисленных в статье, блогер планирует продвигаться шаг за шагом.Технологии, используемые в настоящее время в исходном коде, включают React, React Router, Redux, react-redux, react-router-redux, Redux-saga. , и аксиомы. Позже в планах продвигать Immutable, Reactotron, Redux Persist.

Полный код проекта смотрите на github.

Ссылаться на

  1. React
  2. Redux
  3. React Router v4
  4. redux-saga
  5. Redux Persist