redux实现
redux是用来统一做状态管理的:
- 所有的状态变化都要用明确的指令触发:dispatch
- 执行状态变化的方法统一放在一个函数中处理:reducer
- 可以订阅状态的变化,当状态变化的时候执行回调函数:subscribe
一般使用redux的方式如下:
// store.js
// 1、从redux中引入createStore
import {createStore} from 'redux';
// 2、创建一个reducer函数,用来处理状态的变化逻辑
function countReducer(state=0, action) {
if (action.type === 'ADD') {
return state + 1
}
// 处理其它type的分支
// ...
}
// 3、调用createStore初始化store实例,并将reducer函数作为参数传进去
const store = createStore(countReducer)
export default store;
然后我们就可以在我们的文件中引入store来使用了
import React, {Component} from 'react';
import store from '../store';
export default class ReduxPage extends Component {
componentDidMount() {
// 订阅,当state变化的时候,重新触发执行render
store.subscribe(() => {
this.forceUpdate();
});
}
add = () => {
// 告诉store要执行ADD行为
store.dispatch({type: 'ADD'})
}
render () {
console.log("store", store); //sy-log
return (
<div>
{/* 通过 getState获取最新的state*/}
<div>count:{store.getState()}</div>
{/* 点击的时候触发dispatch方法 */}
<button onClick={this.add}>add</button>
</div>
)
}
}
以上的小例子介绍了redux的简单使用方法,下面来自己动手实现一个简单的redux:
// myRedux.js
export function createStore(reducer) {
let currentState; // 保存当前的状态
let listeners = []; // 收集订阅函数
function dispatch (action) {
currentState = reducer(currentState, action); // 执行dispatch的时候根据reducer获取更新后的state
listeners.forEach(i => i()); // 执行订阅函数
}
// getState直接将最新的state返回就可以了
function getState () {
return currentState;
}
// subscribe将订阅函数收集到listeners里面
function subscribe (listener) {
listeners.push(listener);
}
// 手动触发一次dispatch,让currentState获取到初始的默认值
// 这个type取一个任意的值就行,不要与reducer里的其它type重复就可以
dispatch({type: "@INIT/ANY VALUE"});
return {
dispatch,
getState,
subscribe
}
}
这就实现了一个redux的简单版本了,其实它的原理真的很简单,就是派发一个action,然后让一个函数接收当前的state和派发的action作为参数,执行一下返回新的state就可以了。
中间件实现
redux的原理很简单,实现的功能也很简单,就是通过派发action,然后调用reducer函数来返回新的state。当时当我们要实现一些更复杂的功能的时候,单纯的redux就有点不够用了,所以需要引入中间件。
中间件的功能就是对redux进行增强,在原先redux的基础上再做一些额外的事情。其实现原理就是对dispatch方法进行封装,实现加强版本的dispatch。
例如我们原先执行store.dispatch({type: 'ADD'}),那么就会直接调用reducer方法的ADD的逻辑。而中间件就是对原有的dispatch进行封装,当我们在调用dispatch方法的时候,先处理一些中间件里的功能,做完中间件里的功能之后再触发真正的dispatch。
常用的中间件有redux-logger、redux-thunk,通过这两个中间件来讲解一下中间件的基本原理。
打印日志:
// redux-logger
// 在执行dispatch的时候打印日志
// 保存原始的dispatch
let originDispatch = store.dispatch;
// 对dispatch进行加强,加入打印日志功能
store.dispatch = function(action) {
console.log('这里打印日志'); // 这里除了打印日志也可以做其它的功能
// 最终还是执行原来的dispatch
originDispatch(action);
}
// 异步请求
let originDispatch = store.dispatch;
store.dispatch = function(action) {
model.getData()
.then((data) => {
action.data = data;
originDispatch(action); // 异步请求到数据后再触发原先的dispatch
})
}
从上面可以看出,其实中间件的最终原理就是对dispatch进行包装,先处理中间件的逻辑,处理完之后再触发真正的dispatch。
下面我们要开始动手实现真正的中间件,先来看下中间件是如何使用的:
import {createStore, applyMiddleware} from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";
// 创建一个reducer函数,用来处理状态的变化逻辑
function countReducer(state=0, action) {
if (action.type === 'ADD') {
return state + 1
}
// 处理其它type的分支
// ...
}
// 重点:applyMiddleware
// 在createStore的时候,第二参数为applyMiddleware,applyMiddleware的参数为我们要使用的中间件
// 当我们使用store.dispatch的时候,会先经过thunk和logger中间件,然后再执行真正的dispatch
const store = createStore(countReducer, applyMiddleware(thunk, logger));
createStore处理第二个参数
因为现在createStore可以接收applyMiddleware作为第二个参数,我们对上面的createStore函数进行下处理,适配第二个参数:
// myRedux.js
export function createStore(reducer, enhancer) {
// 关键代码,处理第二个参数
// enhancer就是applyMiddleware(thunk, logger),意思就是对store进行加强
// enhancer接收createStore作为参数,因为要对createStore进行加强,所以必须把原先的createStore传进去
// 当然reducer也要作为参数传过去
// 调用enhancer(createStore)(reducer)的时候,会在函数内部执行createStore(reducer)获得原始的store
// 然后通过store.dispatch获得原始的dispatch,再调用中间件对dispatch进行各种加强
// 这里的确有点绕啊,enhancer是作为createStore的参数的,然后又把createStore作为enhancer的参数
// 最终调用没有enhancer参数的createStore,即createStore(reducer)来获取store,再对这个store里的dispatch进行加强
// 真的有点骚,具体这里如何实现的看下下文的applyMiddleware实现
if (enhancer) {
return enhancer(createStore)(reducer);
}
// 下面的功能是上面文章实现的redux,可以不看...
let currentState; // 保存当前的状态
let listeners = []; // 收集订阅函数
function dispatch (action) {
currentState = reducer(currentState, action); // 执行dispatch的时候根据reducer获取更新后的state
listeners.forEach(i => i()); // 执行订阅函数
}
// getState直接将最新的state返回就可以了
function getState () {
return currentState;
}
// subscribe将订阅函数收集到listeners里面
function subscribe (listener) {
listeners.push(listener);
}
// 手动触发一次dispatch,让currentState获取到初始的默认值
// 这个type取一个任意的值就行,不要与reducer里的其它type重复就可以
dispatch({type: "@INIT/ANY VALUE"});
return {
dispatch,
getState,
subscribe
}
}
applyMiddleware实现
从上面可以看到,我们调用中间件的方式是这样的
const store = createStore(countReducer, applyMiddleware(thunk, logger));
createStore是这样的
function createStore(reducer, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer);
}
...
}
所以执行applyMiddleware返回的是一个函数,接收createStore作为参数,然后执行enhancer(createStore)又返回一个函数,参数是reducer。我们先实现一个applyMiddleware,这个applyMiddleware先不做任何加强,就是让原先的逻辑可以顺利执行,如下:
function applyMiddleware(...middlewares) {
return createStore => (reducer) => {
// 得到原始的store,先不做任何加强,直接把该store返回回去
// 相当于没有使用中间件
const store = createStore(reducer);
return {
...store,
};
}
从上面代码可以看出,applyMiddleware内部还是调用原始的createStore(reducer)来获取store,把该store直接返回回去,就可以让原有的功能顺利执行,只是现在store.dispatch还没有加强,那么如何加强呢?就是要通过中间件,其基本原理如下:
function applyMiddleware(...middlewares) {
return createStore => (reducer) => {
const store = createStore(reducer);
let dispatch = store.dispatch;
dispatch = function () {
// 使用middlewares对dispatch进行处理,然后返回加强版本的dispatch
// ...
return enhancerDispatch
}
return {
...store,
// 用加强版本的dispatch覆盖store里原始的dispatch
dispatch
};
}
所以现在要做的就是如何使用middlewares对原始的dispatch进行加强。
中间件是一个函数,接收dispatch作为参数,然后返回一个新的dispatch。 例如logger中间件:
function logger(dispatch) {
function enhancerDispatch = action => {
console.log(action.type + "执行了");
return dispatch(action);
};
return enhancerDispatch
}
如何把中间件串起来,因为中间件执行后返回的是一个加强版本的新的dispatch,我们只需要把这个新的dispatch作为参数传给下一个中间件就可以了
比如我们有3个中间,middleware1、middleware2、middleware3。
我们如何得到经过3个中间件加强的dispatch呢,只需要像下面这样做就可以了:
let finallyDispatch = middleware3(middleware2(middleware1(dispatch)))
但是像上面这么写有点不那么优雅,所以我们写一个compose函数来将所有的中间件串联成一个函数:
let finallyDispatch = compose(middleware1,middleware2,middleware3)
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
// return () => {};
}
if (funcs.length === 1) {
return funcs[0];
}
// 重点,这一行代码可能需要好好消化
return funcs.reduce((a, b) => (dispatch) => a(b(dispatch)));
}
所以我们现在实现的applyMiddleware是这样的:
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
dispatch = compose(...middlewares)(dispatch);
return {
...store,
// 覆盖上面store里的dispatch
dispatch
};
};
}
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
// return () => {};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
然后我们来实现两个中间件,redux-logger和redux-thunk
// redux-logger
function logger(dispatch) {
function enhancerDispatch = action => {
console.log(action.type + "执行了");
return dispatch(action);
};
return enhancerDispatch
}
// redux-thunk
// 支持action里面传函数
// 如果action是函数,则执行action并将dispatch作为参数传进去
// dispatch可以在action函数内以异步的方式触发
function thunk(dispatch) {
function enhancerDispatch = action => {
// action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
if (typeof action === "function") {
return action(dispatch);
} else {
return dispatch(action);
}
};
return enhancerDispatch
}
以上就是中间件的实现原理
上面我们的中间件函数只接收一个dispatch作为参数,然而中间件可能需要其它参数,所以我们需要在中间件外再套一层函数,以上面的thunk函数为例,如果调用action函数的时候需要传getState方法,那么getState需要作为参数传进来,如下:
function thunk ({getState}) {
// 原先的thunk,现在变成了originThunk
function originThunk(dispatch) {
function enhancerDispatch = action => {
// action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
if (typeof action === "function") {
return action(dispatch);
} else {
return dispatch(action);
}
};
return enhancerDispatch
}
return originThunk
}
那现在thunk什么时候执行,以及getState参数怎么传进来呢?我们得再改造一下我们的applyMiddleware方法,看下面的新增方法就可以了:
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
// ========= 新增代码 ==========
// 需要传给中间件函数的参数
const middleApi = {
getState: store.getState,
dispatch
};
// 给middleware参数,比如说dispatch
// 把所有中间件都先执行一遍,然后将返回的函数作为中间件
// 根据闭包原理,那么返回的函数也可以访问其外层函数的作用域
const middlewaresChain = middlewares.map(middleware =>
middleware(middleApi)
);
// ========= 新增代码 ==========
dispatch = compose(...middlewaresChain)(dispatch);
return {
...store,
// 覆盖上面store里的dispatch
dispatch
};
};
}
从上面的代码可以看出,这里就是使用了闭包的作用域原理,在原先的中奖件函数(f1)外又套了一层(f2),我们执行f2,然后返回f1,那么在执行f1的时候,就可以访问f2的作用域了。
看下最终我们的中间件函数该怎么写:
// reudx-logger
function logger({getState}) {
return dispatch => action => {
console.log(action.type + "执行了"); //sy-log
return dispatch(action);
};
}
// redux-thunk
function thunk({getState}) {
return dispatch => action => {
if (typeof action === "function") {
return action(dispatch, getState);
} else {
return dispatch(action);
}
};
}
applyMiddleware最终代码
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
const middleApi = {
getState: store.getState,
dispatch
};
// 给middleware参数,比如说dispatch
const middlewaresChain = middlewares.map(middleware =>
middleware(middleApi)
);
dispatch = compose(...middlewaresChain)(dispatch);
return {
...store,
// 覆盖上面store里的dispatch
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)));
}