Open yuxino opened 6 years ago
这周决定宅着不出去做完平日里没做完的事情。其中之一就是分析redux的源码。我看的版本是v4.0.0。Redux的作者是Dan Abramov。俄罗斯人。非常好的人,还回复过我的水文。
v4.0.0
Redux本身代码量是很少的。典型的文档比代码多系列。测试也写得很全。Redux的标语是
Predictable state container for JavaScript apps
Predictable可以预测的状态管理容器。可预测最主要的就是可以做time machine,时间旅行。可以回滚到上次的状态。Redux对初学者来说可能是非常complex的。并且有些过于啰嗦。Redux本身不属于React的一部分。React-Redux和Redux是两个不同的项目。Redux本身只是一个很小的状态管理库。可以应用到其他的框架上。
看源码之前要做的事情就是摸熟这个框架或者codebase的用法。这样就不会觉得陌生。
首先要做的事情是从Gihub把Redux的源码克隆下来。
git clone git@github.com:reactjs/redux.git
克隆下来后为了调试代码。我自己写了个webpack.config.js,开个dev server配合vscode 的debugger in chrome进行调试。其实我也不知道正规的看代码方法吧(其实是找了很久没找到,又不想调试test case)。反正现在也不纠结,我就先这样。贴一下配置。
先进入redux根目录创建webpack.config.js。再开个debugger目录。
webpack.config.js
touch webpack.config.js mkdir debugger
装依赖
yarn add -D webpack yarn add -D webpack-cli yarn add -D html-webpack-plugin yarn add -D webpack-dev-server yarn
额 不做这一步也可以,只是我喜欢这样。然后弄完可以直接用vscode调试了。
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', entry: './debugger/index.js', devtool: 'inline-source-map', output: { path: __dirname + '/dist', filename: 'index_debugger_bundle.js' }, plugins: [ new HtmlWebpackPlugin({ template: './debugger/index.html' }) ] }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> 哭哭惹 </body> </html>
import * as redux from '../src' console.log(redux)
"scripts": { "debugger": "webpack-dev-server --open --port=2333" }
改一下vscode调试的配置就可以了。详细的自己看一下吧。source-map必须要开不然这插件就是shit。
以下是每个文件的用处。懒得打字所以一大部分都是抄袭的。贴上原文地址。因为原作者的版本比较旧了。所以自己补充了几个文红没写到的。
applyMiddlewar.js 使用自定义的 middleware 来扩展 Redux
applyMiddlewar.js
bindActionCreators.js 把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用
bindActionCreators.js
combineReducers.js 一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分
combineReducers.js
compose.js 从右到左来组合多个函数,函数编程中常用到
compose.js
createStore.js 创建一个 Redux Store 来放所有的state
createStore.js
utils/warnimng.js 控制台输出一个警告,我们可以不用看
utils/warnimng.js
utils/actionTypes.js redux内部使用的3个action
utils/actionTypes.js
utils/isPlanObject.js 判断对象是不是纯对象
utils/isPlanObject.js
所有的一切都基于createStore创建出来的store。可以说是一切的开始。createStore函数接受三个参数。
createStore
function createStore(reducer, preloadedState, enhancer)
createStore暴露出的方法有以下这些
createStore函数就是一个Observser实现的话也没啥好看的。读一读代码就能理解了。这里就不详细写了。不是很懂的地方有一个就是ensureCanMutateNextListeners的作用。暂时我还没有理解。
ensureCanMutateNextListeners
redux实现middleware和compose函数有很多的关系。这个compose函数非常的简单。核心是这样。我删掉了一些不(特别)重要的代码。
const compose = (...funcs) => { return funcs.reduce((a, b) => (...args) => a(b(...args))) }
这个compose只做一件事情。把函数组合起来。就是函数式编程的那个组合。返回一个从右到左执行的compose function。比如传入(a,b,c)这样的函数参数这样的执行顺序: c(b(a(...args)))。我们可以做个实验,你可以直接把代码复制到控制台里面执行。
compose
compose function
(a,b,c)
c(b(a(...args)))
const a = () => console.log('a') const b = () => console.log('b') const c = () => console.log('c') compose(a,b,c)()
输出的结果会是: c,b,a。但是如果我们把这些函数做一个更加高阶的处理。让函数第一次执行的时候返回函数。变成这样的话。会发生一个很有趣的现象。
c,b,a
const a = ( next ) => ( param ) => { console.log(param); next('b'); } const b = ( next ) => ( param ) => { console.log(param); next('c'); } const c = ( next ) => ( param ) => { console.log(param); next('d'); } const d = (param) => console.log(param) const cp = compose(a,b,c)(d) // execute cp('a')
输出的结果会是a,b,c,d。和之前的函数版本相比。此时的compose function函数拥有了控制是否执行下一个函数的能力。并且通过调用next来执行下一个。同时它变成正序的。Redux利用了这一特性。
a,b,c,d
next
const compose = (...funcs) => { return funcs.reduce((a, b) => (...args) => a(b(...args))) } export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
Redux把store => next => action的最外层通过执行middlewares.map(middleware => middleware(middlewareAPI))传入参数把函数变成了next => action的形式完成了中间件的模式。
store => next => action
middlewares.map(middleware => middleware(middlewareAPI))
next => action
combineReducer的作用是把零零散散的reducer拼起来或者说把复杂的单个reducer拆分成小的reducer。实现的原理都在代码上。以下代码做了一些精简的处理,去掉了一些不是特别重要的警告。
export default function combineReducers(reducers) { // 获取reducers的key const reducerKeys = Object.keys(reducers) const finalReducers = {} // 处理reducerKeys for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] // 非开发环境下 如果reducers[key]为undefined 抛出警告 if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } // 判断是否为方法 if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } // 获取finalReducerKeys的所有key const finalReducerKeys = Object.keys(finalReducers) // 组合state 原理是每次执行调用每个传过来的reducers // 最终通过nextState拼出一个最大的state // 通过`hasChanged`做了缓存处理 return function combination(state = {}, action) { // 缓存处理 let hasChanged = false const nextState = {} // 调用每个reducer .. for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) // 如果nextStateForKey执行后的结果为undefined 说明该reducer返回的 // 结果是undefined 会抛出异常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 和上次的状态比较 如果一致就为false。返回上一次的状态。 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
bindActionCreators的最常见用的地方可能是在react-redux调用mapDispatchToProps的时候。
bindActionCreators
react-redux
mapDispatchToProps
const TodoAction = (...args) => { type: 'TODO', { ...args } } function mapDispatchToProps(dispatch) { actions: bindActionCreators(TodoAction, dispatch) }
不用bindActionCreators的话会是这样写。就如下面的bindActionCreator函数基本一致。要注意源码是没有s那个。
bindActionCreator
s
调用有s那个传一个对象。写起来会是这样。原理是遍历执行bindActionCreators。
import * as TodoActions from '../actions/todo' function mapDispatchToProps(dispatch) { actions: bindActionCreators(TodoActions, dispatch) }
源码如下:
function mapDispatchToProps(dispatch) { actions: (...args) => { dispatch(TodoActions(args)) } }
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? 'null' : typeof actionCreators }. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
这个变量用来处理reducer 中 调用 dispatch,导致死循环 的情况。
如下代码,在reducer里dispatch 又被调用,这种情况redux是不允许的。
var reducer = function(state, action){ switch (action.type) { case 'add_todo': store.dispatch({type: 'yyy'}); // 调用B return state; default: return state; } }; var store = redux.createStore(reducer, []); store.dispacth({type: 'xxx'}); // 调用A
这周决定宅着不出去做完平日里没做完的事情。其中之一就是分析redux的源码。我看的版本是
v4.0.0
。Redux的作者是Dan Abramov。俄罗斯人。非常好的人,还回复过我的水文。Redux本身代码量是很少的。典型的文档比代码多系列。测试也写得很全。Redux的标语是
Predictable可以预测的状态管理容器。可预测最主要的就是可以做time machine,时间旅行。可以回滚到上次的状态。Redux对初学者来说可能是非常complex的。并且有些过于啰嗦。Redux本身不属于React的一部分。React-Redux和Redux是两个不同的项目。Redux本身只是一个很小的状态管理库。可以应用到其他的框架上。
看源码之前要做的事情就是摸熟这个框架或者codebase的用法。这样就不会觉得陌生。
首先要做的事情是从Gihub把Redux的源码克隆下来。
克隆下来后为了调试代码。我自己写了个webpack.config.js,开个dev server配合vscode 的debugger in chrome进行调试。其实我也不知道正规的看代码方法吧(其实是找了很久没找到,又不想调试test case)。反正现在也不纠结,我就先这样。贴一下配置。
先进入redux根目录创建
webpack.config.js
。再开个debugger目录。装依赖
额 不做这一步也可以,只是我喜欢这样。然后弄完可以直接用vscode调试了。
webpack.config.js
debugger/index.html
debugger/index.js
package.json
改一下vscode调试的配置就可以了。详细的自己看一下吧。source-map必须要开不然这插件就是shit。
项目结构
以下是每个文件的用处。懒得打字所以一大部分都是抄袭的。贴上原文地址。因为原作者的版本比较旧了。所以自己补充了几个文红没写到的。
applyMiddlewar.js
使用自定义的 middleware 来扩展 ReduxbindActionCreators.js
把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用combineReducers.js
一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分compose.js
从右到左来组合多个函数,函数编程中常用到createStore.js
创建一个 Redux Store 来放所有的stateutils/warnimng.js
控制台输出一个警告,我们可以不用看utils/actionTypes.js
redux内部使用的3个actionutils/isPlanObject.js
判断对象是不是纯对象createStore
所有的一切都基于createStore创建出来的store。可以说是一切的开始。
createStore
函数接受三个参数。createStore
暴露出的方法有以下这些createStore函数就是一个Observser实现的话也没啥好看的。读一读代码就能理解了。这里就不详细写了。不是很懂的地方有一个就是
ensureCanMutateNextListeners
的作用。暂时我还没有理解。MiddleWare 实现
redux实现middleware和compose函数有很多的关系。这个compose函数非常的简单。核心是这样。我删掉了一些不(特别)重要的代码。
这个
compose
只做一件事情。把函数组合起来。就是函数式编程的那个组合。返回一个从右到左执行的compose function
。比如传入(a,b,c)
这样的函数参数这样的执行顺序:c(b(a(...args)))
。我们可以做个实验,你可以直接把代码复制到控制台里面执行。输出的结果会是:
c,b,a
。但是如果我们把这些函数做一个更加高阶的处理。让函数第一次执行的时候返回函数。变成这样的话。会发生一个很有趣的现象。输出的结果会是
a,b,c,d
。和之前的函数版本相比。此时的compose function
函数拥有了控制是否执行下一个函数的能力。并且通过调用next
来执行下一个。同时它变成正序的。Redux利用了这一特性。Redux把
store => next => action
的最外层通过执行middlewares.map(middleware => middleware(middlewareAPI))
传入参数把函数变成了next => action
的形式完成了中间件的模式。combineReducer 实现
combineReducer的作用是把零零散散的reducer拼起来或者说把复杂的单个reducer拆分成小的reducer。实现的原理都在代码上。以下代码做了一些精简的处理,去掉了一些不是特别重要的警告。
bindActionCreators 实现
bindActionCreators
的最常见用的地方可能是在react-redux
调用mapDispatchToProps
的时候。不用
bindActionCreators
的话会是这样写。就如下面的bindActionCreator
函数基本一致。要注意源码是没有s
那个。调用有
s
那个传一个对象。写起来会是这样。原理是遍历执行bindActionCreators
。源码如下:
源码如下:
isDispatching的作用
这个变量用来处理reducer 中 调用 dispatch,导致死循环 的情况。
如下代码,在reducer里dispatch 又被调用,这种情况redux是不允许的。