💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
*本文为公司前端团队内部分享的文案,是在网上剽窃、参考、学习了诸多文章和代码后,加上自己的理解而成,如果对你有所帮助,不生荣幸。 最终代码在github,地址:[https://github.com/fujiazhang/Redux](https://github.com/fujiazhang/Redux)* 正文: 因为一开始我是做vue技术栈,刚接触react的时候,对于redux有很多不理解的地方,因为vuex封装的很好,而react-redux就很简陋,对于redux也有很多不理解的地方,但是照着文档写也ok,但是总归理解不深入,借着这次分享的机会,参考很多优秀的文章代码,总算是搞懂了redux, 当然本次分享是是redux,不是react-redux,。 首先, redux是一种架构,他和react没有任何关系,可以用在任何东西,像是什么 connect之类是属于react-redux,ok, 现在请忘掉reducer、store、dispatch、middleware这些单词,我们一步步来推导。 ## 一、redux的本质就是:就是使用store来管理state的数据管理器 我们首先定义一个状态管理器: ``` let state = { name: 'zhangsan' } ``` ok, 接下来我们来修改和使用它 ``` console.log(state.name) ``` now, 我们现在有了一个状态管理器了,现在一步步来完善这个状态管理器。 ok,我们现在面临第一个问题,我们没办法在修改的时候,知道我们的状态被修改了,比如我页面上有地方引用了这个数据,但是被修改了,却不知道(无法更新同步view),这里使用订阅/发布模式来解决这个修改了数据需要被通知(知道被修改了)的这个问题。 ``` let state = { name: 'zhangsan' } //保存订阅者数据集合 const liesteners = [] //订阅 function subscribe(liestener) { liesteners.push(liestener) } function changeState(newState) { state = newState //发布 liesteners.forEach(f = >f()) } ``` ok,代码如上,我们来使用下 ``` subscribe(() = >{ console.log('state is changed', state) }) //更改状态 changeState({ name: 'lisi' }) ``` 现在我们可以看到,我们修改 name 的时候,会输出相应的 state值, 解决了当数据更改时,无法知道修改(或者说收到通知)这个问题,现在我们有新的问题: * 这个状态管理器只能管理我这一个对象,不通用 * 公共的代码要封装起来 我们来抽象一下: ``` function createStore(initSate) { let state = initSate const liesteners = [] //订阅集合 function changeState(newState) { state = newState /*发布*/ liesteners.forEach(f = >f()) } /*订阅*/ function subscribe(f) { liesteners.push(f) } function getStore() { return state } return { getStore, changeState, subscribe } } ``` ok, 我们来试试: ``` store.subscribe(()=> { console.log('state is changed:', store.getStore()) }) store.changeState({ name: 'fujiazhang' }) store.changeState({ name: 'guolei' }) store.changeState({ name: 'yuanying' }) ``` 输入如下: ![](images/screenshot_1596460285185.png) 嗯,不错,我们来试试来使用这个状态管理器管理多个状态 ``` const store = createStore(initSate) store.subscribe(() = >{ console.log('state is changed:', store.getStore()) }) store.changeState({...store.getStore(), no1: 'zhangsan' }) store.changeState({...store.getStore(), no2: 'lisi' }) store.changeState({...store.getStore(), no3: 'wangwu' }) ``` 运行输出结果如下: ![](images/screenshot_1596460536321.png) 嗯,很爽,一切如我们想象的一致。 但是,但是,我们发现有一个严重的问题,我的传入修改的对象是任何值,随意传,想传啥,这肯定是不是我们所期望的,我们期望的肯定是,有固定的值,只允许,我们的nox 为传入的值,我们可以这样来解决: 1. 制定一个 state 修改计划,告诉 store,我的修改计划是什么。 2. 修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。 我们来设置一个 plan 函数,接收现在的 state,和一个 action,返回经过改变后的新的 state。 ``` function plan(state, action) { switch (action.type) { case 'CHANGE_NO_1': return {...state, no1: action.data } break; default: return state break; } } ``` 我们把这个计划告诉 store,store.changeState 以后改变 state 要按照我的计划来改。 现在的整体代码如下: ``` function createStore(initSate) { let state = initSate const liesteners = [] function changeState(action) { state = plan(state, action) liesteners.forEach(f = >f()) } function subscribe(f) { liesteners.push(f) } function getStore() { return state } return { getStore, changeState, subscribe } } function plan(state, action) { switch (action.type) { case 'CHANGE_NO_1': return {...state, no1: action.data } break; default: return state break; } } const initSate = { no1: '', no2: '', no3: '' } const store = createStore(initSate) store.subscribe(() = >{ console.log('state is changed:', store.getStore()) }) store.changeState({ type: 'CHANGE_NO_1', data: 'zhangsan' }) ``` 我们来运行一波,结果如下: ![](images/screenshot_1596511272746.png) ok, 结果很理想,接下来我们把名字替换一下,到这里为止,我们已经实现了一个有计划的状态管理器!现在呢给 plan 和 changeState 改下名字好不好? plan 改成 reducer,changeState 改成 dispatch! ,嗯,和redux一致了。 现在的代码如下: ``` function createStore(initSate) { let state = initSate const liesteners = [] function dispatch(action) { state = reducer(state, action) liesteners.forEach(f = >f()) } function subscribe(f) { liesteners.push(f) } function getStore() { return state } return { getStore, dispatch, subscribe } } function reducer(state, action) { switch (action.type) { case 'CHANGE_NO_1': return {...state, no1: action.data } break; default: return state break; } } const initSate = { no1: '', no2: '', no3: '' } const store = createStore(reducer, initSate) store.subscribe(() = >{ console.log('state is changed:', store.getStore()) }) store.dispatch({ type: 'CHANGE_NO_1', data: 'zhangsan' }) ``` ok, 到了这里,我们先把代码分模块拆开,现在一股脑的在一个index.js文件里,看着也累。组件化拆分后目录如下: ![](images/screenshot_1596512459047.png) 我们继续,到这里,其实已经有问题了,因为我们不可能把所有的计划函数( reducer)写在一个文件里,我们肯定是期望一个组建一个reducer, 比如支付组件一个reducer, 登陆组件一个reducer,随着业务的累计,肯定会有大大大大量的reducer 所以,我们需要按组件来拆分出很多个 reducer 函数,然后通过一个函数来把他们合并起来。 现在我们来模拟修改一个state里的 两个数据。 reducer1: ``` export function no1Reducer(state, action) { switch (action.type) { case 'CHANGE_NO_1': return {...state, no1: action.data } break; default: return state break; } } ``` reducer2 ``` export function no2Reducer(state, action) { switch (action.type) { case 'CHANGE_NO_2': return {...state, no2: action.data } break; default: return state break; } } ``` ok, 我们现在来实现一个combineReducer函数,大概这样用 ~~~ const reducer = combineReducers({ counter: counterReducer, info: InfoReducer }); ~~~ 我们来实现这个combineReducer函数: ``` export function combineReducer(reducers) { const reducersKey = Object.keys(reducers) return (state = {}, action) = >{ const newState = {} reducersKey.forEach(key = >{ let reducer = reducers[key] newState[key] = reducer(state[key], action) }) return newState } } ``` 来试试调用: ![](images/screenshot_1596515969322.png) 嗯,完美的实现了合并reducer, ![](images/screenshot_1596515989556.png) 我们把 reducer 按组件维度拆分了,通过 combineReducers 合并了起来。但是还有个问题, state 我们还是写在一起的,这样会造成 state 树很庞大,不直观,很难维护。我们需要拆分,一个 state,一个 reducer 写一块。 ![](images/screenshot_1596516570363.png) 我们修改下 createStore 函数,增加一行`dispatch({ type: "" }) ![](images/screenshot_1596516767177.png) 这里为什么这样做就能ok: 1. createStore 的时候,用一个不匹配任何 type 的 action,来触发`state = reducer(state, action)` 2. 因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。 目前为止,我们的redux已经实现的差不多了。 ### 中间件 middleware 中间件 middleware 是 redux 中比较难理解的地方 ,**中间件的本质是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!** 现在我们需有一个需求,就是想要在每次修改state的时候,把日志输出到控制台,包括修改的action, 修改前后的值(和taro做小程序里,里差不多的需求) 看图 ![](images/screenshot_1596522279576.png)![](images/screenshot_1596522289876.png) ``` import { createStore, combineReducer } from "./redux"; import { no1Reducer } from "./reducer/no1"; import { no2Reducer } from "./reducer/no2"; import { asyncHanlde } from "./middleWares/asyncHanlde"; import { logger } from "./middleWares/logger"; import { timesmap } from "./middleWares/timesmap"; const reducer = combineReducer({ no1: no1Reducer, no2: no2Reducer }) const store = createStore(reducer) const next = store.dispatch const time = timesmap(store) const asyncH = asyncHanlde(store) const log = logger(store) store.dispatch = time(asyncH(log((next)))) store.subscribe(() => { console.log('state is changed:', store.getStore()) }) store.dispatch({ type: 'CHANGE_NO_1', data: 'zhangsan' }) store.dispatch({ type: 'CHANGE_NO_2', data: 'lisi' }) ``` 其实到这里,就算ok了,你会发现和redux核心源码已经90%相似了,还有一些函数,如applyMiddleware(对中间件的用法优化),还有如compoose函数,(将中间件的使用如 [a,b,c] 转换成 a(b(c)))找各种形式,没有实现,但是这个没必要在深入,只是一些语法糖,还有一些函数参数验证我们的reducer只能吃纯函数、订阅时间的退订这些细枝末节的没有实现,但是我们抓住主线就ok了。 ### 总结 到了最后,我想把 redux 中关键的名词列出来,你每个都知道是干啥的吗? * createStore 创建 store 对象,包含 getState, dispatch, subscribe, replaceReducer * reducer reducer 是一个计划函数,接收旧的 state 和 action,生成新的 state * action action 是一个对象,必须包含 type 字段 * dispatch `dispatch( action )`触发 action,生成新的 state * subscribe 实现订阅功能,每次触发 dispatch 的时候,会执行订阅函数 * combineReducers 多 reducer 合并成一个 reducer * middleware 扩展 dispatch 函数! 你再看 redux 流程图,是不是大彻大悟了? ![](https://img.kancloud.cn/c3/ad/c3adb8f9e5e1582daea1865ecc83bdc0_500x375.png)