前言
笔者读过几个前端框架,比如redux,react-redux,react-router,react等等,个人认为熟悉后能自己手敲出一份迷你版的demo,也许领悟会更深一点。笔者的学习方法是,先弄清核心逻辑,然后再去看衍生的判断,边缘处理,就会好理解很多
另外:本次分享不会把 redux 全部源码搬出来,不会去赘述遇到的知识点,只手写里面的核心逻辑,以及分享自己看源码的一些感想
目录页结构如下
首先是创建一个演示页面 Redux_page.js ,这个页面比较简单,就是一个简单的计算,通过 redux 来修改 state的值
import React, { Component } from "react";
import store from './store/index'
export default class Redux_page extends Component{
constructor(props){
super(props);
}
componentDidMount(){
this.unsubscribe= store.subscribe(()=>{
this.forceUpdate()
})
}
componentWillUnmount(){
this.unsubscribe() // 卸载监听
}
add =()=>{
store.dispatch({ type:'ADD',payload:1 })
}
asyncAdd =()=>{
store.dispatch((dispatch,getState)=>{
setTimeout(()=>{
dispatch({ type:'ADD',payload:1 })
},1000);
})
}
render(){
return(
<div>
<br></br><br></br>
<button onClick={this.add}>增加</button>
<br></br>
<button onClick={this.asyncAdd}> async 增加</button>
<div>
当前的 count 的值为 { store.getState() }
</div>
</div>
)
}
}
然后是reducer的创建和store的创建页面,笔者出于方便,就将2个结合一起写了,统一放入store下的index.js,如下
import { createStore, applyMiddleware } from '../redux/index';
export const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case 'ADD': return state + payload;
case 'MIN': return state - payload;
default: return state;
}
}
const store = createStore(counterReducer, applyMiddleware(thunk));
export default store;
通过上面2个页面,铺垫了基本的用法,接下来就开始敲核心逻辑。
笔者建议,从用法倒推源码设计
首先从Redux_page来看,store 里面至少得有3个方法,分别是 getState(),dispatch和subscribe,getState用于获取最新的state值,dipatch接受一个action,然后改变state,subscribe则是订阅更新。 代码如下
export default function createStore(reducer,enhancer) {
if(enhancer){
return enhancer(createStore)(reducer)
}
let currentState ;
let currentListeners = []; //收集监听
function getState() {
return currentState;
}
function dispatch(action) {
currentState = reducer(currentState,action);
currentListeners.forEach((x)=>x())
}
function subscribe(listener) {
currentListeners.push(listener);
return ()=>{
const index = currentListeners.indexOf(listener);
currentListeners.splice(index,1);
}
}
dispatch({type:'xxxxxxxxxxxxxxxxxxxxxxx'})
return {
getState,
dispatch,
subscribe
}
}
先不看中间件 enhancer 的代码,笔者首次读源码的时候,有2处是看不懂的,第一处是 dispatch 里面为什么要去执行监听?第二处是为什么源码里面会去执行一个不相关的 dispatch({ type: xxxxxxx });
第一个问题是跟执行逻辑有关,从当前例子来解释,我们肯定期待dispatch之后会返回后一个新的state,并且页面能立刻展示新的state,所以,我们在页面初始化的时候,肯定要设立一个监听,当redux里面的state改变的时候,要触发页面强制更新。这就解释了为什么页面有subscribe和unsubscribe
componentDidMount(){
this.unsubscribe= store.subscribe(()=>{
this.forceUpdate()
})
}
componentWillUnmount(){
this.unsubscribe() // 卸载监听
}
而在源码中做一个dipatch的动作,是为了获取初始化的state,这里笔者举个栗子,如果没有这句代码,那么初始化的currentState则恒是 undefined,可是,实际用法却是我们会传一个初始值用于页面初次渲染,因此需要执行一个 dispatch 来获取默认的state。
还有一个重点是,这个 dispatch 的 type 并不能随意乱写,要确保能避开所有 reducer 的判断,到这里大概小伙伴会有疑问,redux的开发者怎么就能确定自己这个type不会被访问到呢 ?天下那么多程序员写reducer,难道没有万一的情况 ?eum~~ 这里笔者不太想解释,直接把redux源码搬过来 (如果 type 这都能撞上,那运气浪费在这就可惜了,还是去尝试买买彩票吧········)
看完了 store 的3个接口,接下来看看redux是怎么实现中间件的,接下来会涉及两个有意思的知识点,分别是柯里化和聚合函数,笔者建议先明白这2个的概念和使用,对接下来的分享会有帮助。
首先,redux为什么需要需要中间件?或者说,redux为什么会存在中间件这样的说法?
这个目前为止,很多贴吧,很多分享,都是基于使用,然后回答这个问题,既dispatch默认派发的是一个action对象,而实际情况则可能是为了处理异步,亦或是方便,都需要对action做一个function的校验处理。
笔者认为,从设计角度来看,redux最大的特点就是“纯”,它的“纯”保证了state是干净的,可预测的,而这样的设计就奠定了他自身不能去处理任何携带副作用的函数,redux想满足复杂的实际情况,就必须依赖于某某设计(某某算法),我们称之为为中间件。
中间件最终的体现,就是一个加强版的 dispatch
applyMiddleware.js,如下
export default function applyMiddleware(...middlewares){
return (createStore)=>(reducer)=>{
const store = createStore(reducer);
let dispatch = store.dispatch;
const midApi ={
getState:store.getState,
dispatch:(action,...args)=>dispatch(action,...args)
}
const middleWareChain = middlewares.map((middleware=>middleware(midApi)));
let com_res = compose(...middleWareChain);
dispatch = com_res(store.dispatch);
return {...store,dispatch}
}
}
聚合函数 conpose 如下(跟源码一致)
function compose(...funcs) {
if (funcs.length === 0) {
return (args) => args;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
redux说到底就是一个state管理库,那无非就是 getState 和 setState 两种操作,setState则是我们常说的dispatch,因此只要把getState和dispatch传给中间件,中间件便具备了访问 store 的权限(细心的小伙伴甚至能从这里倒推中间件的源码)。
到此笔者关于redux的分享也就结束了,任何一个看懂源码的秃头猿,肯定也想对源码指点江山啥的,笔者也不例外,比如自己写一个中间件能拦截到很多信息,感觉自己能操作的东西更多了,这也许就是看完源码比较有成就感的一面
const store = createStore(counterReducer, applyMiddleware(thunk,showInfo));
function showInfo({ getState, dispatch }) {
return next => action => {
console.log('pre state',getState());
let returnV= next(action);
console.log('new state',getState());
return returnV;
}
}