Redux源码深度解析
2017-07-21 21:51
579 查看
简要介绍:用了一段时间redux,今天看了一下redux的源码,大致整理了心得如下。
1、什么是redux,这里就不做介绍,如想了解可以移步 ReadMe.redux,整体redux的代码只有800行,src下面分为一下几个部分。
–applyMiddleware.js
–bindActionCreators.js
–combineReducers.js
–compose.js
–createStore.js
–index.js
首先我们来看index.js主js的内容,很简单,就是引入和模块和抛出模块,这里有一句提醒内容,如果是production生产环境并且js已经被压缩,会输出warning信息
注: isCrushed.name 函数名.name是一个es6的属性,返回函数的名称。
除了index.js外,我们接下去从redux实现的接口,来深度分析一下Redux的源码。
2、compose.js
我们首先从compose.js入手,首先redux贯穿始终的是函数式变成的思想,个人对于函数式编程的理解为:
–首先是纯函数(相同的输入产生相同的输出)
–在范畴论理,状态或者输出表示点,函数表示边,从点到点的转移可以看成运算符,函数也是一种运算符,因为运算符是纯净的,因此函数式编程中的函数也是纯净的
–函数式编程中的函数,与变量等价,可以作为参数传递或者成为其他函数函数体里的一部分
–因为是函数式编程,便于函数的组合,这里有一个curry和compose的组合过程
基础了解函数式编程之后,下面我们来看compose.js的源码:
这个compose其实很简单,传入的参数为函数数组,返回的为reduce从左到右合并后的新的函数。是一个类似于链式调用的过程。
来看:
这句特别重要,组合函数的这部非常重要,我们发现…args参数会依次的从右到左执行,比如将b(…args)的执行结果,传入a中作为参数继续执行。
3、applyMiddleware.js
applyMiddleware.js其实是基于compose.js来实现的
上述代码如果applyMiddleware(…Middleware)(createStore)这样调用,会生成一个新的createStore函数,用于创建新的createStore,新在哪里呢?就是链式的调用了所有的middleware:
来看上述的代码,chain是一个函数数组,是middleware({})执行后的返回函数的数组,compose(…chain)是链式的组合函数,这里的…args是初始时候的store.dispatch,当最右边的函数以store.dispatch为参数,执行后生成一个新的store.dispatch,又向外传递,因此middleware是从右到左执行的。
从上述的描述中,我们知道了middle的书写形式,如果以纯函数的形式,首先第一个参数应该是{getState:”,dispatch:”},第二个参数是store.dispatch,第三个参数应该是action,因此最基本形式的middleware应该是:
return ({ dispatch, getState }) => next => action => {
}
我们以redux-thunk为例,redux-thunk中间件是严格按照上述的形式,
代码只有13行:
这个中间件的功能其实很简单,也就是action如果是一个函数就选择执行这个函数,并且action函数执行的时候,会传入dispatch和getState.
4、createStore.js
createStore相对而言会较为的复杂,我们还是从接口出发。
(1)首先看,createStore()函数的返回值store有哪些接口:
–getState():返回当前的state树
–dispatch(action):分发action,是改变state的唯一方法
–subscribe(listener):添加一个监听器,当state变化的时候,执行监听器里面的函数。
–unsubscribe(listener):subscribe的返回值,用于移除监听器
–replaceReducer(nextReducer):替换store中当前的reducer
(2)下面根据代码,依次来看,各个接口的实现情况。
首先明确createStore的形参,形参有3个,分别是reducer(处理函数),initState(初始化state),enhancer(一个高阶函数,可以改变store的接口)。
–getState函数:
getState函数比较简单,类似于一个get的方法,返回currentState的值
–dispatch函数:
dispatch也不复杂,去掉判断类型(因为action必须是对象)的部分,其实只有2步:
这就很显然易见了,就是执行currentReducer()传入当前的currentState和action,返回新的state,并且执行监听函数数组里面的所有函数。
–subscribe:
监听函数也挺简单,就是一个简单的移入和移出,这是一个底层 API。多数情况下,你不会直接使用它,会使用一些 React(或其它库)的绑定。比如react-redux中的容器组件中的props改变会自动的更新(也算一个监听过程)。
–replaceReducer:
这个函数就更加的简单了,replaceReducer是为了改变当前的reducer,因此只要将currentReducer赋值为形参即可。
5、combineReducers.js
最复杂的部分就是combineReducer.js了
–combineReducer(reducer)接受一个reducer对象,recuder是key表
示属性名,value是一个小的reduce函数:
–遍历reducer中的对象,取出其中的value值(reduce函数),生成一个新的对象:
–因为reduce函数有一个初始执行过程,即会自动执行一次 type: ActionTypes.INIT,因此有以下过程:
–最后就是最终要的返回的combine函数:
其原理也很简单,state的属性名和reducer对象的key是相对的,因此也就是在所有的小的reduce函数中,传入相对的state[key],action,依次执行后得到一个新的newState,然后与state做比较,选择性返回。
6、bindActionCreators.js
这个一般比较少用,这里就不分析源码了,只简单的阐述功能,
bindActionCreators()把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们。
1、什么是redux,这里就不做介绍,如想了解可以移步 ReadMe.redux,整体redux的代码只有800行,src下面分为一下几个部分。
–applyMiddleware.js
–bindActionCreators.js
–combineReducers.js
–compose.js
–createStore.js
–index.js
首先我们来看index.js主js的内容,很简单,就是引入和模块和抛出模块,这里有一句提醒内容,如果是production生产环境并且js已经被压缩,会输出warning信息
function isCrushed() {} if ( process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed' ) { warning( 'You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.' ) }
注: isCrushed.name 函数名.name是一个es6的属性,返回函数的名称。
除了index.js外,我们接下去从redux实现的接口,来深度分析一下Redux的源码。
2、compose.js
我们首先从compose.js入手,首先redux贯穿始终的是函数式变成的思想,个人对于函数式编程的理解为:
–首先是纯函数(相同的输入产生相同的输出)
–在范畴论理,状态或者输出表示点,函数表示边,从点到点的转移可以看成运算符,函数也是一种运算符,因为运算符是纯净的,因此函数式编程中的函数也是纯净的
–函数式编程中的函数,与变量等价,可以作为参数传递或者成为其他函数函数体里的一部分
–因为是函数式编程,便于函数的组合,这里有一个curry和compose的组合过程
基础了解函数式编程之后,下面我们来看compose.js的源码:
export default 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))) }
这个compose其实很简单,传入的参数为函数数组,返回的为reduce从左到右合并后的新的函数。是一个类似于链式调用的过程。
来看:
funcs.reduce((a, b) => (...args) => a(b(...args)))
这句特别重要,组合函数的这部非常重要,我们发现…args参数会依次的从右到左执行,比如将b(…args)的执行结果,传入a中作为参数继续执行。
3、applyMiddleware.js
applyMiddleware.js其实是基于compose.js来实现的
export default function 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) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
上述代码如果applyMiddleware(…Middleware)(createStore)这样调用,会生成一个新的createStore函数,用于创建新的createStore,新在哪里呢?就是链式的调用了所有的middleware:
let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch)
来看上述的代码,chain是一个函数数组,是middleware({})执行后的返回函数的数组,compose(…chain)是链式的组合函数,这里的…args是初始时候的store.dispatch,当最右边的函数以store.dispatch为参数,执行后生成一个新的store.dispatch,又向外传递,因此middleware是从右到左执行的。
从上述的描述中,我们知道了middle的书写形式,如果以纯函数的形式,首先第一个参数应该是{getState:”,dispatch:”},第二个参数是store.dispatch,第三个参数应该是action,因此最基本形式的middleware应该是:
return ({ dispatch, getState }) => next => action => {
}
我们以redux-thunk为例,redux-thunk中间件是严格按照上述的形式,
代码只有13行:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
这个中间件的功能其实很简单,也就是action如果是一个函数就选择执行这个函数,并且action函数执行的时候,会传入dispatch和getState.
4、createStore.js
createStore相对而言会较为的复杂,我们还是从接口出发。
(1)首先看,createStore()函数的返回值store有哪些接口:
–getState():返回当前的state树
–dispatch(action):分发action,是改变state的唯一方法
–subscribe(listener):添加一个监听器,当state变化的时候,执行监听器里面的函数。
–unsubscribe(listener):subscribe的返回值,用于移除监听器
–replaceReducer(nextReducer):替换store中当前的reducer
(2)下面根据代码,依次来看,各个接口的实现情况。
首先明确createStore的形参,形参有3个,分别是reducer(处理函数),initState(初始化state),enhancer(一个高阶函数,可以改变store的接口)。
export default function createStore(reducer, preloadedState, enhancer) { }
–getState函数:
let currentState = preloadedState function getState() { return currentState }
getState函数比较简单,类似于一个get的方法,返回currentState的值
–dispatch函数:
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
dispatch也不复杂,去掉判断类型(因为action必须是对象)的部分,其实只有2步:
try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
这就很显然易见了,就是执行currentReducer()传入当前的currentState和action,返回新的state,并且执行监听函数数组里面的所有函数。
–subscribe:
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
监听函数也挺简单,就是一个简单的移入和移出,这是一个底层 API。多数情况下,你不会直接使用它,会使用一些 React(或其它库)的绑定。比如react-redux中的容器组件中的props改变会自动的更新(也算一个监听过程)。
–replaceReducer:
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) }
这个函数就更加的简单了,replaceReducer是为了改变当前的reducer,因此只要将currentReducer赋值为形参即可。
5、combineReducers.js
最复杂的部分就是combineReducer.js了
–combineReducer(reducer)接受一个reducer对象,recuder是key表
示属性名,value是一个小的reduce函数:
export default function combineReducers(reducers) { }
–遍历reducer中的对象,取出其中的value值(reduce函数),生成一个新的对象:
const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] finalReducers[key] = reducers[key] }
–因为reduce函数有一个初始执行过程,即会自动执行一次 type: ActionTypes.INIT,因此有以下过程:
assertReducerShape(finalReducers); function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT })
–最后就是最终要的返回的combine函数:
其原理也很简单,state的属性名和reducer对象的key是相对的,因此也就是在所有的小的reduce函数中,传入相对的state[key],action,依次执行后得到一个新的newState,然后与state做比较,选择性返回。
return function combination(state = {}, action) { let hasChanged = false const nextState = {} 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) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
6、bindActionCreators.js
这个一般比较少用,这里就不分析源码了,只简单的阐述功能,
bindActionCreators()把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们。
相关文章推荐
- 深度学习框架Caffe源码解析
- Kafka源码深度解析-序列5 -Producer -RecordAccumulator队列分析
- iOS开发之Masonry框架源码深度解析
- 对redux的认识(源码深度解读)
- 源码深度解析SpringMvc请求运行机制
- Kafka源码深度解析-序列9 -Consumer -SubscriptionState内部结构分析
- iOS开发之Masonry框架源码深度解析
- fastjson深度源码解析- 序列化(六) - json特定序列化实现解析
- fastjson深度源码解析- 反序列化(一) - 反序列化解析介绍
- iOS开发之Masonry框架源码深度解析
- Spring 源码深度解析笔记 - Spring 模块划分
- Asynctask源码级解析,深度探索源码之旅
- 【深度学习:目标检测】 faster rcnn RPN之anchor(generate_anchors)源码解析
- c语言深度解析中关于static变量测试 请看源码分析
- Java线程池ThreadPoolExecutor深度探索及源码解析
- fastjson深度源码解析- 词法和语法解析(一) - token定义解析
- react-redux部分关键源码解析
- The Wide and Deep Learning Model(译文+Tensorlfow源码解析) 原创 2017年11月03日 22:14:47 标签: 深度学习 / 谷歌 / tensorf
- Kafka源码深度解析-序列5 -Producer -RecordAccumulator队列分析
- RocketMQ源码深度解析三之Broker篇