You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionsubscribe(listener){if(typeoflistener!=='function'){thrownewError('Expected listener to be a function.')}letisSubscribed=trueensureCanMutateNextListeners()nextListeners.push(listener)returnfunctionunsubscribe(){if(!isSubscribed){return}isSubscribed=falseensureCanMutateNextListeners()constindex=nextListeners.indexOf(listener)nextListeners.splice(index,1)}}
functiondispatch(action){if(!isPlainObject(action)){thrownewError('Actions must be plain objects. '+'Use custom middleware for async actions.')}if(typeofaction.type==='undefined'){thrownewError('Actions may not have an undefined "type" property. '+'Have you misspelled a constant?')}if(isDispatching){thrownewError('Reducers may not dispatch actions.')}try{isDispatching=truecurrentState=currentReducer(currentState,action)}finally{isDispatching=false}constlisteners=currentListeners=nextListenersfor(leti=0;i<listeners.length;i++){constlistener=listeners[i]listener()}returnaction}
functionreplaceReducer(nextReducer){if(typeofnextReducer!=='function'){thrownewError('Expected the nextReducer to be a function.')}currentReducer=nextReducerdispatch({type: ActionTypes.INIT})}
这个函数可以组合一组 reducers,然后返回一个新的reducer。由于redux只维护唯一的state,随着整个项目越来越大,state状态树也会越来越庞大,state的层级也会越来越深,当某个action.type所对应的case 只是要修改state.a.b.c.d.e.f这个属性时,我的 handleCase 函数写起来就非常难看,我必须在这个函数的头部验证 state 对象有没有那个属性。这是让开发者非常头疼的一件事。
combineReducers实现方法很简单,它遍历传入的reducers,返回一个新的reducer,这个新对象的 key 跟传入的reducers一样,它的 value 则是传入的reducers的不同key对应的value展开的{ key: value }。貌似讲的有点绕啊~,举个例子好讲明白:
varreducers={todos: (state,action){// 此处的 state 参数是全局 state.todos属性switch(action.type){...}// 返回的 new state 更新到全局 state.todos 属性中},activeFilter: (state,action){// 拿到 state.activeFilter 作为此处的 stateswitch(action.type){...}// new state 更新到全局 state.activeFilter 属性中}}varrootReducer=combineReducers(reducers)
vartodosReducers={active: (state,action)=>{//拿到全局 state.todos.activeswitch(action.type){caseA: //处理 A 场景returnhandleA(state)caseB: //处理 B 场景returnhandleB(state)default:
returnstate}},completed: (state,action)=>{//拿到全局 state.todos.completedswitch(action.type){caseC: //处理 C 场景returnhandleC(state)default:
returnstate}}}vartodosRootReducer=combineReducers(todosReducers)varreducers={todos: (state,action)=>{//拿到全局 state.todosswitch(action.type){caseA:
caseB:
caseC:
// A B C 场景都传递给 todosRootReducerreturntodosRootReducer(state,action)caseD:
//...handle statedefault:
returnstate}}}//rootReducer(state, action) 这里的 state 是真正的全局 statevarrootReducer=combineReducers(reducers)
if(typeofenhancer!=='undefined'){if(typeofenhancer!=='function'){thrownewError('Expected the enhancer to be a function.')}returnenhancer(createStore)(reducer,preloadedState)}
当createStore中传了第三个参数的时候,会执行enhancer(createStore)(reducer, preloadedState),这是一个柯里化函数;我们可以设想中间件的使用方法:const store = createStore( reducer, applyMiddleware([...中间件]))。applyMiddleware([...中间件])的返回值是一个以createStore为参数的函数,这个函数会在createStore中执行,返回的函数也会继续执行,最后返回一个store。我们继续回到applyMiddleware中,在返回store之前,中间件做了什么处理呢?中间件将最重要的两个方法 getState/dispatch整合出来,并传递给中间件使用,中间件处理完之后,返回一个新的dispatch。这里又有疑问,为什么中间件要放在dispatch的时候?借用阮老师的一张图:
1、前言
redux是近期前端开发中最火的框架之一。它是 facebook 提出的 flux 架构的一种优秀实现;而且不局限于为 react 提供数据状态处理。它是零依赖的,可以配合其他任何框架或者类库一起使用。。然而,很多人不清楚它是什么,它有什么好处。
正如官方文档中描述的,redux对于JavaScript应用而言是一个可预测状态的容器。换句话说,它是一个应用数据流框架,而不是传统的像underscore.js或者AngularJs那样的库或者框架。
redux最主要是用作应用状态的管理和数据流的处理。redux用一个单独的常量状态树(对象)保存这一整个应用的状态(官方推荐只有一个state),这个对象不能直接被改变。随着一个项目不停地迭代,项目会越来越庞大,整个应用的数据变得越来越不可控,Redux能完成对数据流的控制,将所有的数据变化变得可预测、可控。
redux的源码非常的简洁,总共加起来就几百行,所以不难理解;建议先去熟悉redux的API和用法再来看本文,会更得心应手。
2、源码结构
2.1 index.js
redux的源码非常简单,index.js就是整个代码的入口:
这里的
isCrushed
函数主要是为了验证在非生产环境下 redux 是否被压缩(默认情况下,isCrushed.name等于isCrushed
,如果被压缩了,函数的名称会变短,一般会压缩成数字,那么(isCrushed.name !== 'isCrushed')
就是true
),如果被压缩,就给开发者一个 warn 提示)。然后就是暴露
createStore
combineReducers
bindActionCreators
applyMiddleware
compose
这几个接口给开发者使用,我们来逐一解析这几个 API。2.2 createStore.js
createStore
是redux非常重要的一个API,createStore
会生成一个store,用来维护一个全局的state。createStore
接受3个参数:reducer
,preloadedState
,enhancer
;第一个参数reducer
和第三个参数enhancer
我们接下来会具体介绍,第二个参数是preloadedState
,它是state的初始值。createStore
的返回值是dispatch
,subscribe
,getState
,replaceReducer
,[$$observable]: observable
,共同组成了一个store,接下来我们也会讲到讲这些方法。1. action
action代表的是用户的操作。redux规定action一定要包含一个type属性,且type属性也要唯一,相同的type,redux视为同一种操作,因为处理action的函数reducer只判断action中的type属性。
2. reducer
reducer 接受两个参数,state以及action函数返回的action对象,并返回最新的state,如下reducer的demo
reducer 只是一个模式匹配的东西,真正处理数据的函数,一般是额外在别的地方写的(当然直接写在reducer中也没问题,只是不利于后期维护),在 reducer 中调用罢了。
reducer 为什么叫 reducer 呢?因为 action 对象各种各样,每种对应某个 case ,但最后都汇总到 state 对象中,从多到一,这是一个减少( reduce )的过程,所以完成这个过程的函数叫 reducer。
3. getState
整个项目的
currentState
是处于一个闭包之中,所以能一直存在,getState
会返回当前最新的state。4. subscribe
subscribe
接收一个listener,它的作用是给store添加监听函数。nextListeners
储存了整个监听函数列表。subscribe
的返回值是一个unsubscribe
,是一个解绑函数,调用该解绑函数,会将已经添加的监听函数删除,该监听函数处于一个闭包之中,会一直存在,所以在解绑函数中能删除该监听函数。(由此可见redux源码设计的精巧,多处地方巧用闭包,精简了许多代码。)5. dispatch
dispatch
接收一个参数action。代码会先调用createStore
传入的参数reducer
方法,reducer
接受当前state和action,通过判断actionType,来做对应的操作,并返回最新的currentState。dispatch
还会触发整个监听函数列表,所以最后整个监听函数列表都会按顺序执行一遍。dispatch
返回值就是传入的action。6. replaceReducer
replaceReducer
是替换当前的reducer的函数,replaceReducer
接受一个新的reducer,替换完成之后,会执行dispatch({ type: ActionTypes.INIT })
,用来初始化store的状态。官方举出了三种replaceReducer
的使用场景,分别是:7. observable
这个API并不是暴露给使用者的,这个是redux内部用的,大家不用深究(千万不要死脑筋啊~~~)。什么,你不信?好吧,实话告诉你,就是内部用的,在测试代码中会用到,感兴趣的可以去test目录下查看(链接)。
2.3 combineReducers.js
这个函数可以组合一组 reducers,然后返回一个新的reducer。由于redux只维护唯一的state,随着整个项目越来越大,state状态树也会越来越庞大,state的层级也会越来越深,当某个
action.type
所对应的case 只是要修改state.a.b.c.d.e.f
这个属性时,我的 handleCase 函数写起来就非常难看,我必须在这个函数的头部验证 state 对象有没有那个属性。这是让开发者非常头疼的一件事。combineReducers
实现方法很简单,它遍历传入的reducers,返回一个新的reducer,这个新对象的 key 跟传入的reducers一样,它的 value 则是传入的reducers的不同key对应的value展开的{ key: value }
。貌似讲的有点绕啊~,举个例子好讲明白:combineReducers
内部会将state.todos属性
作为todos: (state, action)
的state参数传进去,通过switch (action.type)
之后返回的new state也会更新到state.todos 属性中;也会将state.activeFilter属性
作为activeFilter: (state, action)
的state参数传进去,通过switch (action.type)
之后返回的new state也会更新到state.activeFilter属性中。combineReducers
是有缺陷的,源码中mapValues
只是一级深度的映射,目前redux并没有提供简便的映射到state.a.b
一级以上深度的state的方法。这是它目前的不足之处。我们在不改源码的情况下,可以通过嵌套combineReducers
来达到目的。2.4 bindActionCreators.js
bindActionCreators
的代码比较简单,就是将actionCreator和dispatch联结在一起。对于多个 actionCreator,我们可以像reducers一样,组织成一个key/action
的组合。由于很多情况下,action是 actionCreator 返回的,实际上要这样调用 store.dispatch(actionCreator(...args)),很麻烦是吧?只能再封装一层呗,这就是函数式思想的体现,通过反复组合,将嵌套函数分离。(在这里,我不得不再夸一次redux的作者)2.5 compose.js
compose
的代码不难理解,它调用了ES5的Array.prototype.reduce方法,将形如fn(arg1)(arg2)(arg3)...
的柯里化函数按照顺序执行。2.6 applyMiddleware.js
顾名思义,applyMiddleware就是中间件的意思。
applyMiddleware
接收中间件为参数,并返回一个以createStore为参数的函数;同时applyMiddleware
又是createStore函数中的第三个参数,所以我们回到createStore的代码,找到了:当createStore中传了第三个参数的时候,会执行
enhancer(createStore)(reducer, preloadedState)
,这是一个柯里化函数;我们可以设想中间件的使用方法:const store = createStore( reducer, applyMiddleware([...中间件]))
。applyMiddleware([...中间件])
的返回值是一个以createStore为参数的函数,这个函数会在createStore
中执行,返回的函数也会继续执行,最后返回一个store。我们继续回到applyMiddleware
中,在返回store之前,中间件做了什么处理呢?中间件将最重要的两个方法 getState/dispatch整合出来,并传递给中间件使用,中间件处理完之后,返回一个新的dispatch。这里又有疑问,为什么中间件要放在dispatch的时候?借用阮老师的一张图:applyMiddleware
把中间件放在一个chain数组中,并通过compose
方法(我们上面已经介绍过了),让每个中间件按照顺序一次传入diapatch
参数执行,再组合出新的 dispatch。由此可见,每个中间件的格式都应该是接收一个{ dispatch, getState }
,返回一个(dispatch) => { return function(action) { ... } }
3、工作流程
通过熟悉redux的源码,我们也对redux的代码结构本身有了一个完整的理解,我们可以理解redux的整个工作流程:
4、结尾
笔者认为redux非常优秀,而且API也非常稳定,同时facebook又推出了适合react项目的react-redux,也说明了redux的强大;redux是前段不可或缺的技术,衷心希望大家能够谈笑风生的学习和使用。
最后向大家安利一款自己用node写的一个react+webpack的脚手架,戳我→
The text was updated successfully, but these errors were encountered: