*本文为公司前端团队内部分享的文案,是在网上剽窃、参考、学习了诸多文章和代码后,加上自己的理解而成,如果对你有所帮助,不生荣幸。
最终代码在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)
- 前言
- 工作中的一些记录
- 破解快手直播间的webSocket的连接
- 快手「反」反爬虫的研究记录
- HTML AND CSS
- 遇到的一些还行的css笔试题
- css常见面试题
- JavaScript 深度剖析
- ES6到ESNext新特性
- 关于http与缓存
- 关于页面性能
- 关于浏览器的重排(reflow、layout)与重绘
- 手写函数节流
- 手写promise
- 手写函数防抖
- 手写图片懒加载
- 手写jsonp
- 手写深拷贝
- 手写new
- 数据结构和算法
- 前言
- 时间复杂度
- 栈
- 队列
- 集合
- 字典
- 链表
- 树
- 图
- 堆
- 排序
- 搜索
- Webpack
- Webpack原理与实践
- Vue
- Vuejs的Virtual Dom的源码实现
- minVue
- Vuex实现原理
- 一道关于diff算法的面试题
- Vue2源码笔记:源码目录设计
- vue-router源码分析(v4.x)
- React及周边
- 深入理解redux(一步步实现一个 redux)
- React常见面试题汇总
- Taro、小程序等
- TypeScript
- CI/CD
- docker踩坑笔记
- jenkins
- 最后