您的位置:首页 > 其它

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信息

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 包围起来,这样可以直接调用它们。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redux 源码分析