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
importcreateStorefrom'./createStore'importcombineReducersfrom'./combineReducers'importbindActionCreatorsfrom'./bindActionCreators'importapplyMiddlewarefrom'./applyMiddleware'importcomposefrom'./compose'importwarningfrom'./utils/warning'import__DO_NOT_USE__ActionTypesfrom'./utils/actionTypes'/* * This is a dummy function to check if the function name has been altered by minification. * If the function has been minified and NODE_ENV !== 'production', warn the user. */// 是否压缩代码,如果运行环境在非生成环境但是代码被压缩了,警告用户functionisCrushed(){}// 判断环境是否是生成环境,如果是生成环境使用此代码就给出警告提示if(process.env.NODE_ENV!=='production'&&typeofisCrushed.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 setting mode to production in webpack (https://webpack.js.org/concepts/mode/) '+'to ensure you have the correct code for your production build.')}export{createStore,combineReducers,bindActionCreators,applyMiddleware,compose,__DO_NOT_USE__ActionTypes}
/** * @inputState 初始化的state * @reducers 已经过过滤的reducers * @action 随着combinRecuers传入的action * @unexpectedKeyCache 开发者环境是一个空的对象,生成环境是undefined *//** * 检查合法的reducers是否存在 * * */functiongetUnexpectedStateShapeWarningMessage(inputState,reducers,action,unexpectedKeyCache){// 将过滤的reducers的名取出constreducerKeys=Object.keys(reducers)constargumentName=// 如果这个action的type是预制的ActionTypes.INIT// argumentName就是preloadedState argument passed to createStore// 不然是previous state received by the reduceraction&&action.type===ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'// 如果过滤后的reducer长度为0// 则返回字符串告知没有一个合法的reducer(reducer必须是function类型)if(reducerKeys.length===0){return('Store does not have a valid reducer. Make sure the argument passed '+'to combineReducers is an object whose values are reducers.')}// 判断输入的state是否是obj对象// 如果不是 则返回字符串告知inputState不合法if(!isPlainObject(inputState)){return(`The ${argumentName} has unexpected type of "`+{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1]+`". Expected argument to be an object with the following `+`keys: "${reducerKeys.join('", "')}"`)}// 传入的state进行遍历// 如果state的对象名不包含在reducer中 并且不包含在unexpectedKeyCache对象中// unexpectedKeyCache在开发者环境是一个空的对象 因此只要state的对象名不包含在reducer中,这个key就会// 保存到 unexpectedKeys 当中constunexpectedKeys=Object.keys(inputState).filter(key=>!reducers.hasOwnProperty(key)&&!unexpectedKeyCache[key])// 将inputState的key全部设置为trueunexpectedKeys.forEach(key=>{unexpectedKeyCache[key]=true})// 如果这个action的type是定义的定义中的ActionTypes.REPLACE 就返回不执行if(action&&action.type===ActionTypes.REPLACE)return// 如果unexpectedKeys中有值,则发出警告if(unexpectedKeys.length>0){return(`Unexpected ${unexpectedKeys.length>1 ? 'keys' : 'key'} `+`"${unexpectedKeys.join('", "')}" found in ${argumentName}. `+`Expected to find one of the known reducer keys instead: `+`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`)}}
import$$observablefrom'symbol-observable'importActionTypesfrom'./utils/actionTypes'importisPlainObjectfrom'./utils/isPlainObject'exportdefaultfunctioncreateStore(reducer,preloadedState,enhancer){// 如果初始化的state是一个方法并且enhancer也是方法就会报错// 如果enhancer是方法如果第4个参数是方法就会报错if((typeofpreloadedState==='function'&&typeofenhancer==='function')||(typeofenhancer==='function'&&typeofarguments[3]==='function')){thrownewError('It looks like you are passing several store enhancers to '+'createStore(). This is not supported. Instead, compose them '+'together to a single function')}// 如果初始化的state是方法,enhancer的参数为undefinedif(typeofpreloadedState==='function'&&typeofenhancer==='undefined'){// enhancer赋值初始话的staeenhancer=preloadedState// preloadedState赋值为undefinedpreloadedState=undefined// 这里是一个兼容2个参数的处理,当参数仅为2个 第二个参数为enhcaner时的处理}// 如果enhancer 不是undefinedif(typeofenhancer!=='undefined'){// 如果enhancer不是方法会报错if(typeofenhancer!=='function'){thrownewError('Expected the enhancer to be a function.')}// 返回enhancer的方法returnenhancer(createStore)(reducer,preloadedState)}// 如果reducer不是方法 则报错if(typeofreducer!=='function'){thrownewError('Expected the reducer to be a function.')}// rootReducer赋值到currentReducer当中 实际是一个函数letcurrentReducer=reducer// 当前store中的state 默认是初始化的stateletcurrentState=preloadedStateletcurrentListeners=[]letnextListeners=currentListenersletisDispatching=falsefunctionensureCanMutateNextListeners(){if(nextListeners===currentListeners){// 浅拷贝一个数组 虽然是浅拷贝 但是currentListener不会被nextListener改变nextListeners=currentListeners.slice()}}functiongetState(){// 省略代码...}functionsubscribe(listener){// 省略代码...}functiondispatch(action){// 省略代码...}functionreplaceReducer(nextReducer){// 省略代码...}functionobservable(){// 省略代码...}// When a store is created, an "INIT" action is dispatched so that every// reducer returns their initial state. This effectively populates// the initial state tree.// 执行dispatch 来初始化store中的statedispatch({type: ActionTypes.INIT})return{
dispatch,
subscribe,
getState,
replaceReducer,[$$observable]: observable}}
看完之后,我们可能在这个地方有一点疑惑,就是这里
// 如果enhancer 不是undefinedif(typeofenhancer!=='undefined'){// 如果enhancer不是方法会报错if(typeofenhancer!=='function'){thrownewError('Expected the enhancer to be a function.')}// 返回enhancer的方法returnenhancer(createStore)(reducer,preloadedState)}
importcomposefrom'./compose'/** * 创建一个store的增强器,使用中间件来包装dispath方法,这对于各种任务来说都很方便 * 比如以简洁的方式进行异步操作,或记录每个操作有效负载 * * 查看`redux-thunk`包,这是一个中间件的例子 * * 因为中间件可能是异步的,所以应该是对个enhancer传参 * * 每一个中间件都要提供dispatch和getstate两个方法作参数 * */exportdefaultfunctionapplyMiddleware(...middlewares){returncreateStore=>(...args)=>{// 创建一个store ...args为reducer, preloadedStateconststore=createStore(...args)// 默认定义disptach方法,是一个抛出的报错letdispatch=()=>{thrownewError(`Dispatching while constructing your middleware is not allowed. `+`Other middleware would not be applied to this dispatch.`)}// 中间件的的参数constmiddlewareAPI={getState: store.getState,dispatch: (...args)=>dispatch(...args)}// 将所有的中间件遍历,将参数传入到中间件函数中,返回一个中间件函数的数组constchain=middlewares.map(middleware=>middleware(middlewareAPI))// dispatch做了包装,会在dispatch的同时同时将中间件的方法也返回dispatch=compose(...chain)(store.dispatch)// 返回store中的属性以及新的dispatch方法return{
...store,
dispatch
}}}
/** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */functiongetState(){if(isDispatching){thrownewError('You may not call store.getState() while the reducer is executing. '+'The reducer has already received the state as an argument. '+'Pass it down from the top reducer instead of reading it from the store.')}// 返回statereturncurrentState}
subscribe
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
functionsubscribe(listener){// listener必须是一个方法if(typeoflistener!=='function'){thrownewError('Expected the listener to be a function.')}if(isDispatching){thrownewError('You may not call store.subscribe() while the reducer is executing. '+'If you would like to be notified after the store has been updated, subscribe from a '+'component and invoke store.getState() in the callback to access the latest state. '+'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}letisSubscribed=trueensureCanMutateNextListeners()// 把listenr加入到nextListeners的数组当中nextListeners.push(listener)// 解除观察returnfunctionunsubscribe(){if(!isSubscribed){return}if(isDispatching){thrownewError('You may not unsubscribe from a store listener while the reducer is executing. '+'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}isSubscribed=false// 这里做了个拷贝 做的所有操作不影响currentListenerensureCanMutateNextListeners()constindex=nextListeners.indexOf(listener)// 在nextListener将它去除nextListeners.splice(index,1)}}
functiondispatch(action){// 如果dispatch的参数不是actionif(!isPlainObject(action)){thrownewError('Actions must be plain objects. '+'Use custom middleware for async actions.')}// action必须得有type属性,如果没有会报错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=true// 执行reducer 遍历过滤后的reducer,随后依次赋值到state当中currentState=currentReducer(currentState,action)}finally{isDispatching=false}// 获取当前的监听器constlisteners=(currentListeners=nextListeners)for(leti=0;i<listeners.length;i++){constlistener=listeners[i]// 依次执行监听器回调listener()}// dispatch默认返回actionreturnaction}
replaceReducer
/** * Replaces the reducer currently used by the store to calculate the state. * * You might need this if your app implements code splitting and you want to * load some of the reducers dynamically. You might also need this if you * implement a hot reloading mechanism for Redux. * * @param {Function} nextReducer The reducer for the store to use instead. * @returns {void} *//** * 替换reducer * * 动态替换原有的reducer */functionreplaceReducer(nextReducer){if(typeofnextReducer!=='function'){thrownewError('Expected the nextReducer to be a function.')}// 将reducer赋值currentReducer=nextReducer// 发送一个dispatch 随后重置storedispatch({type: ActionTypes.REPLACE})}
/** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */functionobservable(){constouterSubscribe=subscribereturn{/** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. *//** * @param {Object} 任何对象都可以当做observer * observer应该有一个`next`方法 * @returns {subscription} 一个对象,它有`unsubscribe`方法能够 * 用来从store中unsubscribe observable */subscribe(observer){// observer必须是一个非空的objectif(typeofobserver!=='object'||observer===null){thrownewTypeError('Expected the observer to be an object.')}functionobserveState(){if(observer.next){observer.next(getState())}}observeState()constunsubscribe=outerSubscribe(observeState)return{ unsubscribe }},[$$observable](){returnthis}}}
bindActionCreators
在讲这个方法前,先看下文档对它的使用说明
Example
TodoActionCreators.js
我们在文件中创建了2个普通的action。
exportfunctionaddTodo(text){return{type: 'ADD_TODO',
text
}}
exportfunctionremoveTodo(id){return{type: 'REMOVE_TODO',
id
}}
SomeComponent.js
import{Component}from'react'import{bindActionCreators}from'redux'import{connect}from'react-redux'
import*asTodoActionCreatorsfrom'./TodoActionCreators'console.log(TodoActionCreators)// {// addTodo: Function,// removeTodo: Function// }
classTodoListContainerextendsComponent{constructor(props){super(props)
const{ dispatch }=props
// Here's a good use case for bindActionCreators:// You want a child component to be completely unaware of Redux.// We create bound versions of these functions now so we can// pass them down to our child later.
this.boundActionCreators=bindActionCreators(TodoActionCreators,dispatch)console.log(this.boundActionCreators)// {// addTodo: Function,// removeTodo: Function// }}
componentDidMount(){// Injected by react-redux:let{ dispatch }=this.props
// Note: this won't work:// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.// You must dispatch the action, too!
// This will work:letaction=TodoActionCreators.addTodo('Use Redux')dispatch(action)}
render(){// Injected by react-redux:let{ todos }=this.props
return<TodoListtodos={todos}{...this.boundActionCreators}/>
// An alternative to bindActionCreators is to pass// just the dispatch function down, but then your child component// needs to import action creators and know about them.
// return <TodoList todos={todos} dispatch={dispatch} />}}
exportdefaultconnect(state=>({todos: state.todos}))(TodoListContainer)
bindActionCreators.js
我们接下来来看它的源码
/** * * 在看`bindActionCreator`方法之前可以先看`bindActionCreators`方法 * * @param {Function} actionCreator 实际就是 action * * @param {Function} * * @returns {Function} */functionbindActionCreator(actionCreator,dispatch){returnfunction(){// 返回的是dispathreturndispatch(actionCreator.apply(this,arguments))}}// actionCreators是一个包含众多actions的对象exportdefaultfunctionbindActionCreators(actionCreators,dispatch){if(typeofactionCreators==='function'){// actionCreators是函数就代表他是单一的action方法returnbindActionCreator(actionCreators,dispatch)}// actionCreator如果不是object 或者它是空的则报错if(typeofactionCreators!=='object'||actionCreators===null){thrownewError(`bindActionCreators expected an object or a function, instead received ${actionCreators===null ? 'null' : typeofactionCreators}. `+`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`)}// 将action的keys遍历出来constkeys=Object.keys(actionCreators)constboundActionCreators={}for(leti=0;i<keys.length;i++){// 每个action的keyconstkey=keys[i]// 将action取出 这是一个方法constactionCreator=actionCreators[key]if(typeofactionCreator==='function'){// bindActionCreator返回的是dispatch的返回值// 实际是action 所以boundActionCreators是一个dispatch function的对象// 同时如果key相同会被覆盖boundActionCreators[key]=bindActionCreator(actionCreator,dispatch)}}returnboundActionCreators}
redux 源码分析
背景
在之前的文章Redux从入门到实践当中对redux的使用进行了说明,这次就来看下它的源码,从而进一步的熟悉它。
构建
相关git地址
构建文档是
CONTRBUTING.md
package.json
从
package.json
当中可以看到redux
的入口文件是lib/redux.js
,这个文件是通过打包出来的。那我们看下打包配置文件rollup.config
可以看到入口文件应该是
src/index.js
我们来看下
src/index.js
src/index.js
主要是将方法暴露出来,给使用者使用createStore
用于创建storecombineReducers
用于组合成rootReducers,因为在外部初始化store时,只能传入一个reducersbindActionCreators
组装了dispatch方法applyMiddleware
合并多个中间件compose
将中间件(middleware)和增强器(enhancer)合并传入到createStore
中combineReducers
src/combineReducers.js
combineReducers
方法会先对传入的reducers进行校验,reducer的类型只能是function
,最后返回的是个方法,这个方法很关键,因为在disptach时,最后执行的就是这个方法。这个方法有2个参数state
和action
,方法内会根据传入的action返回state,最后会比较新旧的state,如果不相等,则会返回新的state,如果相等会返回新的state。那么如果我们直接对store的state进行操作而不是通过dispatch会发生呢,比如说我们这样
我们看一下
combineReducers
中的getUnexpectedStateShapeWarningMessage
这个方法,它会检查store中初始化的state的key有没有在各个子reducer当中,如果没有就会报错。compose
compose会返回一个方法,这个方法可以将传入的方法依次执行
createStore
我们接下来看下
createStore.js
这个文件,它只暴露出了createStore的方法,在createStore
中,初始化了一些参数,同时返回了一个store,store中包括了dispatch
,subscribe
,getState
,replaceReducer
,[$$observable]: observable
看完之后,我们可能在这个地方有一点疑惑,就是这里
这个返回的是什么呢,我们知道
applyMiddleware
返回的其实就是enhancer,那我们结合在一起看一下applyMiddleware
如果直接返回了
enhancer
那么返回的其实也是store
,但是这个store
中的dispatch
被包装过,当dispatch
被执行时,会将所有中间件也依次执行。接下来分析一下
createStore
中的方法getState
很简单,就是返回
currentState
subscribe
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
dispatch
dispatch首先会检查参数,随后会执行
currentReducer(currentState, action)
,而这个方法实际就是combineReducers
的replaceReducer
observable
这里不谈太多observable
这里有个使用例子
bindActionCreators
在讲这个方法前,先看下文档对它的使用说明
Example
TodoActionCreators.js
我们在文件中创建了2个普通的action。
SomeComponent.js
bindActionCreators.js
我们接下来来看它的源码
使用bindActionCreators实际可以创建一个充满dispatch方法的对象。然后可以将这个对象传递子组件来使用。
总结
看完源码后我们大致了解到为什么reducer必须是function,store中的state为什么会创建和reducer相应的对象名的state,为什么只能通过dispatch来对store进行操作。另外redux的一个核心不可变性,redux本身并不能保证。所以我们在自己写的reducer当中必须要保证不能改变store原有的对象,必须得重新创建。
广而告之
本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。
欢迎讨论,点个赞再走吧 。◕‿◕。 ~
The text was updated successfully, but these errors were encountered: