本文代码
一、什么是 Redux
A Predictable State Container for JS Apps(JS应用的可预测状态容器)
可预测:实际上指的是纯函数(每个相同的输入,都会有固定输出,并且没有副作用)这个是由 reducer 来保证的,同时方便了测试
状态容器: 在 web 页面中,每个 DOM 元素都有自己的状态,比如弹出框有显示与隐藏两种状态,列表当前处于第几页,每页显示多少条就是这个列表的状态,存储这些状态的对象就是状态容器。
虽说 Redux 是 React 全家桶的一员,但实际上 Redux 与 React 没有必然联系,它可以应用在任何其他库上,只是跟 React 搭配比较火。 
Redux 的核心概念
- Store: 存储状态的容器, JS 对象
- Actions: 对象,描述对状态进行怎样的操作
- Reducers: 函数,操作状态并返回新的状态
- React Component: 组件,或者称之为视图
Redux 工作流程
- 组件通过 dispatch 方法触发 Action
- Store 接收 Action 并将 Action 分发给 Reducer
- Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
- 组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件
接下来分别讲解下:
React Components
就是 React 组件,也就是 UI 层
Store
管理数据的仓库,对外暴露些 API
let store = createStore(reducers);
有以下职责:
- 维持应用的 state;
- 提供
getState() 方法获取 state; - 提供
dispatch(action) 方法更新 state; - 通过
subscribe(listener) 注册监听器; - 通过
subscribe(listener) 返回的函数注销监听器。
Action
action 就是个动作,在组件里通过 dispatch(action) 来触发 store 里的数据更改
Reducer
action 只是个指令,告诉 store 要进行怎样的更改,真正更改数据的是 reducer。
二、为什么要使用 Redux
默认 React 传递数据只能自上而下传递,而下层组件要向上层组件传递数据时,需要上层组件传递修改数据的方法到下层组件,当项目越来越大时,这种传递方式会很杂乱。
而引用了 Redux,由于 Store 是独立于组件,使得数据管理独立于组件,解决了组件间传递数据困难的问题
计数器
定义个 store 容器文件,根据 reducer 生成 store
import { createStore } from "redux";
const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case "ADD":
return state + payload;
case "MINUS":
return state - payload;
default:
return state;
}
};
export default createStore(counterReducer);
在组件中
import React, { Component } from "react";
import store from "../store";
export default class Redux extends Component {
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
add = () => {
store.dispatch({ type: "ADD", payload: 1 });
};
minus = () => {
store.dispatch({ type: "MINUS", payload: 1 });
};
render() {
return (
<div className="border">
<h3>加减器</h3>
<button onClick={this.add}>add</button>
<span style={{ marginLeft: "10px", marginRight: "10px" }}>
{store.getState()}
</span>
<button onClick={this.minus}>minus</button>
</div>
);
}
}
- 通过 getState 显示 state
- 点击 add 或 minus 时触发 dispatch 并传递指令(action)
- 并在 componentDidMount 监听 state 更改,有更改则就 forceUpdate 强制渲染
- componentWillUnmount 清除监听

三、开始手写
通过上面的讲解也可以看到,其实主要的就是 createStore 函数,该函数会暴露 getState,dispatch,subScribe 三个函数 所以先搭下架子,创建 createStore.js 文件
export default function createStore(reducer) {
let currentState;
function getState() {}
function dispatch() {}
function subscribe() {}
return {
getState,
dispatch,
subscribe,
};
}
接着完善下各方法
getState
返回当前的 state
function getState() {
return currentState
}
dispatch
接收 action,并更新 store,通过谁更新的呢: reducer
function dispatch(action) {
currentState = reducer(currentState, action);
}
subscribe
作用: 订阅 state 的更改
如何做: 采用观察者模式,组件方监听 subscribe ,并传入回调函数,在 subscribe 里注册回调,并在 dispatch 方法里触发回调
let curerntListeners = [];
function subscribe(listener) {
curerntListeners.push(listener);
return () => {
const index = curerntListeners.indexOf(listener);
curerntListeners.splice(index, 1);
};
}
dispatch 方法在更新数据之后,要执行订阅事件。
function dispatch(action) {
currentState = reducer(currentState, action);
curerntListeners.forEach(listener => listener());
}
完整代码
将上面计数器里的 redux 改成引用手写的 redux,会发现页面没有最初值 
所以在 createStore 里加上 dispatch({ type: "kkk" }); 给 state 赋初始值,要注意传入的这个 type 要进入 reducer 函数的 default 条件
完整代码如下:
export default function createStore(reducer) {
let currentState;
let curerntListeners = [];
function getState() {
return currentState;
}
function dispatch(action) {
currentState = reducer(currentState, action);
curerntListeners.forEach((listener) => listener());
}
function subscribe(listener) {
curerntListeners.push(listener);
return () => {
const index = curerntListeners.indexOf(listener);
curerntListeners.splice(index, 1);
};
}
dispatch({ type: "kkk" });
return {
getState,
dispatch,
subscribe,
};
}
大家也可以自行查看下 Redux 里的 createStore 源码
四、Redux 中间件
说到 Redux,那就不得不提中间件,因为本身 Redux 能做的东西很有限,比如需要 redux-thunk 来达到异步调用,redux-logger 记录日志等。 中间件就是个函数,在组件发出 Action 和执行 Reducer 这两步之间,添加其他功能,相当于加强 dispatch。

开发 Redux 中间件
开发中间件是有模板代码的
export default store => next => action => {}
- 一个中间件接收 store 作为参数,返回一个函数
- 返回的这个函数接收 next(老的 dispatch 函数) 作为参数,返回一个新的函数
- 返回的新的函数就是加强的 dispatch 函数,在这个函数里可以拿到上面两层传递进来的 store 以及 next
比如模拟写个 logger 中间件
function logger(store) {
return (next) => {
return (action) => {
console.log("====================================");
console.log(action.type + "执行了!");
console.log("prev state", store.getState());
next(action);
console.log("next state", store.getState());
console.log("====================================");
};
};
}
export default logger;
注册中间件
const store = createStore(
reducer,
applyMiddleware(logger)
)
可以看出是通过 createStore 的第二个参数来实现的,这个参数官方称为 enhancer。 enhancer 是个参数为 createStore 的函数,并返回个新的 createStore 函数
function enhancer (createStore) {
return function (reducer) {
var store = createStore(reducer);
var dispatch = store.dispatch;
function _dispatch (action) {
if (typeof action === 'function') {
return action(dispatch)
}
dispatch(action);
}
return {
...store,
dispatch: _dispatch
}
}
}
实现上 createStore 总共是有三个参数,除了第一个 reducer 参数是必传的之外,第二个 state 初始值,以及第三个 enhancer 都是可选的
下面我们在手写的 createStore 里加入 enhancer 参数, 以及手写下 applyMiddleware 函数
createStore 里加上 enhancer
function createStore(reducer,enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer必须是函数')
}
return enhancer(createStore)(reducer)
}
}
手写 applyMiddleware
按上面的分析,applyMiddleware 函数,会接收中间件函数,并返回个 enhancer,所以基本结构为
export default function applyMiddleware(...middlewares) {
return function (createStore) {
return function newCreateStore(reducer) {};
};
}
看下 logger 中间件的结构,
function logger(store) {
return (next) => {
return (action) => {
console.log("====================================");
console.log(action.type + "执行了!");
console.log("prev state", store.getState());
next(action);
console.log("next state", store.getState());
console.log("====================================");
};
};
}
export default logger;
完善下 applyMiddleware
export default function applyMiddleware(middleware) {
return function (createStore) {
return function newCreateStore(reducer) {
let store = createStore(reducer);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
const chain = middleware(midApi);
dispatch = chain(dispatch);
return {
...store,
dispatch,
};
};
};
}
测试: 是可以正常打印出日志的 
支持多个中间件
上面已经在 applyMiddleware 函数里处理只有一个中间件的情况,那多个的场景呢?
首先我们再模拟写个 redux-thunk 中间件
默认 redux 只支持同步,并且参数只能是对象形式,redux-thunk 要实现的是你传入一个函数时,我就直接执行该函数,异步操作代码写在传递过来的函数里,如果传递过来的是个对象,则调用下一个中间件
function thunk({ getState, dispatch }) {
return next => {
return action => {
if (typeof action == 'function') {
return action(dispatch, getState)
}
next(action)
}
}
}
现在要去依次执行各个中间件,要如何依次执行呢?就得采用柯里化,首先写个 compose 函数
function compose(...funs) {
if (funs.length === 0) {
return (arg) => arg
}
if (funs.length === 1) {
return funs[0]
}
return funs.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})
}
applyMiddleware 函数支持多个中间件:
export default function applyMiddleware(...middlewares) {
return function (createStore) {
return function newCreateStore(reducer) {
let store = createStore(reducer);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
const middlewareChain = middlewares.map((middle) => middle(midApi));
const middleCompose = compose(...middlewareChain);
dispatch = middleCompose(dispatch);
return {
...store,
dispatch,
};
};
};
}
验证:
import { createStore } from "../kredux";
import logger from "../kredux/middlewares/logger";
import thunk from "../kredux/middlewares/thunk";
import applyMiddleware from "../kredux/applyMiddleware";
const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case "ADD":
return state + payload;
case "MINUS":
return state - payload;
default:
return state;
}
};
export default createStore(counterReducer, applyMiddleware(thunk, logger));
并将 add 函数更改成异步触发 dispatch
add = () => {
store.dispatch(function (dispatch) {
setTimeout(() => {
dispatch({ type: "ADD", payload: 2 });
}, 1000);
});
};

五、手写 combineReducers
当业务逻辑复杂时,不可能都写在一个 reducer 里,这时就得使用 combineReducers 将几个 reducer 组合起来。
再添加个 userReducer:
const userReducer = (state = { ...initialUser }, { type, payload }) => {
switch (type) {
case "SET":
return { ...state, ...payload };
default:
return state;
}
};
引入 combineReducers ,该函数接收个对象,key 为标识,value 为每个 reducer
export default createStore(
combineReducers({ count: counterReducer, user: userReducer }),
applyMiddleware(thunk, logger)
);
根据上面的分析,手写个 combineReducers ,它要返回个 reducer 函数,reducer 函数自然是要接收 state 跟 action 并返回新的 state
export default function combineReducers(reducers) {
return function reducer(state = {}, action) {
let nextState = {};
for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
};
}
六、总结
- Redux 本身就只是个可预测性状态容器,状态的更改都是通过发出 Action 指令,Action 指令会分发给 Reducer ,再由 Reducer 返回新的状态值,组件进行监听,但状态值更改了,就重新渲染。
- Redux 是典型的观察者模式,subscribe 时注册回调事件,dispatch action 时执行回调
- Redux 重点在于 createStore 函数,该函数会返回三个方法,其中 getState 用于返回当前的 state,subscribe 用于订阅 state 的更改,dispatch 更新 store,并执行回调事件
- 默认的 Redux 只支持传入对象,以及只能执行同步,要满足更多的场景就需要采用中间件
- 中间件是个模板为
export default store => next => action => {} 的函数 - 注册中间件,就要使用 createStore 的 enhancer
- Redux 的中间件是对 dispatch 进行加强,这就是典型的装饰者模式,可以看出 Redux 里到处是设计模式
|