написать впереди
Прошло много лет с момента начала реагирования в 2013 году. Реагиологический экологический redux, React-redux и redux-Saga также стал опорным стандартам для разработчиков реагирования, и они вполне знакомы использовать. Эта статья кратко рассказывает о redux, Реагировать - принцип реализации Redux и Redux-Saga.
redux
Эта классическая диаграмма описывает рабочий процесс редукции.Короче говоря, уровень представления запускает действие для диспетчера.Диспетчер передает действие редьюсеру для расчета, а затем передает его уровню представления для обновления состояния после получения нового состояния. .
Ниже приведена упрощенная версия кода createStore.js.
export function createStore(reducer, enhandler) {
if (enhandler) {
return enhandler(createStore)(reducer)
}
let state = {}; //全局state存放的地方
let observers = []; // 观察者队列
// getter
function getState() {
return state;
}
// setter
function dispatch(action) {
state = reducer(state, action);
observers.forEach(fn => fn());
}
function subscribe(fn) {
observers.push(fn);
}
dispatch({ type: '@@REDUX_INIT' }) // 初始化state数据
return {getState, dispatch, subscribe}
}
Как видите, createStore — это функция более высокого порядка, которая получает в качестве параметров функцию редуктора и функцию обработчика и возвращает три функции: getState, dispatch и subscribe, среди которых
Редуктор используется для вычисления нового состояния в соответствии с текущим состоянием и входящим действием.Редукс предусматривает, что редуктор должен быть чистой функцией;
Обработчик — это функция расширения промежуточного программного обеспечения после выполнения applyMiddleware, которая используется для улучшения диспетчеризации;
getState просто возвращает состояние, эквивалентное геттеру;
диспетчеризация — единственная запись для изменения состояния, эквивалентная сеттеру;
subscribe — это подписка на состояние, при изменении состояния сработает соответствующая callback-функция.
Проще говоря, createStore сохраняет глобальное состояние как переменную замыкания, гарантируя, что внешний мир не сможет напрямую прочитать и изменить его, и возвращает дескриптор для управления состоянием, а также обеспечивает мониторинг изменений состояния.
В части редуктора пример вычисляет состояние в соответствии с различными типами действий и возвращает новое состояние.
import { ADD, SUB } from '../action/app';
const initialState = {
count: 0
}
export const appReducer = function(state, action) {
switch (action.type) {
case ADD:
return {
...state,
count: state.count + action.text
}
case SUB:
return {
...state,
count: state.count - action.text
}
default:
return state || initialState;
}
}
Обычно мы объединяем редукторы, а затем передаем их в createStore.
import {combineReducers} from '../myRedux';
import {appReducer} from './app';
import {compReducer} from './comp';
const rootReducer = combineReducers({
app: appReducer,
comp: compReducer
})
export default rootReducer;
код для combReducers
export const combineReducers = (reducers) => {
return (state = {}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
},
{}
);
};
}
Видно, что после передачи createStore в rootReducer полученная структура состояния имеет вид
{
app: {},
comp: {}
}
CombineReducers распределяет состояние.Например, appReducer передает только данные, соответствующие ключу приложения, что обеспечивает эффект разделения данных и их отдельной обработки.
Redux также предоставляет расширенные функции.Мы знаем, что этот шаг расширения реализуется между диспетчеризацией и редуктором, то есть за счет улучшения диспетчеризации для достижения эффекта расширения других функций.Ниже приведена реализация упрощенной версии applyMiddleware. js
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)))
}
export const applyMiddleware = (...middlewares) => {
return (createStore) => {
return (reducer) => {
const store = createStore(reducer)
let { getState, dispatch } = store
const params = {
getState,
dispatch: (action) => dispatch(action)
}
const middlewareArr = middlewares.map(middleware => middleware(params))
dispatch = compose(...middlewareArr)(dispatch)
return { ...store, dispatch }
}
}
}
Видно, что applyMiddleware — это каррированная функция, которая как раз соответствует вызывающему методу enhandler(createStore)(reducer), а самые важные две строки кода — это
const middlewareArr = middlewares.map(middleware => middleware(params))
dispatch = compose(...middlewareArr)(dispatch)
Здесь поочередно инициализируется входящее промежуточное ПО, а в промежуточное ПО вставляются getState и dispatch, чтобы у промежуточного ПО была возможность доступа к хранилищу.После завершения первого вызова промежуточного ПО функция compose используется для объединения нескольких промежуточное ПО Части соединяются вместе, передавая старую отправку для второго вызова и, наконец, возвращая расширенную отправку.
Взгляните на пример промежуточного программного обеспечения, снова промежуточное программное обеспечение — это каррированная функция.
export default function logger({ getState }) {
return (next) => (action) => {
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}
Другими словами, applyMiddleware и средство ведения журнала промежуточного программного обеспечения являются каррированными функциями, которые используют преимущества своих характеристик отложенного выполнения и вызывают их шаг за шагом.
После обработки applyMiddleware(middleware1, middleware2, middleware3) он получит
Метод выполнения dispatch = middleware1(middleware2(middleware3(action))), то есть луковая модель
Короче говоря, общая идея реализации редукса состоит в том, чтобы извлечь состояние в единое место для управления, состояние устанавливается только для чтения и может быть изменено только редьюсером. Согласовано, что действие — это объект, а редюсер — это чистая функция, которая не мешает обработке асинхронных сценариев, а лишь предоставляет механизм посредника для открытия расширенных функций. С точки зрения принципа реализации, код воплощает идею функционального программирования с использованием таких методов, как функции высшего порядка и многократное каррирование функций, а дизайн кода довольно лаконичен и изобретателен.
react-redux
Когда redux используется вместе с реакцией, нам нужно вручную отслеживать изменения состояния и обновлять компонент через подписку в компоненте.Чтобы решить эту проблему, редукс официально предоставляет библиотеку react-redux, которая связывает компоненты состояния и реакции, подключая для достижения эффекта автоматического мониторинга используется следующий
index.js
<Provider store={store}>
<React.StrictMode>
<App />
<Comp />
</React.StrictMode>
</Provider>
app.js
class APP extends React.Component {
constructor(props) {
super(props)
}
handleAddItem = () => {
const {dispatch} = this.props;
dispatch({
type: `${namespace}/ADD_ITEM`,
text: 2
})
}
handleDelItem = () => {
const {dispatch} = this.props;
dispatch({
type: `${namespace}/DEL_ITEM`,
text: 2
})
}
render() {
const {comp} = this.props;
const {list} = comp;
return (
<div>
<ul>
{
list.map(i => {
return <li>{i}</li>
})
}
</ul>
<button onClick={this.handleAddItem}>add li</button>
<button onClick={this.handleDelItem}>del li</button>
</div>
)
}
}
function mapStateToProps(state){
return {
comp: state.comp
}
}
function mapDispatchToProps(dispatch){
return {
dispatch
}
}
export default connect(mapStateToProps, mapDispatchToProps)(APP);
В основном смотрите на реализацию provider.js и connect.js
provider.js простая версия
import React from 'react'
import PropTypes from 'prop-types'
export default class Provider extends React.Component {
// 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法
static childContextTypes = {
store: PropTypes.object
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
// 实现getChildContext方法,返回context对象,也是固定写法
getChildContext() {
return { store: this.store }
}
// 渲染被Provider包裹的组件
render() {
return this.props.children
}
}
Видно, что, используя контекстную функцию react, нужно только поставить компонент Provider на верхний уровень, а все остальные компоненты могут получать состояние из контекста, избегая послойной передачи props
Давайте посмотрим на простую реализацию подключения
import React from 'react';
import PropTypes from 'prop-types';
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() {
const {store} = this.context;
const {getState, dispatch} = store;
return (
<Component
// 传入该组件的props,需要由connect这个高阶组件原样传回原组件
{ ...this.props }
// 根据mapStateToProps把state挂到this.props上
{ ...mapStateToProps(getState()) }
// 根据mapDispatchToProps把dispatch(action)挂到this.props上
{ ...mapDispatchToProps(dispatch) }
/>
)
}
}
//接收context的固定写法
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
}
}
connect — это высокоуровневый компонент, который получает параметры mapStateToProps и mapDispatchToProps. Функция mapStateToProps — сопоставить конкретное состояние с реквизитами компонента. , При изменении состояния все подключенные компоненты будут выполнять рендеринг.
Резюме: Суть Provider заключается в использовании контекста для унифицированной доставки. Суть соединения заключается в извлечении и повторном использовании логики мониторинга и получения состояния. Это также обычная функция компонентов более высокого порядка. Подключенный компонент становится пользовательским интерфейсом. -type.Вы можете получить состояние из реквизита и отрендерить его.
redux-saga
Когда дело доходит до redux-saga, обычно упоминается redux-thunk, оба из которых являются промежуточным программным обеспечением для redux, и оба имеют дело с асинхронными сценариями. redux-thunk очень короткий, всего дюжина строк кода, простая реализация такова
export default function thunk({ dispatch, getState }) {
return (next) => (action) => {
if (typeof action === 'function') {
action(dispatch, getState())
}
next(action)
}
}
Видно, что преобразователь поддерживает действие в виде функции, а дескриптор отправки передается функции для обработки.Мы можем вызвать действие асинхронно, и дождаться возврата результата перед отправкой, чтобы добиться эффекта поддержки асинхронный Простой пример использования
const addCountAction = (text) => {
return {
type: ADD,
text
}
}
const fetchData = (text) => (dispatch) => {
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 2000)
}).then(() => {
dispatch(addCountAction(text))
})
}
Здесь предполагается, что асинхронный результат возвращается через 2 с, а диспетчерский вызов выполняется после возврата.
Примерный процесс redux-thunk:
Хотя redux-thunk поддерживает асинхронные сценарии, его недостатки также очевидны:
1. Используйте метод обратного вызова для достижения асинхронности, и легко формировать слои кода лапши обратного вызова.
2. В каждом действии разбросана асинхронная логика, которой сложно управлять единообразно
Поэтому появилась более мощная асинхронная схема управления redux-saga, которую можно использовать вместо redux-thunk.
Примерный процесс редукс-саги:
Его основные особенности
1. Реализован генератором, что больше соответствует стилю синхронного кода;
2. Унифицированный мониторинг действий.При попадании в действие выполняется соответствующая задача саги, и поддерживаются взаимные вызовы между каждой сагой, что делает асинхронный код более удобным для унифицированного управления.
В саге появляется новое понятие, в котором эффект относится к общему js-объекту, описывающему заданное действие, а сага относится к функции-генератору.
Сначала посмотрите на доступ к саге:
//index.js
const sagaMiddleware = createSagaMiddleware();
store = createStore(rootReducer, applyMiddleware(sagaMiddleware, logger));
sagaMiddleware.run(rootSaga)
//rootSaga.js
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// Our worker Saga: 将异步执行 increment 任务
function* addAsync(action) {
yield fork(delay, 1000)
yield put(formatAction(action, namespace))
}
export default function* rootSaga() {
yield takeEvery(`${namespace}/ADD_ASYNC`, addAsync)
}
Сначала сгенерируйте sagaMiddleware через createSagaMiddleware, зарегистрируйте его как промежуточное ПО Redux, а затем вызовите метод запуска, предоставляемый промежуточным ПО.Функция запуска заключается в единообразной инициализации rootSaga и включении мониторинга действий. Простая версия createSagaMiddleware выглядит следующим образом:
export default function sagaMiddlewareFactory() {
let _store; //闭包store,后续sagaMiddleware可以访问到
function sagaMiddleware(store) {
_store = store;
return (next) => (action) => {
next(action);
channel.put(action)
}
}
// 启动rootSaga,即进行入口saga的自执行
sagaMiddleware.run = function(rootSaga) {
let iterator = rootSaga();
proc(iterator, _store);
}
return sagaMiddleware;
}
sagaMiddleware — это промежуточное ПО, которое соответствует стандарту redux и монтирует метод запуска в sagaMiddleware, который вызывает proc в методе запуска и видит реализацию proc
export default function proc(iterator, store) {
next();
function next(err, preValue) {
let result;
if (err) {
result = iterator.throw(err);
} else {
result = iterator.next(preValue)
}
if (result.done) return result;
if (isPromise(result.value)) { //yield promise
let promise = result.value;
promise.then((success) => next(null, success)).catch((err) => next(err, null))
} else if (isEffect(result.value)) { //yield effect
let effect = result.value;
runEffect[effect.type](effect, next, store)
} else { //yield others
next(null, result.value)
}
}
}
Видно, что proc является самоисполнителем генератора, который реализован рекурсивно, когда result.done имеет значение true, это означает, что выполнение генератора завершено. Мы предполагаем, что есть только три типа yield, promise, effect и общий синтаксис, которые обрабатываются соответствующим образом. Например эффект, когда в саге Когда yield put(action), он просто вызывает обычную функцию put и возвращает эффект типа put, эффект представляет собой обычный объект js, посмотрите на определение эффекта:
export function take(signal) {
return {
isEffect: true,
type: 'take',
signal
}
}
export function put(action) {
return {
isEffect: true,
type: 'put',
action
}
}
export function takeEvery(signal, saga) {
return {
isEffect: true,
type: 'takeEvery',
signal,
saga
}
}
Поэтому эффект описывает тип и связанные с ним параметры задачи, а выполнение эффекта находится в ссылке runEffect, а именно runEffect.js:
function runTake({signal}, next, store) {
channel.take({
signal,
callback: (args) => {next(null, args)}
})
}
function runPut({action}, next, store) {
const {dispatch} = store;
dispatch(action);
next();
}
function runTakeEvery({signal, saga, ...args}, next, store) {
function *takeEveryGenerator() {
while(true) {
let action = yield take(signal);
yield fork(saga, action);
}
}
runFork({saga: takeEveryGenerator}, next, store);
}
runEffect выполняет соответствующий эффект, например, put, вы можете видеть, что суть заключается в инкапсуляции диспетчеризации. Другие вспомогательные функции, предоставляемые saga, такие как takeEvery, — это инкапсуляция низкоуровневых эффектов.
Итак, когда мы используем takeEvery для мониторинга действия и вызываем take для мониторинга, что означает вызванный в функции канал. Взгляните на пример реализации канала:
function channel() {
let _task = null;
function take(task) {
_task = task;
}
function put(action) {
const {type, ...args} = action;
if (!_task) {
return;
}
_task.signal === type && _task.callback(action);
}
return {
take, put
}
}
export default channel();
Можно видеть, что реализация канала представляет собой простой способ создания потребителей, выполнения задач генерации и размещения задач потребления. Вот тут видно почему тейк заблокирован.При взятии типа действия он фактически помещает задачу в канал.Только при отправке экшена вызывается пут потребление.Вот сага где находится тейк эффект Итератор будет продолжают выполняться, поэтому, когда дубль выполняется, на самом деле итератор next не выполняет следующую итерацию, что приводит к блокировке саги.
Резюме: Из этой простой модели видно, что redux-saga фактически устанавливает слой асинхронной обработки между диспетчером и редьюсером для решения асинхронных задач. Когда sagaMiddleware инициализирует запуск, он выполняет самовыполнение саги о записи и начинает отслеживать действие. Когда встречается эффект доходности, он выполняется соответствующим runEffect.При попадании в действие выводится соответствующая задача саги.Это общий принцип редукс-саги.
На данный момент я завершил простой анализ принципов redux, react-redux и redux-saga, из которого я могу не только почерпнуть отличные дизайнерские идеи, но и понять, почему они используются в бизнесе.
демонстрационный адрес:GitHub.com/Фабрика/Обучение...
Использованная литература:
woohoo.brief.com/afraid/1608786С 9…
сегмент fault.com/ah/119000001…
Автор: Xianchong@ppmoney