redux上手
Redux是一个状态管理器
redux工作流程
- 创建一个
Store来存储数据
使用 createStore 创建一个 Store 来存放应用中的 state,应用中有且仅有一个 Store
import { createStore } from "redux";
// 创建一个reducer,定义state中的修改规则
const countReducer = function(count = 0, action) {
switch(action.type) {
case 'ADD':
return count + 1;
case 'MINUS':
return count - 1;
default:
return count;
}
}
// 创建一个store来存储state
const store = createStore(countReducer);
export default store;
-
Store里的Reducer初始化State并定义State修改规则Reducer是一个纯函数,它接受Action和当前的state作为参数,返回一个新的state。在创建Store时,将Reducer当做参数传递给createStore, 如果state的结构比较复杂,可以考虑将多个Reducer进行拆分,再使用combineReducer合成。
import { combineReducers, createStore } from "redux";
// reducer 纯函数
const countReducer = function (count = 0, action) {
console.log("action", action);
switch (action.type) {
case "ADD":
return count + 1;
case "MINUS":
return count - 1;
default:
return count;
}
};
const userReducer = function (name = "焦糖瓜子", action) {
switch (action.type) {
case "ENGLISH":
return `ENGLISH:${name}`;
case "CHINESE":
return `中文:${name}`;
default:
return "无名氏";
}
};
// 可以给combineReducers中的reducer进行重命名
const store = createStore(combineReducers({userReducer, countReducer}));
export default store;
- 用户通过
dispatch一个Action提交数据的修改 单独使用dispatch提交Action触发state数据修改,无法触发视图更新。
import { Component } from "react";
import store from "./store";
export default class ReduxPage extends Component {
componentDidMount() {
// store的变化无法实时触发render进行视图渲染,需要使用subscribe监听state的变化,强制触发更新
store.subscribe(() => {
this.forceUpdate();
})
}
handleAdd = () => {
// 通过dispatch提交action:{ type: 'ADD' }
store.dispatch({ type: 'ADD' });
}
render() {
return (
<div>
ReduxPage上手界面, redux缓存的数据: {store.getState()} <br />
<button onClick={this.handleAdd}>增加</button>
<button onClick={this.handleMinus}>减少</button>
</div>
);
}
}
Store自动调用Reducer, 并将当前State和Action传给Reducer,Reducer根据传入的Action的type,返回新的State
// reducer根据action提交过来的type,返回新的state
const countReducer = function (count = 0, action) {
console.log("action", action);
switch (action.type) {
case "ADD":
return count + 1;
case "MINUS":
return count - 1;
default:
return count;
}
};
手写redux
createStore
createStore(reducer, enhancer)
作用: 创建一个store来存放应用中的所有数据。
参数:
reducer(Function): 纯函数enhancer(Function):Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator
返回值: (store)保存所有应用的state对象。store中有几个方法: getState(),dispatch(action),subscribe(listener),replaceReducer(nextReducer) 各方法的使用参考 官方文档
/**
* 创建一个store来存放应用中的所有数据
* @param {Function} reducer 一个纯函数
* @returns 返回保存所有state的对象:包含 getState、dispatch、subscribe方法
*/
export default function createStore(reducer) {
let currentState;
let currentListeners =[]; // 用于保存所有的listener
/**
* 分发action。这是触发state变化的唯一途径
* @returns 返回当前的state
*/
const getState = () => {
return currentState;
}
/**
* 分发action。这是触发state变化的唯一途径
* 使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数
* @param {Object} action 描述应用变化的普通函数
* @returns 要dispatch的action
*/
const dispatch = (action) => {
currentState = reducer(getState(), action);
currentListeners.map(listener => listener());
return action;
}
// 执行一次dispatch, 初始化reducer的数据, 返回reducer的default数据
dispatch({ type: 'redux/source' });
const subscribe = (listener) => {
if (typeof listener !== 'function') {
throw new Error('listener必须是一个函数!');
}
// 将所有的监听函数存入currentListener中
currentListeners.push(listener);
return () => {
currentListeners = [];
}
}
return {
getState,
dispatch,
subscribe
}
}
combineReducer
随着应用变得越来越复杂,可以将 reducer函数 拆分成多个单独的函数(即多个reducer), 拆分后每个reducer管理 state 的一部分。
combineReducer: 接收由多个 reducer 组合而成的对象,返回一个新的 reducer, 每个 reducer 管理state中属于自己的一部分
/**
* 把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数
* 然后就可以对这个 reducer 调用 createStore 方法
* @param {Object} reducers 接收由多个reducer组合而成的对象
* @returns 返回一个由reducer组合的reducer
*/
export default function combineReducers(reducers) {
if (Object.prototype.toString.call(reducers) !== '[object Object]') {
throw new Error('combineReducers要求传入一个由reducer组合而成的对象');
}
// 返回一个reducer: 参数state和action
return (state = {}, action) => {
// reducer 返回一个新的state, 最新的newState通过执行reducers中的reducer得来
let newState = {};
// 执行所有的reducer获取最新的数据,更新state
for (let key in reducers) {
let reducer = reducers[key];
newState[key] = reducer(state[key], action);
}
return newState;
}
}
applyMiddleware
applyMiddleware的作用是增强 dispatch 功能,方便外部自定义功能的 middleware 来扩展
第一版 实现日志的记录
1、要求在用户提交action时,输出日志
// loggerMiddleware.js
// 加强dispatch的同时,在函数内部要执行 dispatch的基础功能
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = action => {
console.log(`开始执行${action.type}`);
next(action);
console.log(`执行完毕${action.type}`);
}
第二版 记录异常
又有一个需求,需要记录每次数据出错的原因,我们扩展下dispatch
// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;
// 在记录日志的基础上记录异常
store.dispatch = action => {
try{
console.log('store的state树', store.getState());
next(action);
} catch(e) {
console.log(e);
}
}
多功能中间件的合作
如果最新需求要求: 即记录日志也记录异常,输出当前state树。需要将两个函数进行合并
// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = action => {
try {
console.log('store的state树', store.getState());
// 记录日志
console.log(`开始执行${action.type}`);
next(action);
console.log(`执行完毕${action.type}`);
} catch(e){
console.log('e', e);
}
}
如果继续来新的需求,每次都要手动改dispatch的扩展。所以我们需要考虑下实现扩展性很强的多中间件合作模式
1、把记录日志的 loggerMiddleware 进行提取
// loggerMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = action => {
console.log(`开始执行${action.type}`);
next(action);
console.log(`执行完毕${action.type}`);
}
store.dispatch = loggerMiddleware;
2、将记录异常的 errorMiddleware 进行提取,正常流程记录store中的数据
// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;
const errorMiddleware = action => {
try {
console.log('store的state树', store.getState());
loggerMiddleware(action);
}
console.log('e', e);
}
store.dispatch = errorMiddleware;
3、目前存在的问题: errorMiddleware 里的执行函数 next 写死了 loggerMiddleware, loggerMiddleware 中的执行函数写死了 next 为 store.dispatch , 都进行了强关联, 耦合性过高。我们的需求是 应该任意中间件都可以糅合使用
const store = createStore(reducer);
const next = store.dispatch
// loggerMiddleware 需要接收一个next,进行升阶操作
const loggerMiddleware = next => action => {
console.log(`开始执行${action.type}`);
next(action);
console.log(`执行完毕${action.type}`);
}
const errorMiddleware = next => action => {
try {
console.log('store的state树', store.getState());
next(action);
} catch(e) {
console.log('e', e);
}
}
const store = createStore(reducer);
store.dispatch = errorMiddleware(loggerMiddleware(next))
4、将中间件进行模块化划分
不同的中间件放入不同的文件中。当前loggerMiddleware和errorMiddleware中间件依然强关联了 store, 继续升阶,将 store 作为参数传入
// loggerMiddleware.js
// loggerMiddleware 需要再接收一个store,进行再次升阶操作
export default function loggerMiddleware = store => next => action => {
console.log(`开始执行${action.type}`);
next(action);
console.log(`执行完毕${action.type}`);
}
// errorMiddleware.js
export default function errorMiddleware = store => next => action => {
try {
console.log('store的state树', store.getState());
// 当前顺序 errorMiddleware中的next为loggerMiddleware
next(action);
} catch(e) {
console.log('e', e);
}
}
// store.js
const store = createStore(reducer);
const next = store.dispatch;
const newErrorMiddleWare = errorMiddleWare(store);
const newLoggerMiddleWare = loggerMiddleWare(store);
store.dispatch = newErrorMiddleWare(newLoggerMiddleWare(next));
中间件使用方式优化
根据上节实现的中间件,但是中间件的使用方式不友好,当前需要将中间件的细节进行封装。实际上我们不需要知道中间件的具体细节,只需要知道这有中间件就可以了。我们可以将细节进行封装,通过扩展createStore来实现。
其中store.dispatch = errorMiddleware(loggerMiddleware(next))中间件合并方式,我们可以通过一个compose函数来实现。
// store.js
const store = createStore(reducer);
const next = store.dispatch;
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
store.dispatch = compose(errorMiddleware(store), loggerMiddleware(store))(next);
目前,虽然使用了compose封装了中间件,但是store.dispatch等细节依然暴露在外面,我们可以继续封装升级createStore的功能。
实现 applyMiddleware:接收中间件 返回一个存放数据的store
// applyMiddleware.js
/**
* 加强dispatch
* @param {...any} middlewares 中间件
*/
export default function applyMiddleware(...middlewares) {
/**
*
* @param {...any} funcs 中间件
* @returns 将中间件组合起来,返回一个新的函数
*/
function compose(...funcs) {
console.log("funcs", funcs);
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
return store => {
// 增强store中的dispatch
let dispatch = store.dispatch;
// 中间件的参数
let params = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
// 将参数传入每个middleware
let middlewaresChain = middlewares.map((middleware) => middleware(params));
// 重写dispatch
dispatch = compose(...middlewaresChain)(dispatch);
return {
...store,
dispatch,
};
};
}
// store.js
const store = createStore(reducer);
const stongerStore = applyMiddlewares(errorMiddleware, loggerMiddleware)(store);
现在优化有的 applyMiddleware 依然存在问题,这将导致创建store时需要同步创建两个store,为了统一,简化使用方法, 我们可以通过修改createStore将applyMiddleware作为参数来进行增强createStore的功能
// createStore.js
/**
* 创建一个store来存放应用中的所有数据
* @param {Function} reducer 一个纯函数
* @returns 返回保存所有state的对象:包含 getState、dispatch、subscribe方法
*/
export default function createStore(reducer, enhancer) {
let currentState;
let currentListeners =[]; // 用于保存所有的listener
if (enhancer) {
return enhancer(createStore)(reducer);
}
/**
* 分发action。这是触发state变化的唯一途径
* @returns 返回当前的state
*/
const getState = () => {
return currentState;
}
/**
* 分发action。这是触发state变化的唯一途径
* 使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数
* @param {Object} action 描述应用变化的普通函数
* @returns 要dispatch的action
*/
const dispatch = (action) => {
currentState = reducer(getState(), action);
currentListeners.map(listener => listener());
return action;
}
// 执行一次dispatch, 初始化reducer的数据, 返回reducer的default数据
dispatch({ type: 'redux/source' });
const subscribe = (listener) => {
if (typeof listener !== 'function') {
throw new Error('listener必须是一个函数!');
}
// 将所有的监听函数存入currentListener中
currentListeners.push(listener);
return () => {
currentListeners = [];
}
}
return {
getState,
dispatch,
subscribe
}
}
同时我们需要修改一些applyMiddleware的接收参数
// applyMiddleware.js
/**
* 加强dispatch
* @param {...any} middlewares 中间件
*/
export default function applyMiddleware(...middlewares) {
/**
*
* @param {...any} funcs 中间件
* @returns 将中间件组合起来,返回一个新的函数
*/
function compose(...funcs) {
console.log("funcs", funcs);
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
return (createStore) => (reducer) => {
// 增强store中的dispatch
const store = createStore(reducer);
let dispatch = store.dispatch;
// 中间件的参数
let params = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
// 将参数传入每个middleware
let middlewaresChain = middlewares.map((middleware) => middleware(params));
// 重写dispatch
dispatch = compose(...middlewaresChain)(dispatch);
return {
...store,
dispatch,
};
};
}
将applyMiddleWare的增强放入createStore中后,在创建store时与原先一样,直接只需要执行createStore生成一个即可
// store.js
const store = createStore(reducer, applyMiddleWare(errorMiddleWare, toggerMiddleWare));
bindActionCreator
bindActionCreator 主要作用就是将 dispatch 和 action creator隐藏起来,把 action creator 绑定到 dispatch 上,以便外部直接调用。
很少使用到,使用场景比较少;在react-redux中有使用
/**
* 把一个value为不同action creator的对象,转成拥有同名key的对象。
* 同时使用dispatch对每个action creator 进行包装,以便可以直接调用它们
*
* actionCreators也可以是数组,但是对于使用不是很友好
* @param {Function | Object} actionCreators
* @param {Function} dispatch
*/
function bindActionCreator (actionCreator, dispatch) {
// 返回通过dispatch增强的actionCreator
// args: action creator传入的参数
return (...args) => dispatch(actionCreator(...args));
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error('actionCreators必须为Function或者Object');
}
let boundActionCreators = {};
for(let key in actionCreators) {
let actionCreator = actionCreators[key];
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators;
}