На самом деле у автора не было плана написания, связанного с редуксом, но коллега в компании недавно поделился технологиями, связанными с редуксом, и автор взял на себя задачу рецензирования некоторых статей.В процессе рецензирования автор потратил немало сил И время для проверки информации.И код реализации, у меня накопились тысячи слов заметок до и после, и у меня также есть мнение о редуксе, поэтому я написал эту статью от руки, надеясь вдохновить и поразмыслить всех , Спасибо♪(・ω·)ノПосле этой статьи Чтобы научиться, читатель должен научиться понимать:
redux
Идеи дизайна и принципы реализацииreact-redux
Идеи дизайна и принципы реализацииredux中间件
Идеи дизайна и принципы реализации
1. Реализация редукса
Прежде чем все начнется, мы должны сначала ответить на вопрос: зачем нам нужен редукс и какую проблему он решает для нас? Только ответив на этот вопрос, мы сможем понять идеи дизайна Redux.
Поскольку среда разработки основана на компонентах, между компонентами существует множество взаимодействий. Иногда эти взаимодействия охватывают несколько компонентов или набор данных используется совместно несколькими компонентами. Простая передача значений родительских и дочерних компонентов не может удовлетворить наши потребности. нужно место для доступа и управления этим публичным состоянием. И redux предоставляет нам решение для управления публичным состоянием, и наш последующий дизайн и реализация также будут сосредоточены на этом требовании.
Давайте подумаем, как управлять публичным состоянием: поскольку это публичное состояние, мы можем напрямую извлечь публичное состояние. Мы создаем файл store.js, а затем храним в нем публичное состояние, другие компоненты могут получить доступ к общему состоянию, пока они импортируют это хранилище.
const state = {
count: 0
}
Мы храним общедоступный счетчик состояний в хранилище, и компонент может управлять этим счетчиком после импорта хранилища. Это самый прямой магазин.Конечно, наш магазин не должен быть спроектирован таким образом.Есть две основные причины:
1. Легко использовать не по назначению
Например, если кто-то случайно присваивает значение {} хранилищу, очищает хранилище или по ошибке изменяет данные других компонентов, это явно небезопасно, и устранять ошибки сложно, поэтому нам нужноусловноУправляйте хранилищем, чтобы пользователи не могли напрямую изменять данные в хранилище.
2. Плохая читаемость
JS — это язык, который сильно зависит от семантики.Представьте, если публичное состояние изменяется непосредственно в коде без комментариев, другие люди будут больше запутываться в сопровождении кода в будущем.Чтобы понять смысл изменения состояния, это должно быть выведено из контекста, поэтому лучше дать каждой операциисделать себе имя.
Передача проекта
Давайте переосмыслим, как мы проектируем этогосударственный управляющий, согласно нашему вышеприведенному анализу, мы надеемся, что к публичному состоянию можно получить глобальный доступ, а частное состояние нельзя будет изменить напрямую.ЗакрытиеСоответствует ли оно только этим двум требованиям, поэтому мы спроектируем публичное состояние как замыкание (студенты, которые испытывают трудности с пониманием замыканий, также могут пропускать замыкания, что не влияет на последующее понимание)
Поскольку мы хотим получить доступ к состоянию, мы должны иметьgetterиsetter, кроме того, при изменении состояния мы должны широковещательно уведомить компонент о том, что состояние изменилось. Это не то же самое, что три API Redux:getState、dispatch、subscribe
Совпадает? Обрисуем приблизительную форму магазина несколькими строчками кода:
export const createStore = () => {
let currentState = {} // 公共状态
function getState() {} // getter
function dispatch() {} // setter
function subscribe() {} // 发布订阅
return { getState, dispatch, subscribe }
}
1. Реализация getState
getState()
Реализация очень проста, достаточно вернуть текущее состояние:
export const createStore = () => {
let currentState = {} // 公共状态
function getState() { // getter
return currentState
}
function dispatch() {} // setter
function subscribe() {} // 发布订阅
return { getState, dispatch, subscribe }
}
2.диспетчерская реализация
ноdispatch()
Мы должны подумать о реализации вышеизложенного, после вышеприведенного анализа наша цельусловно, по имениИзмените данные магазина, так как же нам достичь этих двух пунктов? Мы уже знаем, что при использовании диспетчеризации мы будем передавать объект действия в диспетчерскую(), который включает в себя состояние, которое мы хотим изменить, и имя операции (actionType).В зависимости от типа хранилище будет изменять соответствующее состояние. . Мы также используем этот дизайн здесь:
export const createStore = () => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
switch (action.type) {
case 'plus':
currentState = {
...state,
count: currentState.count + 1
}
}
}
function subscribe() {}
return { getState, subscribe, dispatch }
}
Мы написали суждение actionType в диспетчеризации, что очень раздуто и коряво, поэтому мы подумали о том, чтобы извлечь эту часть правил модификации состояния и вынести ее наружу, с чем мы и знакомыreducer
.Давайте изменим код, чтобы редьюсер проходил снаружи:
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {}
return { getState, dispatch, subscribe }
}
Затем мы создаем файл reducer.js и пишем наш редуктор
//reducer.js
const initialState = {
count: 0
}
export function reducer(state = initialState, action) {
switch(action.type) {
case 'plus':
return {
...state,
count: state.count + 1
}
case 'subtract':
return {
...state,
count: state.count - 1
}
default:
return initialState
}
}
Код написан здесь, мы можем его проверитьgetState
иdispatch
:
//store.js
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {}
return { getState, subscribe, dispatch }
}
const store = createStore(reducer) //创建store
store.dispatch({ type: 'plus' }) //执行加法操作,给count加1
console.log(store.getState()) //获取state
Запустив код, мы обнаружим, что напечатанное состояние: { count: NaN }, это потому, что исходные данные в хранилище пусты, state.count + 1 на самом деле underfind+1, и выводится NaN, поэтому мы имеем чтобы сделать это сначала Сохраните инициализацию данных, мы выполняем инициализированную отправку перед выполнением отправки ({ type: 'plus' }), actionType этой отправки может быть заполнен по желанию, если он не дублирует существующий тип, поэтому что переключатель в редюсере по умолчанию может перейти на Just initialize the store:
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
}
function subscribe() {}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
const store = createStore(reducer) //创建store
store.dispatch({ type: 'plus' }) //执行加法操作,给count加1
console.log(store.getState()) //获取state
Запустив код, мы можем вывести правильное состояние: { count: 1 }
3. реализация подписки
Хотя мы смогли получить доступ к общедоступному состоянию, изменение хранилища напрямую не вызовет обновление представления. Нам нужно отслеживать изменение хранилища. Здесь мы применяем шаблон проектирования - шаблон наблюдателя. Шаблон наблюдателя широко используется для мониторинга событий.Реализация (в некоторых местах прописан режим публикации-подписки, но я лично считаю, что здесь правильнее называть режим наблюдателя. Существует много дискуссий о разнице между наблюдателями и публикацией-подпиской, читатели могут поискать)
Так называемый режим наблюдателя, концепция также очень проста: наблюдатель прислушивается к изменениям наблюдаемого и уведомляет всех наблюдателей, когда наблюдаемое изменяется. Итак, как нам реализовать эту функцию мониторинга-уведомления?Чтобы позаботиться о студентах, которые не знакомы с реализацией режима наблюдателя, давайте выпрыгнем из редукса и напишем простой код реализации режима наблюдателя:
//观察者
class Observer {
constructor (fn) {
this.update = fn
}
}
//被观察者
class Subject {
constructor() {
this.observers = [] //观察者队列
}
addObserver(observer) {
this.observers.push(observer)//往观察者队列添加观察者
}
notify() { //通知所有观察者,实际上是把观察者的update()都执行了一遍
this.observers.forEach(observer => {
observer.update() //依次取出观察者,并执行观察者的update方法
})
}
}
var subject = new Subject() //被观察者
const update = () => {console.log('被观察者发出通知')} //收到广播时要执行的方法
var ob1 = new Observer(update) //观察者1
var ob2 = new Observer(update) //观察者2
subject.addObserver(ob1) //观察者1订阅subject的通知
subject.addObserver(ob2) //观察者2订阅subject的通知
subject.notify() //发出广播,执行所有观察者的update方法
Объясните приведенный выше код: объект наблюдателя имеетupdate
метод (метод, который будет выполняться после получения уведомления), мы хотим выполнить метод после того, как наблюдаемое отправит уведомление; у наблюдаемого естьaddObserver
иnotify
Метод addObserver используется для сбора наблюдателей, по сути, это добавление методов обновления наблюдателей в очередь, а при выполнении уведомления методы обновления всех наблюдателей берутся из очереди и выполняются, тем самым реализуя уведомление , Особенности. Наша функция мониторинга-уведомления redux также будет реализовывать подписку в соответствии с этой идеей реализации:
В приведенном выше примере шаблона наблюдателя реализация подписки должна быть хорошо понятна.Здесь диспетчеризация и уведомление объединены.Каждый раз, когда мы отправляем, мы делаем широковещательную рассылку, чтобы уведомить хранилище компонентов об изменении состояния.
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
Давайте попробуем эту подписку (нет необходимости создавать компонент, а затем вводить магазин, а затем подписываться, и напрямую моделировать два компонента в store.js, чтобы использовать подписку для подписки на изменения в магазине):
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
const store = createStore(reducer) //创建store
store.subscribe(() => { console.log('组件1收到store的通知') })
store.subscribe(() => { console.log('组件2收到store的通知') })
store.dispatch({ type: 'plus' }) //执行dispatch,触发store的通知
Консоль успешно выводит результат выполнения обратного вызова, переданного store.subscribe():
К этому моменту простой избыточный код был завершен, и такие детали, как проверка параметров, также добавлены в реальный исходный код избыточного кода, но общая идея в основном такая же, как и выше.
Мы уже можем внедрить хранилище в компонент для доступа к состоянию и подписки на изменения хранилища.Если посчитать, то получается ровно десять строк кода (`∀´)Ψ. Но когда мы взглянем на полосу прогресса справа, мы обнаружим, что все не так просто, и мы прошли только одну треть пути. Хоть мы и реализовали redux, кодеров это не устраивает.Когда мы используем store, нам нужно вводить store в каждый компонент, потом getState, потом dispatch, и subscribe.Код избыточен и нам нужно объединить некоторые повторяющиеся операции, и одно из решений по упрощению слияния нам знакомоreact-redux.
2. Реализация реакции-редукции
Как упоминалось выше, если компонент хочет получить доступ к общедоступному состоянию из хранилища, ему необходимо выполнить четыре шага: импортировать в хранилище, получить состояние для получения состояния, отправить для изменения состояния, подписаться, чтобы подписаться на обновление, код относительно избыточны, мы хотим объединить некоторые повторения, и react-redux предоставляет решение для операций слияния: react-redux обеспечиваетProvider
иconnect
Два API, которые провайдер будет хранить в this.context лет, исключая импорт этого шага, подключение getState, отправка объединенных в this.props и подписка на автоматические обновления, упрощают еще три шага, давайте посмотрим, как достичь этих двух API:
1. Реализация провайдера
Начнем с более простогоProvider
Начинаем реализовывать, Провайдер — это компонент, который получает хранилище и кладет его в глобальнуюcontext
Object, почему его следует помещать в контекст, мы поймем позже, когда будем реализовывать соединение. Затем мы создаем компонент Provider и помещаем хранилище в контекст.Существует несколько фиксированных способов использования контекстного API (об использовании контекста см.эта статья)
import React from 'react'
import PropTypes from 'prop-types'
export 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, не нужно импортировать магазин отдельно.
2. подключить реализацию
Давайте подумаем, как добитьсяconnect
, давайте сначала рассмотрим использование connect:
connect(mapStateToProps, mapDispatchToProps)(App)
Мы уже знаем, что connect получает два метода, mapStateToProps и mapDispatchToProps, а затем возвращает функцию более высокого порядка, которая получает компонент и возвращает компонент более высокого порядка (фактически добавляя некоторые свойства и функции к входящему компоненту). map, и монтируем состояние и диспетчеризацию(действие) на пропсы подкомпонента.Мы непосредственно выпускаем код реализации подключения, который не усложняется несколькими строками:
export 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() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{ ...this.props }
// 根据mapStateToProps把state挂到this.props上
{ ...mapStateToProps(this.context.store.getState()) }
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{ ...mapDispatchToProps(this.context.store.dispatch) }
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
После написания кода для подключения нам нужно объяснить два момента:
1. Значение Provider: Давайте посмотрим на код коннекта.На самом деле контекст только предоставляет коннекту способ получить магазин.Мы можем полностью заменить контекст, напрямую импортируя магазин в коннект. Так в чем смысл существования Провайдера?Вообще-то я тоже некоторое время думал об этом, а потом вспомнил... Коннект выше написан мной, конечно, можно напрямую импортировать магазин, но коннект оф. react-redux инкапсулирован и предоставляет API только внешнему миру. , поэтому вам нужно пропустить Provider в хранилище.
2. Шаблон декоратора в connect: посмотрите, как вызывается connect:connect(mapStateToProps, mapDispatchToProps)(App)
На самом деле, connect может передавать приложение вместе с mapStateToProps. Кажется, что нет необходимости возвращать функцию, а затем передавать ее приложению. Почему react-redux спроектирован таким образом? Как широко используемый модуль, react-redux должен имеют свой глубокий смысл в его дизайне.
На самом деле конструкция соединения такова.Шаблон декоратораРеализация так называемого шаблона декоратора — это просто оболочка для класса, позволяющая динамически расширять функции класса. connect и компоненты более высокого порядка (HoC) в React являются реализациями этого шаблона. Кроме того, есть и более прямые причины: этот дизайн совместим с ES7.装饰器(Decorator)
, чтобы мы могли использовать @connect для упрощения кода, см. это для использования @connect:
//普通connect使用
class App extends React.Component{
render(){
return <div>hello</div>
}
}
function mapStateToProps(state){
return state.main
}
function mapDispatchToProps(dispatch){
return bindActionCreators(action,dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(App)
//使用装饰器简化
@connect(
state=>state.main,
dispatch=>bindActionCreators(action,dispatch)
)
class App extends React.Component{
render(){
return <div>hello</div>
}
}
После написания react-redux мы можем написать демо, чтобы протестировать его: используйтеcreate-react-app
Создайте проект, удалите ненужные файлы и создайте store.js, reducer.js, react-redux.js, чтобы написать наш код redux и react-redux соответственно, index.js — это входной файл проекта, в App.js In Например, мы просто пишем счетчик, нажимаем кнопку, чтобы отправить отправку, увеличиваем количество в магазине на единицу и отображаем количество на странице. Окончательный каталог файла и код выглядят следующим образом:
// store.js
export const createStore = (reducer) => {
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
//reducer.js
const initialState = {
count: 0
}
export function reducer(state = initialState, action) {
switch(action.type) {
case 'plus':
return {
...state,
count: state.count + 1
}
case 'subtract':
return {
...state,
count: state.count - 1
}
default:
return initialState
}
}
//react-redux.js
import React from 'react'
import PropTypes from 'prop-types'
export 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
}
}
export 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() {
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{ ...this.props }
// 根据mapStateToProps把state挂到this.props上
{ ...mapStateToProps(this.context.store.getState()) }
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{ ...mapDispatchToProps(this.context.store.dispatch) }
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from './react-redux'
import { createStore } from './store'
import { reducer } from './reducer'
ReactDOM.render(
<Provider store={createStore(reducer)}>
<App />
</Provider>,
document.getElementById('root')
);
//App.js
import React from 'react'
import { connect } from './react-redux'
const addCountAction = {
type: 'plus'
}
const mapStateToProps = state => {
return {
count: state.count
}
}
const mapDispatchToProps = dispatch => {
return {
addCount: () => {
dispatch(addCountAction)
}
}
}
class App extends React.Component {
render() {
return (
<div className="App">
{ this.props.count }
<button onClick={ () => this.props.addCount() }>增加</button>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Запускаем проект, нажимаем кнопку добавить, можно считать правильно, ок, большой успех, весь наш процесс редукции, реакции-редукции пропал
3. Реализация промежуточного программного обеспечения redux
Реализация вышеупомянутого редукса и реакции-редукса относительно проста, давайте проанализируем реализацию, которая немного сложнееизбыточное промежуточное ПО. Под так называемым промежуточным ПО можно понимать перехватчик, который используется для перехвата и обработки определенных процессов, причем промежуточное ПО может использоваться последовательно. В редукции наше промежуточное ПО перехватывает процесс отправки диспетчера редюсеру, тем самым улучшая функцию диспетчеризации.
Я проверил много информации, связанной с промежуточным программным обеспечением redux, но в конце концов обнаружил, что нет ничего лучше написанного, чемофициальная документацияЯсно, в документе есть четкие и яркие объяснения для каждого шага от требований промежуточного программного обеспечения до проектирования, от концепции до реализации. Ниже мы шаг за шагом проанализируем дизайн и реализацию промежуточного программного обеспечения redux, взяв в качестве примера промежуточное программное обеспечение, которое записывает журналы, как и документ.
Давайте подумаем, если мы хотим распечатать содержимое хранилища после каждой отправки, как бы мы это сделали:
1. Вручную распечатывать содержимое магазина после каждой отправки
store.dispatch({ type: 'plus' })
console.log('next state', store.getState())
Это самый прямой способ, конечно, мы не можем вставлять кусок кода для печати лога после каждой отправки в проект, мы должны хотя бы извлечь эту часть функции.
2. Инкапсулируйте отправку
function dispatchAndLog(store, action) {
store.dispatch(action)
console.log('next state', store.getState())
}
Мы можем переупаковать общедоступный новый метод отправки, что может уменьшить часть повторяющегося кода. Однако каждый раз, когда вы используете эту новую диспетчеризацию, вам приходится импортировать ее извне, что еще более проблематично.
3. Заменить отправку
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
let result = next(action)
console.log('next state', store.getState())
return result
}
Если мы заменим диспетчеризацию напрямую, не нужно ли нам ссылаться на нее извне каждый раз, когда мы ее используем? Для простой печати лога этого достаточно, но если у нас все же есть необходимость мониторить ошибки диспетчеризации, то можно добавить код отлова ошибки после кода вывода лога, но с увеличением функциональных модулей код объем будет быстро увеличиваться, и это промежуточное программное обеспечение будет невозможно поддерживать в будущем Мы надеемся, что различные функциинезависимый сменныймодуль.
4. Модульный
// 打印日志中间件
function patchStoreToAddLogging(store) {
let next = store.dispatch //此处也可以写成匿名函数
store.dispatch = function dispatchAndLog(action) {
let result = next(action)
console.log('next state', store.getState())
return result
}
}
// 监控错误中间件
function patchStoreToAddCrashReporting(store) {
//这里取到的dispatch已经是被上一个中间件包装过的dispatch, 从而实现中间件串联
let next = store.dispatch
store.dispatch = function dispatchAndReportErrors(action) {
try {
return next(action)
} catch (err) {
console.error('捕获一个异常!', err)
throw err
}
}
}
Мы разделяем модули с разными функциями на разные методы,Получите последнюю цепочку вызовов реализации Store.Dispatch, упакованную промежуточным ПО.. Затем мы можем использовать и комбинировать это промежуточное ПО по отдельности, вызывая эти методы промежуточного ПО.
patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)
На данный момент мы в основном реализовали компонуемое и подключаемое промежуточное ПО, но мы все еще можем написать код, который будет выглядеть лучше. Мы заметили, что методы промежуточного слоя, которые мы сейчас пишем, должны сначала получить диспетчеризацию, а затем заменить диспетчеризацию в методе Мы можем немного упростить эту часть повторяющегося кода: мы не заменяем диспетчеризацию в методе, а возвращаем новый диспетчеризация, Затем пусть цикл выполняет замену на каждом шаге.
5. applyMiddleware
Измените промежуточное ПО, чтобы оно возвращало новую отправку вместо замены исходной отправки.
function logger(store) {
let next = store.dispatch
// 我们之前的做法(在方法内直接替换dispatch):
// store.dispatch = function dispatchAndLog(action) {
// ...
// }
return function dispatchAndLog(action) {
let result = next(action)
console.log('next state', store.getState())
return result
}
}
Добавьте вспомогательный метод в ReduxapplyMiddleware, для добавления промежуточного ПО
function applyMiddleware(store, middlewares) {
middlewares = [ ...middlewares ] //浅拷贝数组, 避免下面reserve()影响原数组
middlewares.reverse() //由于循环替换dispatch时,前面的中间件在最里层,因此需要翻转数组才能保证中间件的调用顺序
// 循环替换dispatch
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
Затем мы можем добавить промежуточное ПО в такой форме:
applyMiddleware(store, [ logger, crashReporter ])
На данный момент мы можем просто проверить промежуточное программное обеспечение. Я создал три промежуточных программного обеспечения, а именно Logger1, Thunk и Logger2, и их функции также очень просты, Print Logger1 -> Выполнить асинхронную диспетчерцу -> Распечатать Logger2, мы используем этот пример для наблюдения за порядком промежуточного программного обеспечения
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from './react-redux'
import { createStore } from './store'
import { reducer } from './reducer'
let store = createStore(reducer)
function logger(store) {
let next = store.dispatch
return (action) => {
console.log('logger1')
let result = next(action)
return result
}
}
function thunk(store) {
let next = store.dispatch
return (action) => {
console.log('thunk')
return typeof action === 'function' ? action(store.dispatch) : next(action)
}
}
function logger2(store) {
let next = store.dispatch
return (action) => {
console.log('logger2')
let result = next(action)
return result
}
}
function applyMiddleware(store, middlewares) {
middlewares = [ ...middlewares ]
middlewares.reverse()
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
applyMiddleware(store, [ logger, thunk, logger2 ])
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Выполнить асинхронную отправку
function addCountAction(dispatch) {
setTimeout(() => {
dispatch({ type: 'plus' })
}, 1000)
}
dispatch(addCountAction)
выходной результат
Видно, что консоль сначала выводит результат печати промежуточного программного обеспечения logger1, а затем входит в промежуточное программное обеспечение thunk для печати «thunk». После ожидания в течение одной секунды запускается асинхронная отправка, и она проходит через logger1 -> thunk - > logger2 снова. На данный момент мы в основном реализовали подключаемый и компонуемый механизм промежуточного программного обеспечения, а также, кстати, реализовали redux-thunk.
6. Чистые функции
Предыдущий пример в основном удовлетворил наши потребности, но мы можем его еще улучшить. Приведенная выше функция все еще не кажется достаточно "чистой". Функция модифицирует диспетчеризацию самого хранилища в теле функции, в результате чего получается так называемый " побочные эффекты». Исходя из спецификации программирования, мы можем сделать некоторые преобразования. Опираясь на идеи реализации react-redux, мы можем использовать applyMiddleware в качестве функции более высокого порядка для улучшения хранилища вместо замены отправки:
Сначала внесите небольшую модификацию в createStore, передайте обработчик (т.е. applyMiddleware), и обработчик получит и укрепит createStore.
// store.js
export const createStore = (reducer, heightener) => {
// heightener是一个高阶函数,用于增强createStore
//如果存在heightener,则执行增强后的createStore
if (heightener) {
return heightener(createStore)(reducer)
}
let currentState = {}
let observers = [] //观察者队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action);
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
dispatch({ type: '@@REDUX_INIT' })//初始化store数据
return { getState, subscribe, dispatch }
}
Промежуточное программное обеспечение далее каррируется, и next передается в качестве параметра
const logger = store => next => action => {
console.log('log1')
let result = next(action)
return result
}
const thunk = store => next =>action => {
console.log('thunk')
const { dispatch, getState } = store
return typeof action === 'function' ? action(store.dispatch) : next(action)
}
const logger2 = store => next => action => {
console.log('log2')
let result = next(action)
return result
}
Применение модернизации ПО промежуточного слоя
const applyMiddleware = (...middlewares) => createStore => reducer => {
const store = createStore(reducer)
let { getState, dispatch } = store
const params = {
getState,
dispatch: (action) => dispatch(action)
//解释一下这里为什么不直接 dispatch: dispatch
//因为直接使用dispatch会产生闭包,导致所有中间件都共享同一个dispatch,如果有中间件修改了dispatch或者进行异步dispatch就可能出错
}
const middlewareArr = middlewares.map(middleware => middleware(params))
dispatch = compose(...middlewareArr)(dispatch)
return { ...store, dispatch }
}
//compose这一步对应了middlewares.reverse(),是函数式编程一种常见的组合方法
function compose(...fns) {
if (fns.length === 0) return arg => arg
if (fns.length === 1) return fns[0]
return fns.reduce((res, cur) =>(...args) => res(cur(...args)))
}
Код не должен быть сложным для понимания, основываясь на предыдущем примере, мы в основном сделали две модификации
1. Вместо этого используйте метод composemiddlewares.reverse(), compose — это распространенный способ комбинирования функций в функциональном программировании, compose использует сокращение для оригинального комбинирования функций промежуточного программного обеспечения, так что входящие функции промежуточного программного обеспечения становятся(...arg) => mid1(mid2(mid3(...arg)))
эта форма
2. Вместо того, чтобы напрямую заменять диспетчеризацию, createStore расширен как функция более высокого порядка, и в конце возвращается новое хранилище.
7. Модель лукового кольца
Причина, по которой модель луковичного кольца отложена, заключается в том, что луковичное кольцо не имеет тесного отношения к реализации переднего промежуточного программного обеспечения.Чтобы не запутать читателей, я упомяну это здесь. Мы напрямую выпускаем три промежуточных ПО, которые печатают журналы, наблюдают за выходными результатами и легко понимают модель луковичных колец.
const logger1 = store => next => action => {
console.log('进入log1')
let result = next(action)
console.log('离开log1')
return result
}
const logger2 = store => next => action => {
console.log('进入log2')
let result = next(action)
console.log('离开log2')
return result
}
const logger3 = store => next => action => {
console.log('进入log3')
let result = next(action)
console.log('离开log3')
return result
}
Результаты
Поскольку наше промежуточное ПО устроено следующим образом:
logger1(
console.log('进入logger1')
logger2(
console.log('进入logger2')
logger3(
console.log('进入logger3')
//dispatch()
console.log('离开logger3')
)
console.log('离开logger2')
)
console.log('离开logger1')
)
Итак, мы видим, что порядок выполнения промежуточного программного обеспечения на самом деле такой:
Введите log1 -> выполнить следующий -> введите log2 -> выполните следующий -> введите log3 -> выполните следующий -> следующее выполнение будет завершено -> оставить log3 -> вернуться к верхнему промежуточному программному обеспечению, выполнить оператор после верхнего промежуточного программного обеспечения next - > оставить log2 -> вернуться к промежуточному программному обеспечению log1, выполнить оператор после следующего log1 -> оставить log1
Это называется "модель луковых колец".
4. Резюме и благодарность
На самом деле, прочитав полный текст, читатели должны понять, что реализация redux, react-redux и redux middleware не сложна, и основной код каждого из них составляет всего более десяти строк, но между этими несколькими строками code, содержит ряд идей программирования и парадигм проектирования - шаблон наблюдателя, шаблон декоратора, механизм промежуточного программного обеспечения, каррирование функций, функциональное программирование. Смысл чтения исходного кода состоит в том, чтобы понять и испытать эти идеи.
На написание всей статьи ушёл месяц, основными справочными материалами поделились коллеги и рядом статей по теме, отдельное спасибо г-ну Лонг Чао и г-ну Ю Чжуну за то, что они поделились. В процессе исследования деталей я также получил много сомнений от друзей, которые никогда не встречались, особенно благодаря Frank1e за его помощь в понимании каррирования промежуточного программного обеспечения. Всем большое спасибо♪(・ω・)ノ