Реализация Redux.
В этой статье реализован простойredux
а такжеreact-redux
, основное содержание
-
redux
Идеи дизайна и принципы реализации -
redux 中间件
Идеи дизайна и принципы реализации -
react-redux
Идеи дизайна и принципы реализации
redux
Это менеджер состояний, который хранит данные, например, когда мы создаемstore.js
, в котором мы храним эти данные, и нам достаточно обратиться в любом месте к этому файлу, чтобы получить соответствующее значение статуса:
let state = {
count: 1
}
Читаем и модифицируем следующее состояние:
console.log(state.count)
state.count = 2
Теперь реализуем модификацию и использование состояния (счетчика)! Конечно, есть очевидная проблема с вышеизложенным:
- Этот менеджер состояния может управлять только
count
, непригодный. - Исправлять
count
После этого используйтеcount
где вы не можете получать уведомления.
реализовать подписку
Мы можем использовать шаблон публикации-подписки для решения этой проблемы. Давайте обернем это в функциюredux
function createStore(initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
function changeState(newState) {
state = newState
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
function getState() {
return state
}
return { subscribe, changeState, getState }
}
На данный момент мы завершили простой менеджер состояний.
-
state
данные могут быть свободно определены - Мы модифицируем состояние и прослушиваем изменения в месте подписки, что может обеспечить мониторинг.
let initState = {
count: 1,
info: {
age: 18
}
}
let store = createStore(initState)
store.subscribe(() => {
let state = store.getState()
console.log('subscribe function one: ', state)
})
store.subscribe(() => {
let state = store.getState()
console.log('subscribe function two: ', state)
})
store.changeState({ ...store.getState(), count: store.getState().count + 1 })
store.changeState({
...store.getState(),
info: { age: store.getState().info.age - 1 }
})
// ==== result
// subscribe function one: { count: 2, info: { age: 18 } }
// subscribe function two: { count: 2, info: { age: 18 } }
// subscribe function one: { count: 2, info: { age: 17 } }
// subscribe function two: { count: 2, info: { age: 17 } }
Здесь нужно понимать, чтоcreateStore
,при условииchangeState
,getState
,subscribe
три способности.
В приведенной выше функции мы вызываемstore.changeState
можно изменитьstate
的值,这样就存在很大的弊端了。 Напримерstore.changeState({})
Мы случайно поставили
store
Данные очищаются, или данные других компонентов изменены по ошибке, это явно небезопасно, и устранить ошибки сложно, поэтому нам нужно действовать условноstore
, не позволяя пользователям напрямую изменятьstore
Данные.
Поэтому нам нужно ограничение для измененияstate
значение, не допуская возникновения непредвиденных ситуаций.state
Значение очищено или используется неправильно. Мы можем решить эту проблему в два этапа:
-
dispatch
: делатьstate
пересмотреть план, сказатьstore
, каков мой план модификации. -
reducer
: Исправлятьstore.changeState
метод, скажите ему изменитьstate
, по нашему плану модифицировать.
То есть мы будемstore.changeState
Переписатьstore.dispatch
, передайте еще один в функциюreducer
функция для ограничения модификации значений состояния.
Реализовать редукторы
Редьюсер — это чистая функция, которая принимает состояние и возвращает новое состояние.
function createStore(reducer, initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
/* state 值的修改 */
function dispatch(action) {
state = reducer(state, action)
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
function getState() {
return state
}
return { subscribe, dispatch, getState }
}
попробуем использоватьdispatch
а такжеreducer
для достижения автоинкремента и автодекремента
let initState = {
count: 1,
info: {
age: 18
}
}
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 }
case 'DECREMENT':
return { ...state, count: state.count - 1 }
default:
return state
}
}
let store = createStore(reducer, initState)
store.subscribe(() => {
let state = store.getState()
console.log('subscribe function: ', state)
})
store.dispatch({ type: 'INCREMENT' }) // 自增
store.dispatch({ type: 'DECREMENT' }) // 自减
store.dispatch({ count: 2 }) // 计划外:不生效
мы знаемreducer
является функцией ограничения, которая принимает староеstate
, верните новый, как и планировалосьstate
. В нашем проекте большое количествоstate
, каждыйstate
Требуются функции ограничений.Как бы они выглядели, если бы все они были написаны вместе?
Все планы написаны в одномreducer
функция, приведет кreducer
функции огромны и сложны. Следующее будет инкапсулироватьcombineReducers
гранулироватьreducer
функция.
Реализовать combReducers
Гранулированный редуктор
Исходя из опыта, мы обязательно разделим многие компоненты в соответствии с размерностью компонентов.reducer
функцию, а затем объединить их с функцией.
Давайте управлять двумяstate
,Одинcounter
,Одинinfo
.
let state = {
counter: { count: 0 },
info: { age: 18 }
}
их соответствующиеreducer
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 }
default:
return state
}
}
function infoReducer(state, action) {
switch (action.type) {
case 'INCREMENT-AGE':
return { age: state.age + 1 }
default:
return state
}
}
Давайте попробуем достичьcombineReducers
функция
- Передайте параметр объекта,
key
значениеstate
дерево состоянийkey
стоимость,value
для соответствующегоreducer
функция. - Перебирает параметры объекта, выполняя каждый
reducer
функция, пройти вstate[key]
, функция получает каждыйreducer
Новейшиеstate
стоимость. - связь
state
значение и возврат. вернуть новый объединенныйreducer
функция.
function combineReducers(reducers) {
/* reducerKeys = ['counter', 'info']*/
const reducerKeys = Object.keys(reducers)
/*返回合并后的新的reducer函数*/
return function combination(state = {}, action) {
/*生成的新的state*/
const nextState = {}
/*遍历执行所有的reducers,整合成为一个新的state*/
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
const reducer = reducers[key]
/*之前的 key 的 state*/
const previousStateForKey = state[key]
/*执行 分 reducer,获得新的state*/
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
}
return nextState
}
}
использоватьcombineReducers
:
const reducers = combineReducers({
counter: counterReducer,
info: infoReducer
})
let store = createStore(reducers, initState)
store.subscribe(() => {
let state = store.getState()
console.log('subscribe function: ', state)
})
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'INCREMENT-AGE' })
Однако этого недостаточно, положимreducer
Разделить по размеру компонента, черезcombineReducers
объединены. Но есть еще проблема,state
Мы по-прежнему писали это вместе, что могло вызватьstate
Деревья огромные, неинтуитивные и сложные в уходе. нам нужно разделить, т.state
,Одинreducer
Напишите произведение.
гранулированное состояние
переписатьcombineReducers
функция, не более чем простая, вcreateStore
выполнять в функцииdispatch({ type: Symbol() })
function createStore(reducer, initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
function dispatch(action) {
state = reducer(state, action)
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
/* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
dispatch({ type: Symbol() })
function getState() {
return state
}
return { subscribe, dispatch, getState }
}
Будуstate
в их соответствующиеreducer
:
function counterReducer(state = { count: 1 }, action) {
//...
}
function infoReducer(state = { age: 18 }, action) {
//...
}
// 合并 reducer
const reducers = combineReducers({
counter: counterReducer,
info: infoReducer
})
// 移除 initState
let store = createStore(reducers)
console.log(store.getState()) // { counter: { count: 1 }, info: { age: 18 } }
Давайте подумаем, какой эффект может принести эта линия?
-
createStore
при использовании, которое не соответствует ни одномуtype
изaction
, для запускаstate = reducer(state, action)
- потому что
action.type
не совпадает, каждый субreducer
войдетdefault
элемент, вернуть самоинициализированныйstate
, так что инициализированныйstate
дерево вверх.
Внедрение промежуточного программного обеспечения Redux
если вы использовалиExpress
а такжеKoa
таких как серверные библиотеки, то вы, вероятно, уже знакомы с концепцией промежуточного программного обеспечения.
Под так называемым промежуточным ПО можно понимать перехватчик, который используется для перехвата и обработки определенных процессов, причем промежуточное ПО можно использовать последовательно. В основном здесьdispatch
расширение или переписывание, улучшениеdispatch
функция.
Пример промежуточного ПО
Сначала реализуемredux-logger
Плагин, то есть функция журнала печати, записывает изменения до и после модификацииstate
а такжеaction
, мы можем переписатьstore.dispatch
реализовать:
const reducers = combineReducers({ counter: counterReducer })
let store = createStore(reducers)
const next = store.dispatch
// 重写 dispatch
store.dispatch = action => {
console.log('prevState: ', store.getState())
console.log('action', action)
next(action)
console.log('nextState: ', store.getState())
}
store.dispatch({ type: 'INCREMENT' })
выходной результат
prevState: { counter: { count: 1 } }
action { type: 'INCREMENT' }
nextState: { counter: { count: 2 } }
Теперь мы реализовали простойredux-logger
промежуточное ПО. У меня есть другое требование, мне нужно записать причину каждой ошибки данных, давайте расширимdispatch
store.dispatch = action => {
try {
next(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
Таким образом, каждый раз, когда в диспетчере возникает исключение, мы будем его записывать.
Сотрудничество с несколькими промежуточными программами
Теперь мне нужно записывать и журналы, и исключения, что мне делать? Конечно очень просто, две функции совмещены!
store.dispatch = action => {
try {
console.log('prevState: ', store.getState())
console.log('action', action)
next(action)
console.log('nextState: ', store.getState())
} catch (err) {
console.error('错误报告: ', err)
}
}
А если будет другой спрос? затем изменитьdispatch
функция? Как насчет еще 10 потребностей? К тому времениdispatch
Функция должна быть слишком большой и грязной, чтобы ее поддерживать!
Нам необходимо подумать о том, как реализовать масштабируемую модель сотрудничества с несколькими промежуточными программами.
- мы кладем
loggerMiddleware
извлечен
const loggerMiddleware = action => {
console.log('prevState: ', store.getState())
console.log('action', action)
next(action)
console.log('nextState: ', store.getState())
}
- мы кладем
exceptionMiddleware
извлечен
const exceptionMiddleware = action => {
try {
/*next(action)*/
loggerMiddleware(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
store.dispatch = exceptionMiddleware
- Текущий код имеет очень серьезную проблему, т.
exceptionMiddleware
Это написано мертвымloggerMiddleware
, мы должны позволитьnext(action)
Станьте динамичным, любое промежуточное ПО может быть
const exceptionMiddleware = next => action => {
try {
/*loggerMiddleware(action);*/
next(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
/*loggerMiddleware 变成参数传进去*/
store.dispatch = exceptionMiddleware(loggerMiddleware)
- Одна и та же причина,
loggerMiddleware
внутриnext
теперь равноstore.dispatch
, Привести кloggerMiddleware
Никакое другое промежуточное ПО не может быть расширено в нем! мы также ставимnext
написано как динамический
const loggerMiddleware = next => action => {
console.log('this state', store.getState())
console.log('action', action)
next(action)
console.log('next state', store.getState())
}
До сих пор мы исследовали высоко масштабируемую модель сотрудничества промежуточного программного обеспечения!
const store = createStore(reducer)
const next = store.dispatch
const loggerMiddleware = next => action => {
console.log('this state', store.getState())
console.log('action', action)
next(action)
console.log('next state', store.getState())
}
const exceptionMiddleware = next => action => {
try {
next(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
store.dispatch = exceptionMiddleware(loggerMiddleware(next))
В это время мы с радостью построили новыйloggerMiddleware.js
,ОдинexceptionMiddleware.js
файл, я хочу разделить два промежуточного программного обеспечения на отдельные файлы. Будут ли проблемы?
loggerMiddleware
содержит внешние переменныеstore
, что делает невозможным разделение промежуточного ПО. Затем мы кладемstore
Также передайте его как параметр~
const store = createStore(reducer)
const next = store.dispatch
const loggerMiddleware = store => next => action => {
console.log('this state', store.getState())
console.log('action', action)
next(action)
console.log('next state', store.getState())
}
const exceptionMiddleware = store => next => action => {
try {
next(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
const logger = loggerMiddleware(store)
const exception = exceptionMiddleware(store)
store.dispatch = exception(logger(next))
До сих пор мы действительно реализовали два независимых промежуточных ПО!
Теперь у меня есть требование вывести текущую метку времени перед печатью журнала. Используйте промежуточное ПО для достижения!
const timeMiddleware = store => next => action => {
console.log('time', new Date().getTime())
next(action)
}
const time = timeMiddleware(store)
store.dispatch = exception(time(logger(next)))
Внедрение промежуточного программного обеспечения
В предыдущем разделе мы полностью реализовали правильное промежуточное ПО! Но использование промежуточного программного обеспечения не очень дружелюбно
let store = createStore(reducers)
const next = store.dispatch
const loggerMiddleware = store => next => action => {
console.log('this state', store.getState())
console.log('action', action)
next(action)
console.log('next state', store.getState())
}
const exceptionMiddleware = store => next => action => {
try {
next(action)
} catch (err) {
console.error('错误报告: ', err)
}
}
const timeMiddleware = store => next => action => {
console.log('time', new Date().getTime())
next(action)
}
const time = timeMiddleware(store)
const logger = loggerMiddleware(store)
const exception = exceptionMiddleware(store)
store.dispatch = exception(time(logger(next)))
На самом деле нам нужно знать только три мидлвара, а остальные детали можно инкапсулировать! мы расширяемcreateStore
реализовать!
Давайте сначала посмотрим на ожидаемое использование
/*接收旧的 createStore,返回新的 createStore*/
const newCreateStore = applyMiddleware(
exceptionMiddleware,
timeMiddleware,
loggerMiddleware
)(createStore)
/*返回了一个 dispatch 被重写过的 store*/
const store = newCreateStore(reducer)
выполнитьapplyMiddleware
const applyMiddleware = function(...middlewares) {
/*返回一个重写createStore的方法*/
return function rewriteCreateStoreFunc(oldCreateStore) {
/*返回重写后新的 createStore*/
return function newCreateStore(reducer, initState) {
/*1. 生成store*/
const store = oldCreateStore(reducer, initState)
/*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/
/* const chain = [exception, time, logger]*/
const chain = middlewares.map(middleware => middleware(store))
let dispatch = store.dispatch
/* 实现 exception(time((logger(dispatch))))*/
chain.reverse().map(middleware => {
dispatch = middleware(dispatch)
})
/*2. 重写 dispatch*/
store.dispatch = dispatch
return store
}
}
}
Теперь есть небольшая проблема, у нас есть дваcreateStore
.
/*没有中间件的 createStore*/
let store = createStore(reducers, initState)
/*有中间件的 createStore*/
const rewriteCreateStoreFunc = applyMiddleware(
exceptionMiddleware,
timeMiddleware,
loggerMiddleware
)
const newCreateStore = rewriteCreateStoreFunc(createStore)
const store = newCreateStore(reducer, initState)
Чтобы сделать пользователей более унифицированными, мы можем легко заставить их использовать одинаково, мы изменим следующиеcreateStore
метод
function createStore(reducer, initState, rewriteCreateStoreFunc) {
/*如果有 rewriteCreateStoreFunc,那就采用新的 createStore */
if (rewriteCreateStoreFunc) {
const newCreateStore = rewriteCreateStoreFunc(createStore)
return newCreateStore(reducer, initState)
}
/*否则按照正常的流程走*/
//...
}
окончательное использование
const rewriteCreateStoreFunc = applyMiddleware(
exceptionMiddleware,
timeMiddleware,
loggerMiddleware
)
const store = createStore(reducer, initState, rewriteCreateStoreFunc)
compose
нашapplyMiddleware
Вход[A, B, C]
Перевести вA(B(C(next)))
, достигается так
const chain = [A, B, C]
let dispatch = store.dispatch
chain.reverse().map(middleware => {
dispatch = middleware(dispatch)
})
redux
предоставленный А.compose
способ помочь нам сделать это
const chain = [A, B, C]
dispatch = compose(...chain)(store.dispatch)
Посмотрите, как он это делает
export default function compose(...funcs) {
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
Конечноcompose
Функции могут быть трудны для понимания новичками, вам нужно только то, что они делают!
опустить initState
Иногда мы создаемstore
не передается, когдаinitState
Как мы используем?
const store = createStore(reducer, {}, rewriteCreateStoreFunc)
redux
позвольте нам написать
const store = createStore(reducer, rewriteCreateStoreFunc)
Нам просто нужно изменитьcreateStore
функция, если второй аргументobjec
т, мы думаем, что онinitState
,еслиfunction
Мы думаем, что онrewriteCreateStoreFunc
.
function craeteStore(reducer, initState, rewriteCreateStoreFunc) {
if (typeof initState === 'function') {
rewriteCreateStoreFunc = initState
initState = undefined
}
//...
}
Реализация реакции-редукции
Выше мы выполнили простойredux
.
Если компонент хочет получить доступ к общедоступному состоянию из хранилища, ему необходимо выполнить четыре шага:import
представлятьstore
,getState
получить статус,dispatch
Изменить статус,subscribe
Обновления подписки, код относительно избыточен,
а такжеreact-redux
Он предоставляет решение для операции слияния:react-redux
поставкаProvider
а такжеconnect
два API,Provider
Будуstore
вставитьthis.context
Чтобы сэкономитьimport
этот шаг,connec
т будетgetState
,dispatch
слился вthis.props
, и автоматически подписываться на обновления, упрощая еще три шага,
Реализовать поставщика
import React from 'react'
import PropTypes from 'prop-types'
export default class Provider extends React.Component {
// 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法
static childContextTypes = {
store: PropTypes.object
}
// 实现getChildContext方法,返回context对象,也是固定写法
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
// 渲染被Provider包裹的组件
render() {
return this.props.children
}
}
ЗаканчиватьProvider
После этого мы можем передать компонентthis.context.store
Возьми эту формуstore
, не надо отделятьimport store
.
реализовать подключение
Давайте подумаем, как добитьсяconnect
, Давайте рассмотримconnect
Как использовать:
connect(mapStateToProps, mapDispatchToProps)(App)
Мы уже знаем,connect
перениматьmapStateToProps
,mapDispatchToProps
Два метода, а затем возвращают функцию более высокого порядка, эта функция более высокого порядка получает компонент и возвращает компонент более высокого порядка (фактически, это добавление некоторых свойств и функций к входящему компоненту)connect
По поступающимmap
,Будуstate
а такжеdispatch(action)
монтировать подкомпонентыprops
, мы сразу выпускаемconnect
Код реализации в несколько строк не сложен:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import bindActionCreators from '../redux/bindActionCreators'
export default function connect(mapStateToProps, mapDispatchToProps) {
return function(Component) {
class Connect extends React.Component {
componentDidMount() {
// 从context获取store并订阅更新
this.context.store.subscribe(this.handleStoreChange.bind(this))
}
handleStoreChange() {
// 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新
this.forceUpdate()
}
render() {
const dispathProps =
typeof mapDispatchToProps &&
bindActionCreators(mapDispatchToProps, this.context.store.dispatch)
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{...this.props}
// 根据mapStateToProps把state挂到this.props上
{...mapStateToProps(this.context.store.getState())}
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{...dispathProps}
/>
)
}
}
// 接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
конец статьи
См. код:Реализовать редукцию и реакцию-редукцию
- происходить отПолностью понять Redux (реализация Redux с нуля
- заимствовано из10 строк кода, чтобы увидеть принцип редукции