手把手教你撸一套Redux(Redux源码解读)
Redux 版本:3.7.2
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
说白了Redux就是声明了一个对象,提供了这个对象的读取,修改,订阅等方法。
- getState: getter(读取)
- dispatch: setter(修改)
- subscribe: 订阅
Redux 提供了五个方法
接下来我们来一一解析。
createStore
创建一个 Redux store 来以存放应用中所有的 state。应用中应有且仅有一个 store。
参数:
- reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。
- [ reloadedState ] (any):初始时的 state。
- enhancer (Function):后面再讲。
返回值:
- getState:获取store方法
- dispatch:修改store方法
- subscribe:订阅store变化方法
- replaceReducer:重置reducer方法
先来写一个基础的 createStore 如下:
function createStore() {
function getState() { } // 取
function dispatch() { } // 存
function subscribe() { } // 订阅
function replaceReducer() { } // 重置reducer
return { getState, dispatch, subscribe, replaceReducer }
}
getState
getState 实现很简单,直接返回 currentState。
function createStore() {
let currentState = {}; // 数据
function getState() { // 取
return currentState;
}
function dispatch() { } // 存
function subscribe() { } // 订阅
function replaceReducer() { } // 重置reducer
return { getState, dispatch, subscribe, replaceReducer }
}
dispatch
dispatch 传入 action,通过 action.type 区别操作。
function createStore() {
let currentState = {};
function getState() { // 取
return currentState;
}
function dispatch(action) { // 存
switch (action.type) {
case 'PLUS':
currentState = {
...currentState,
count: currentState.count + 1,
};
}
return action;
}
function subscribe() { } // 订阅
function replaceReducer() { } // 重置reducer
return { getState, dispatch, subscribe, replaceReducer }
}
因为 Redux 要通用,所以 dispatch 内和业务相关的代码要提取出来,Redux 给它起了个名字,叫 reducer。
提取reducer,
const initialState = {
count: 0,
}
export default (state = initialState, action) => {
switch (action.type) {
case 'PLUS':
return {
...state,
count: state.count + 1,
}
case 'MINUS':
return {
...state,
count: state.count - 1,
}
default:
return state
}
}
给 createStore 添加两个参数 reducer, preloadedState。
preloadedState非必传,如果不传,currentState 默认值就是 undefined。
在 createStore 中添加初始化方法 dispatch({ type: '@@redux/INIT' })
; 初始化的 action.type 必须是 reducer 中没有使用过的,Redux 源码中使用了 '@@redux/INIT'
。初始化方法会执行一次 dispatch。
初始化时,如果 currentState 是 undefined, 那么在 reducer 中, state = initialState
会把 initialState 赋值给 state,然后通过 default return 出去, 最后修改 currentState。相当于 currentState = initialState。
最后 createStore 如下
function createStore(reducer, preloadedState) {
let currentState = preloadedState;
function getState() { // 取
return currentState;
}
function dispatch(action) { // 存
currentState = reducer(currentState, action);
return action;
}
function subscribe() { } // 订阅
function replaceReducer() { } // 重置reducer
dispatch({ type: '@@redux/INIT' }); // 初始化
return { getState, dispatch, subscribe, replaceReducer }
}
根据代码可以看出,reducer 和 action 都是开发者自定义的,Redux 只是把 reducer 返回的 state 赋值给了 currentState,那么开发者自定义其他格式的action ,并且在 reducer 中作出对应的解析,然后返回 state,当然也是完全可以的。只是 Redux 统一了这种写法,降低了个性化带来的开发成本。
实际上 createStore 还有第三个参数 enhancer,目前用不到,后面再讲。
subscribe
subscribe 有一个参数 listener (Function): 每当 dispatch action 的时候都会执行的回调。
subscribe 使用了设计模式中的 发布-订阅模式,又叫 观察者模式。
实现:
- 在 createStore 中添加一个储存 变化监听器 的数组 currentListeners;
- subscribe 将 变化监听器 放入 currentListeners;
- 每次 dispatch 时, 循环执行 currentListeners 中的 变化监听器。
function createStore(reducer, preloadedState) {
let currentState = preloadedState;
let currentListeners = [];
function getState() { // 取
return currentState;
}
function dispatch(action) { // 存
currentState = reducer(currentState, action);
currentListeners.forEach(fn => fn());
return action;
}
function subscribe(listener) { // 订阅
currentListeners.push(listener);
}
function replaceReducer() { } // 重置reducer
dispatch({ type: '@@redux/INIT' }); // 初始化
return { getState, dispatch, subscribe, replaceReducer }
}
replaceReducer
重置 reducer, 并不会重置 currentState。
实现:
- 添加变量 currentReducer;
- dispatch 使用 currentReducer;
- replaceReducer 方法将 nextReducer 赋值给 replaceReducer, 然后执行
dispatch({ type: '@@redux/INIT' })
。
注意:实际上,replaceReducer 中的 dispatch({ type: '@@redux/INIT' })
,只有此时 currentState 是 undefined 时,才有作用,会把新的 initialState 赋值给 currentState。
function createStore(reducer, preloadedState) {
let currentReducer = reducer
let currentState = preloadedState;
let currentListeners = [];
function getState() { // 取
return currentState;
}
function dispatch(action) { // 存
currentState = currentReducer(currentState, action);
currentListeners.forEach(fn => fn());
return action;
}
function subscribe(listener) { // 发布订阅
currentListeners.push(listener);
}
function replaceReducer(nextReducer) { // 重置reducer
currentReducer = nextReducer;
dispatch({ type: '@@redux/INIT' }); // 重置
}
dispatch({ type: '@@redux/INIT' }); // 初始化
return { getState, dispatch, subscribe, replaceReducer }
}
createStore 的实现到这里已经完成,Redux 源码除此之外还做了大量的错误校验。
combineReducers
随着项目越来越大,把 reducer 放在一个文件里写会越来越臃肿,于是 Redux 提供了 combineReducers 方法。
先来看下如何使用
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}
combineReducers 参数是 reducers 对象,返回一个合成后的 reducer。
实现逻辑比较简单,循环把 reducers 里的每一个 reducer 都执行, 执行结果放在 nextState 里,如果数据改变了就返回 nextState,如果数据没有改变就返回传入的 state。
注意:如果数据没有改变,返回的是传入的 state,虽然此时和 nextState 数据是一样的,但是实际地址并不一样。为了区分,Redux 特意用了 hasChanged 变量来记录。
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers); // key[]
return function combination(state = {}, action) {
let hasChanged = false; // state 是否改变
const nextState = {}; // 改变后的 state
// 循环 reducers
reducerKeys.forEach(key => {
const reducer = reducers[key]; // 当前 reducer
const previousStateForKey = state[key]; // 当前 state
const nextStateForKey = reducer(previousStateForKey, action); // 如果 没有匹配到action.type,会在 reducer 中的 switch default 返回传入的 state,即 previousStateForKey
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
})
return hasChanged ? nextState : state;
}
}
bindActionCreators
bindActionCreators(actionCreators, dispatch) 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。
action 生成器名字叫做叫 action creator, 如下
function addTodo(text) {
return {
type: 'ADD_TODO',
text,
};
}
修改数据需要这样写
dispatch(addTodo('Use Redux'))
如果我们多个 action creator,写起来会比较繁琐,
dispatch(addTodo('Use Redux'))
dispatch(plusTodo())
dispatch(setDataTodo({ id: 1 }))
所以 Redux 提供了 bindActionCreators 函数,传入 action creators 和 dispatch, 返回绑定了 dispatch 的 action creators。
实现也很简单,遍历 actionCreators, 把每个元素用 dispatch 处理后生成新的函数,返回新函数的集合。
actionCreators 参数是 action creator 的集合对象,如 { addTodo, addTodo1 }
。实现代码如下:
function bindActionCreators(actionCreators, dispatch) {
const boundActionCreators = {};
Object.keys(actionCreators).forEach(key => {
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = (...args) => dispatch(actionCreator(...args));
}
})
return boundActionCreators;
}
使用 bindActionCreators 写起来就会方便很多
const boundActionCreators = bindActionCreators({
addTodo,
plusTodo,
setDataTodo,
}, dispatch);
// 写入数据
boundActionCreators.addTodo('Use Redux')
boundActionCreators.plusTodo()
boundActionCreators.addTodo({ id: 1 })
Redux 支持 actionCreators 是一个单个 action creator 的函数,所以提取公共方法。改造如下:
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
const boundActionCreators = {};
Object.keys(actionCreators).forEach(key => {
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
})
return boundActionCreators;
}
compose
从右到左来组合多个函数。
先来看看源码:
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)))
}
最后一行很难理解,把它换成function写法如下
funcs.reduce(function (a, b) {
return function (...args) {
return a(b(...args))
}
})
先看下reduce方法
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
// 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
previousValue 上次循环的返回值
currentValue 当前循环item
所以第二次循环过程如下
// 第一次循环返回值为
function (...args) {
return a(b(...args))
}
// 第二次循环时,第一个参数为:第一次循环的返回值,第二个参数为:funcs 内第三个元素,用c来表示
// 第二次循环返回值为
function (...args) {
return (function (...args) {
return a(b(...args))
})(c(...args))
}
// 整理后
function (...args) {
return a(b(c(...args)))
}
所以 [a, b, c, d, e]
的执行结果是 (...args) => a(b(c(d(e(...args)))))
。
所以能看出来,funcs 内函数需要满足 函数参数和函数返回值结构一致。
applyMiddleware
applyMiddleware 是把 dispatch 一层一层包装。洋葱圈模型。
先看看 createStore 的第三个参数 enhancer
function createStore(reducer, preloadedState, enhancer) {
// 实现了 preloadedState 参数可以省略
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
// 看起来 enhancer 是个高阶函数,返回值还是 store creator
// 可以看出 enhancer 的大概结构为
// (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)
return enhancer(createStore)(reducer, preloadedState)
}
// 这里是其他代码
// ...
}
再看看官网给的 applyMiddleware 使用例子
let store = createStore(
todos,
[ 'Use Redux' ],
applyMiddleware(logger)
)
所以 applyMiddleware 的结构应该是
(...middlewares) => (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)
所以猜出来了 applyMiddleware 的参数是函数,返回值执行多次后还是 createStore(educer, preloadedState)。
所以再来看官方定义就比较好理解
Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
来看 applyMiddleware 看源码, 跟着 序号看会稍微清晰点:
applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 2、chain内元素结构为 (store.dispatch) => store.dispatch
// 所以 middleware(middlewareAPI) 结果为 (store.dispatch) => store.dispatch
// 所以 middleware 结构为 (middlewareAPI) => (store.dispatch) => store.dispatch
// 即 参数 middlewares 内元素结构为 (middlewareAPI) => (store.dispatch) => store.dispatch
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 1、上面解释过 compose 的返回值是 (...arg) => a(b(c(...arg))),
// 所以下面 dispatch = ((...arg) => a(b(c(...arg))))(store.dispatch)
// 即 dispatch = a(b(c(store.dispatch)))
// 所以 a、b、c 即 chain内元素 的结构需要为 (store.dispatch) => store.dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch // 这里可以看出,applyMiddleware 只包装替换了 createStore 的 dispatch
}
}
}
现在我们知道了 applyMiddleware 的参数结构是 (middlewareAPI) => (store.dispatch) => store.dispatch
,然后我们来写个简单的 middleware
// 原始长这个样子
function logger(middlewareAPI) {
return (dispatch) => dispatch;
}
// 然后 给 dispatch 包装以下,并且换个名字叫 next
function logger(middlewareAPI) {
return (next) => (action) => {
let value = next(action);
return value;
};
}
// 然后 加入功能
function logger(middlewareAPI) {
return (next) => (action) => {
// 这里的 dispatch 是 createStore 创建的。一般不用。
const { getState, dispatch } = middlewareAPI;
console.log('will dispatch', action);
let value = next(action);
console.log('state after dispatch', getState());
// createStore 里实现的 dispatch 返回 action,
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。
return value;
};
}
最后再来回味下 applyMiddleware 的这几个结构
// compose
([a, b, c, d, e]) => (...args) => a(b(c(d(e(...args)))))
// applyMiddleware
(...middlewares) => (createStore) => (reducer, preloadedState) => createStore(educer, preloadedState)
// middleware
(middlewareAPI) => (dispatch) => dispatch
手把手教你撸一套Redux(Redux源码解读)的更多相关文章
- redux:applyMiddleware源码解读
前言: 笔者之前也有一篇关于applyMiddleware的总结.是applyMiddleware的浅析. 现在阅读了一下redux的源码.下面说说我的理解. 概要源码: step 1: apply ...
- 通过ES6写法去对Redux部分源码解读
在Redux源码中主要有四个文件createStore,applyMiddleware,bindActionCreators,combineRedures createStore.js export ...
- 手把手教你撸个vue2.0弹窗组件
手把手教你撸个vue2.0弹窗组件 在开始之前需要了解一下开发vue插件的前置知识,推荐先看一下vue官网的插件介绍 预览地址 http://haogewudi.me/kiko/inde... 源码地 ...
- redux源码解读
react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合react使用,保证数据的单向流动,可以很好的管理整个项目的状态,但是具体来说,下面是 ...
- redux源码解读(二)
之前,已经写过一篇redux源码解读(一),主要分析了 redux 的核心思想,并用100多行代码实现一个简单的 redux .但是,那个实现还不具备合并 reducer 和添加 middleware ...
- redux源码解读(一)
redux 的源码虽然代码量并不多(除去注释大概300行吧).但是,因为函数式编程的思想在里面体现得淋漓尽致,理解起来并不太容易,所以准备使用三篇文章来分析. 第一篇,主要研究 redux 的核心思想 ...
- redux的源码解析
一. redux出现的动机 1. Javascript 需要管理比任何时候都要多的state2. state 在什么时候,由于什么原因,如何变化已然不受控制.3. 来自前端开发领域的新需求4. 我们总 ...
- Redux 源码解读 —— 从源码开始学 Redux
已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...
- AMA指标原作者Perry Kaufman 100+套交易策略源码分享
更多精彩内容,欢迎关注公众号:数量技术宅.想要获取本期分享的完整策略代码,请加技术宅微信:sljsz01 AMA技术指标与原作者 Kaufman 说起 Perry Kaufman 这个名字,不少读者会 ...
随机推荐
- Daily Scrum 12/21/2015
Process: Zhaoyang: Integrate the oxford Speech API Code to the IOS client and do some UI optimizatio ...
- Go gRPC进阶-go-grpc-middleware使用(八)
前言 上篇介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用.gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱.本篇简单介绍go-grpc-middleware的 ...
- 浅析CAS与AtomicInteger原子类
一:CAS简介 CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,比如我们的atomic包下的原子类就是基于CAS来实现.区别于悲观锁synchroniz ...
- 吃瓜的正确姿势,Python绘制罗志祥词云图
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 这篇文章中向大家介绍了Python绘制词云的方法,不难看出绘制词云可以说是一 ...
- Java中Character类
Character 类在对象中包装一个基本类型char的值此外,该类提供了几种方法,以确定字符的类别(小写字母,数字,等),并将字符从大写转小写,反之亦然. 构造方法: Character(char ...
- python face_recognition模块实现人脸识别
import face_recognition #人脸识别库 pip cmake dlib import cv2 #读取图像 face_image1 = face_recognition.load_i ...
- spring源码阅读笔记09:循环依赖
前面的文章一直在研究Spring创建Bean的整个过程,创建一个bean是一个非常复杂的过程,而其中最难以理解的就是对循环依赖的处理,本文就来研究一下spring是如何处理循环依赖的. 1. 什么是循 ...
- web--ajax--json
案例: 其中,用eval()解析的话,如果json有一些代码,如:{"age":"alert(你好)"},他也会帮你实行,而JOSN.parse();则会自动报 ...
- Linux查看端口或pid使用路径
1. lsof -i:10010 查看10010端口的占用情况 命令返回结果: 2. netstat -lpn | grep 80 查看80端口服务端socket占用状况 3. ll /proc/26 ...
- ansible的剧本play(四)
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAA68AAAETCAYAAADZDzDOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw