Предыдущая статья——Создание приложений React с нуля (1) — базовая конструкцияОписывает, как использовать webpack для создания очень простой среды разработки React. В этой статье будет подробно описана архитектура построения приложения React.
Адрес склада:GitHub.com/г-н Чжан123/…
redux
В нашем процессе разработки много раз нам нужно позволить компонентам совместно использовать некоторые данные.Хотя совместное использование данных может быть достигнуто с помощью компонентов, если между компонентами нет отношений родитель-потомок, передача данных очень проблематична, и легко сделать код Читабельность снижена, в это время нам нужен инструмент управления состоянием (state). Общие инструменты управления состоянием включают redux и mobx, здесь Redux выбран для управления состоянием. Стоит отметить, что React 16.3 привносит совершенно новыйContext API, мы также можем использовать новый Context API для управления состоянием.
Redux — это контейнер состояния JavaScript, обеспечивающий предсказуемое управление состоянием. Позволяет создавать согласованные приложения, которые работают в разных средах (клиент, сервер, собственные приложения) и легко тестируются. Мало того, он также обеспечивает очень хороший опыт разработки, например, отладчик путешествий во времени, который может предварительно просмотреть в реальном времени после редактирования.
Поток данных redux показан на следующем рисунке:
Три принципа REDUX:
- Все приложение
state
Все они хранятся в дереве объектов, и это дерево объектов существует только в одном хранилище, но это не означает, что использование избыточности требует, чтобы все состояние сохранялось в избыточности. -
состояние только для чтения, единственный способ изменить состояние — запустить
action
,action
это обычный объект, используемый для описания произошедшего события. - Используйте чистые функции для выполнения модификаций, чтобы описать, как действия изменяют дерево состояний, необходимо написать редюсеры.
Промежуточное ПО (промежуточное ПО Redux)
Redux middleware Предоставляет точку расширения после запуска действия и до достижения редуктора. Действия, инициированные диспетчером, по очереди проходят через промежуточное ПО и, наконец, достигают редуктора. Мы можем использовать промежуточное ПО Redux для ведения журнала, создания отчетов о сбоях, вызова асинхронных интерфейсов или маршрутизации и т. д.По сути, промежуточное ПО просто расширяетstore.dispatch
метод.
Улучшитель магазина
Усилитель магазина используется для улучшения функциональности магазина. Усилитель магазина на самом деле представляет собой функцию более высокого порядка, которая возвращает новый расширенный создатель магазина.
const logEnhancer = createStore => (reducer, initialState, enhancer) => {
const store = createStore(reducer, initialState, enhancer)
function dispatch(action) {
console.log(`dispatch an action: ${JSON.stringify(action)}`)
const res = store.dispatch(action)
const newState = store.getState()
console.log(`current state: ${JSON.stringify(newState)}`)
return res
}
return { ...store, dispatch }
}
можно увидетьlogEnhancer
Изменено поведение магазина по умолчанию, каждый разdispatch
До и после будет выводиться журнал.
react-redux
Redux сам по себе является государственной библиотекой state JS, которую можно использовать в сочетании с приложениями react, vue, angular и даже с родными JS.Чтобы redux помогал нам управлять состоянием реагирующих приложений, нам нужно связать redux с реакцией. Официально предоставленоreact-reduxбиблиотека.
реакция-редукс обеспечиваетProvider
Компонент внедряет хранилище в приложение через контекст, а затем компонент используетconnect
Метод более высокого порядка получает и отслеживает хранилище, затем вычисляет новые реквизиты на основе состояния хранилища и собственных реквизитов компонента, вводит их в компонент и сравнивает рассчитанные новые реквизиты, наблюдая за хранилищем, чтобы определить, нужно ли компоненту быть обновлено.
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('app')
)
Интегрировать избыточность в реагирующее приложение
Объединить редукторы
В реагирующем приложении есть только одно хранилище, компоненты передают данные редьюсеру, вызывая функцию действия, редуктор изменяет соответствующее состояние в соответствии с данными. Однако по мере увеличения сложности приложения редюсер будет становиться все больше и больше.В настоящее время вы можете рассмотреть возможность разделения редьюсера на несколько отдельных функций, каждая из которых отвечает за независимое управление частью состояния.
редукс обеспечиваетcombineReducers
Вспомогательная функция объединяет рассредоточенный редюсер в конечную функцию редуктора, а затем использует ее при вызове CreateStore.
Интеграция промежуточного ПО
Иногда нам нужно объединить несколько промежуточных программ вместе, чтобы сформировать цепочку промежуточных программ для улучшенияstore.dispatch
, при создании магазина нам необходимо интегрировать в магазин цепочку промежуточного программного обеспечения, которая официально предоставляетсяapplyMiddleware(...middleware)
Связывайте промежуточное ПО вместе.
Интеграция усилителей магазина
Усилитель магазина используется для улучшения магазина. Если нам нужно интегрировать несколько усилителей магазина, когда у нас есть несколько усилителей магазина, мы будем использовать их в это время.compose(...functions)
.
использоватьcompose
При объединении нескольких функций каждая функция принимает один параметр, ее возвращаемое значение будет предоставлено в качестве параметра функции слева и т. д. Самая правая функция может принимать несколько параметров.compose(funA,funB,funC)
можно понимать какcompose(funA(funB(funC())))
, который, наконец, возвращает итоговую функцию после объединения функций, полученных справа налево.
Создать магазин
редукс черезcreateStore
Создайте магазин REDUX для хранения всех приложенийstate
,createStore
Параметры формы следующие:
createStore(reducer, [preloadedState], enhancer)
Итак, наш код для создания магазина выглядит следующим образом:
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import reducers from '../reducers'
const initialState = {}
const store = createStore(reducers, initialState, applyMiddleware(thunk))
export default store
Магазин, который будет создан позже, передаетсяProvider
Интегрируйте Redux с реагирующими приложениями, внедряя компоненты в реагирующие приложения.
Примечание: в приложении должен быть и только один магазин.
React Router
React Router — это комплексное решение для маршрутизации для React, которое поддерживает синхронизацию пользовательского интерфейса с URL-адресом. В проект мы интегрируем последнюю версию React Router v4.
В react-router v4 react-router разделен на три пакета: react-router, react-router-dom и react-router-native, отличия заключаются в следующем:
- react-router: Предоставляет основные компоненты и функции маршрутизации.
- react-router-dom: реагирующий маршрутизатор для браузеров
- react-router-native: реагирующий маршрутизатор для реагирующего нативного
редукс и реактивный маршрутизатор
React Router в основном отлично работает при использовании с Redux, но иногда есть обновления маршрутов, но нет обновлений дочерних маршрутов или активных навигационных ссылок. Это происходит, когда:
- Компонент
connect()(Comp)
Подключиться к редуксу. - Компонент не является «компонентом маршрутизации», т.е. компонент не похож на
<Route component={SomeConnectedThing} />
Рендерить вот так.
Причина этой проблемы в том, что Redux реализуетshouldComponentUpdate
, компонент не получает обновления реквизита при изменении маршрута.
Решение этой проблемы простое, найтиconnect
и использовать егоwithRouter
пакет:
// before
export default connect(mapStateToProps)(Something)
// after
import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps)(Something))
Глубокая интеграция редукции с реактивным маршрутизатором
Иногда мы можем захотеть более глубоко интегрировать Redux с React Router для достижения:
- Синхронизируйте данные роутера с магазином и получите к ним доступ из магазина
- Навигация через диспетчерские действия
- Поддержка отладки изменений маршрута во времени в Redux devtools
Этого можно достичь за счет глубокой интеграции react-router с redux через Connected-React-Router и библиотеки истории.
В официальной документации упоминается react-router-redux, и он был интегрирован в react-router v4, но, согласно документации react-router-redux, репозиторий больше не поддерживается, и рекомендуется connect-react-router.
Сначала установите Connected-React-Router и библиотеки истории:
$ npm install --save connected-react-router
$ npm install --save history
Затем добавьте в хранилище следующую конфигурацию:
- Создайте
history
объект, потому что наше приложение работает на стороне браузера, поэтому используйтеcreateBrowserHistory
Создайте - использовать
connectRouter
обертывает корневой редуктор и обеспечиваетhistory
объект, получить новый корневой редуктор - использовать
routerMiddleware(history)
Реализуйте действия с использованием истории отправки, чтобы вы могли использоватьpush('/path/to/somewhere')
изменить маршрут (нажатие здесь от connect-react-router)
import thunk from 'redux-thunk'
import { createBrowserHistory } from 'history'
import { createStore, applyMiddleware } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import reducers from '../reducers'
export const history = createBrowserHistory()
const initialState = {}
const store = createStore(
connectRouter(history)(reducers),
initialState,
applyMiddleware(thunk, routerMiddleware(history))
)
export default store
В корневом компоненте добавляем следующую конфигурацию:
- использовать
ConnectedRouter
Обернуть маршрут и разместить созданный в магазинеhistory
Введение объекта, переданного в приложение в качестве реквизита -
ConnectedRouter
компонент, который будетProvider
подкомпонент
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import App from './App'
import store from './redux/store'
import { history } from './redux/store'
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('app')
)
Таким образом, мы интегрировали Redux с react-router.
Используйте диспетчеризацию для переключения маршрутов
После завершения вышеуказанной конфигурации вы можете использоватьdispatch
Переключение маршрутов:
import { push } from 'react-router-redux'
// Now you can dispatch navigation actions from anywhere!
store.dispatch(push('/about'))
react-router-config
До реакции-маршрутизатора v4 - статическая маршрутизация
В версиях до react-router v4 мы могли настраивать маршруты приложения напрямую, используя статические маршруты, что позволяло проверять и сопоставлять маршруты перед рендерингом.
В router.js обычно есть такой код:
const routes = (
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User} />
</Route>
<Route path="*" component={NoMatch} />
</Route>
</Router>
)
export default routes
Затем импортируйте маршрут во время инициализации, а затем выполните рендеринг:
import ReactDOM from 'react-dom'
import routes from './config/routes'
ReactDOM.render(routes, document.getElementById('app'))
Динамический маршрутизатор React-Router V4--
Начиная с версии V4, React-Router использует динамические компоненты вместо конфигурации пути, т.е.react-router является общим компонентом реагирующего приложения., напишите так, как вы его используете, вам не нужно отделять маршрут от компонента, как раньше. Таким образом, приложение реакции добавляет react-router, чтобы представить то, что нам нужно.
import React from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
Здесь мы будемBrowserRouter
Представлен и переименован вRouter
,BrowserRouter
Разрешить react-router передавать информацию о маршрутизации приложения черезcontext
Передается любому из необходимых компонентов. Таким образом, для правильной работы react-router необходимо, чтобы он отображался в корневом узле приложения.BrowserRouter
.
import React from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
class App extends Component {
render() {
return (
<Router>
<div>
<div>
<Link to="/">Home</Link>
</div>
<hr />
<Route exact path="/" component={Home} />
</div>
</Router>
)
}
}
также используетсяRoute
, когда местоположение приложения совпадает с маршрутом,Route
Отобразит указанный компонент, иначе отобразитnull
.
Добавить больше маршрутов, добавитьRoute
Компоненты можно использовать, но такой способ написания может показаться немного сумбурным, ведь маршруты разбросаны по всем компонентам, сложно увидеть маршруты всего приложения так же легко, как раньше, а если в проекте использовались версия react before -Router до v4, то обновление v4 тоже очень дорого.Чтобы решить эту проблему, официальный предоставляет библиотеку, специально используемую для работы со статической конфигурацией маршрутизации - react-router-config.
Добавьте react-router-config для использования статической маршрутизации
После добавления react-router-config мы можем написать наши знакомые статические маршруты. В то же время с его помощью конфигурацию маршрутизации можно рассредоточить по различным компонентам, и в итоге использоватьrenderRoutes
Слейте разрозненные фрагменты роутинга в корневом компоненте и отрендерите его.
Настройте статические маршруты:
import Home from './views/Home'
import About from './views/About'
const routes = [
{
path: '/',
exact: true,
component: Home
},
{
path: '/about',
component: About
}
]
export default routes
Затем слейте корневой компонент, визуализируйте:
import { renderRoutes } from 'react-router-config'
import HomeRoute from './views/Home/router'
import AboutRoute from './views/About/router'
// 合并路由
const routes = [...HomeRoute, ...AboutRoute]
class App extends Component {
render() {
return (
<Router>
<div className="screen">{renderRoutes(routes)}</div>
</Router>
)
}
}
renderRoutes
Это на самом деле помогло нам сделать что-то подобное:
const routes = (
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User} />
</Route>
<Route path="*" component={NoMatch} />
</Route>
</Router>
)
Это добавляет статическую маршрутизацию в приложение React.
Добавить замену горячего модуля
Функция горячей замены модулей (HMR) заменяет, добавляет или удаляет модули во время работы приложения без перезагрузки всей страницы. В основном следующими способами:
- Сохранить состояние приложения, потерянное при полной перезагрузке страницы
- Обновляйте только измененный контент, чтобы сэкономить время разработки
- Изменение стилей не требует обновления страницы
В режиме разработки HMR может заменить LiveReload, поддерживаемый webpack-dev-server.hot
модальный, прежде чем пытаться перезагрузить всю страницу,hot
Схема пытается обновиться с помощью HMR.
Включить HMR
Добавьте плагин HMR в файл конфигурации webpack:
plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin()]
добавлено сюдаNamedModulesPlugin
плагин,
Установите сервер webpack-dev наhot
модель:
const server = new WebpackDevServer(compiler, {
+ hot: true,
// noInfo: true,
quiet: true,
historyApiFallback: true,
filename: config.output.filename,
publicPath: config.output.publicPath,
stats: {
colors: true
}
});
Таким образом, при изменении кода реакции страница будет автоматически обновляться, изменять файл css, страница не будет обновляться, а стиль будет представлен напрямую.
Но будет проблема.Автоматическое обновление страницы приведет к потере состояния нашего компонента реакции.Можем ли мы изменить компонент реакции, например, изменить файл css, не обновляя страницу (сохраняя состояние страницы) и заменить это напрямую? Ответ — да, вы можете использовать react-hot-loader.
добавить реактивный горячий загрузчик
Добавить react-hot-loader очень просто, просто добавьте метод более высокого порядка при экспорте корневого компонента.hot
Только что:
import { hot } from "react-hot-loader";
class App extends Component {
...
}
export default hot(module)(App);
Таким образом, все приложение может изменять компонент реакции, сохраняя при этом состояние во время разработки.
Примечание:
В процессе разработки я читал какие-то статьи и говорил, что для взаимодействия с редуксом нужно добавить в store.js следующий код:
if (process.env.NODE_ENV === 'development') {
if (module.hot) {
module.hot.accept('../reducers/index.js', () => {
// const nextReducer = combineReducers(require('../reducers'))
// store.replaceReducer(nextReducer)
store.replaceReducer(require('../reducers/index.js').default)
})
}
}
Однако в react-hot-loader v4 он не нужен, добавьте его напрямуюhot
Вот и все.
Загружать компоненты асинхронно (разделение кода)
После завершения вышеуказанной конфигурации наше основное тело было построено почти, но когда вы открываете инструмент разработчика, вы обнаружите, что когда приложение начинает загружать, все JS всей приложения загружается напрямую, но мы ожидаем, что Страница для загрузки кода какой страницы, так как реализовать кодовое расщепление приложения?
Фактически, для достижения реагированного кода разделения библиотеки там много, таких как:
Вы можете выбрать один из них, в моем проекте используется react-loadable.
Мы настроили статическую маршрутизацию в проекте ранее, и компоненты импортируются напрямую, нам нужно обработать только напрямую импортированные компоненты, код выглядит следующим образом:
import loadable from 'react-loadable'
import Loading from '../../components/Loading'
export const Home = loadable({
loader: () => import('./Home'),
loading: Loading
})
export const About = loadable({
loader: () => import('./About'),
loading: Loading
})
const routes = [
{
path: '/',
exact: true,
component: Home
},
{
path: '/about',
component: About
}
]
export default routes
Асинхронное управление потоком задач
Идеи для реализации асинхронных операций
В большинстве случаев наши приложения являются синхронными операциями, то есть при отправке действия состояние будет обновляться немедленно, но иногда нам нужно выполнять асинхронные операции. Синхронные операции должны выполнять только один тип действий, а асинхронные операции должны выполнять три типа действий.
- Действие, когда действие инициировано
- Действия при успешном выполнении операции
- Действия при сбое операции
Чтобы различать эти три действия, можно добавить специальныйstatus
Поля как биты флага:
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
Или определить для них разные типы:
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
Итак, если вы хотите добиться асинхронной работы, вам нужно сделать:
- Когда операция начинается, выдается действие, состояние обновляется до «работа», а представление повторно отображается.
- После завершения операции выдается другое действие, которое запускает обновление состояния до «Конец операции», и представление снова перерисовывается.
redux-thunk
Асинхронная операция отправляет как минимум два действия. Первое действие такое же, как и синхронная операция, которую можно отправить напрямую. Итак, как отправить второе действие?
Мы можем отправить функцию Action Creator при отправке первого действия, чтобы второе действие могло быть отправлено автоматически после завершения асинхронного выполнения.
componentDidMount() {
store.dispatch(fetchPosts())
}
После успешной загрузки компонента отправьте действие для запроса данных, здесьfetchPosts
Это создатель действий. Код fetchPosts выглядит следующим образом:
export const SET_DEMO_DATA = createActionSet('SET_DEMO_DATA')
export const fetchPosts = () => async (dispatch, getState) => {
store.dispatch({ type: SET_DEMO_DATA.PENDING })
await axios
.get('https://jsonplaceholder.typicode.com/users')
.then(response => store.dispatch({ type: SET_DEMO_DATA.SUCCESS, payload: response }))
.catch(err => store.dispatch({ type: SET_DEMO_DATA.ERROR, payload: err }))
}
fetchPosts
это создатель действий, который возвращает функцию при выполненииdispatch
Действие, указывающее, что будет выполнена асинхронная операция; после завершения асинхронного выполнения, в соответствии с различными результатами запроса,dispatch
Различные действия возвращают результат асинхронной операции.
Несколько замечаний:
-
fetchPosts
Возвращается функция, тогда как обычный Action Creator по умолчанию возвращает объект. - Параметры возвращаемой функции:
dispatch
а такжеgetState
Для этих двух методов Redux параметром общего Action Creator является содержимое Action. - В возвращаемой функции сначала введите
store.dispatch({type: SET_DEMO_DATA.PENDING})
, указывающий на начало асинхронной операции. - После завершения асинхронной операции выполните еще одну
store.dispatch({ type: SET_DEMO_DATA.SUCCESS, payload: response })
, означающий окончание операции.
Но существует проблема,store.dispatch
При нормальных обстоятельствах можно отправлять только объекты, а мы хотим отправлять функции, чтобы позволитьstore.dispatch
Функции можно отправлять, мы используем middleware — redux-thunk.
Внедрить redux-thunk легко, просто используйте его при создании магазина.applyMiddleware(thunk)
Импортируйте его.
Инструменты разработки и отладки
Отладка неизбежна в процессе разработки.Существует множество часто используемых инструментов отладки, таких как redux-devtools-extension, redux-devtools, storybook и т. д.
redux-devtools-extension
redux-devtools-extension — инструмент для отладки редукса, которым очень удобно следить за действиями.
Первый по версии браузераChrome Web StoreилиMozilla Add-onsчтобы скачать плагин.
Затем при создании магазина добавьте его в конфигурацию Enhancer Store можно:
import thunk from "redux-thunk";
import { createBrowserHistory } from "history";
import { createStore, applyMiddleware } from "redux";
+ import { composeWithDevTools } from "redux-devtools-extension/logOnlyInProduction";
import { connectRouter, routerMiddleware } from "connected-react-router";
import reducers from "../reducers";
export const history = createBrowserHistory();
const initialState = {};
+ const composeEnhancers = composeWithDevTools({
+ // options like actionSanitizer, stateSanitizer
+ });
const store = createStore(
connectRouter(history)(reducers),
initialState,
+ composeEnhancers(applyMiddleware(thunk, routerMiddleware(history)))
);
напиши в конце
В этой статье выясняется мое понимание архитектуры приложения React и конкретной конфигурации связанных библиотек, а также углубляется понимание архитектуры приложения React.Однако в этой статье не рассматриваются такие аспекты, как неизменяемость данных, сохраняемость, оптимизация веб-пакетов и т. д. , и в будущем продолжит изучение смежных тем, а также постарается построить более полноценное React-приложение.
Кроме того, после обновления последней версии Babel в процессе сборки проекта было обнаружено, что @babel/preset-stage-0 скоро устаревает.Рекомендуется использовать другие альтернативы.Подробнее см. здесь:
прикрепил
Ключевые слова:
- redux
- react-router
- react-router-config
- Асинхронная загрузка (разделение кода)
- Горячее обновление
- Асинхронное управление задачами — redux-thunk
- react-redux
- redux-devtools-extension
Некоторые используемые библиотеки
Ссылаться на
- Дизайн архитектуры приложения React
- Анализ усилителя магазина Redux
- createStore
- applyMiddleware
- combineReducers
- compose
- [Перевод] Краткое руководство по React Router v4
- Интеграция React Router и Redux
- горячая замена модуля
- react-router4 на основе разделения маршрута react-router-config и загрузки по запросу
- Введение в React Router 4 и лежащую в его основе философию маршрутизации
- Асинхронное действие
- redux-thunk промежуточного программного обеспечения redux
- Вводное руководство по Redux (2): промежуточное ПО и асинхронные операции