redux源码结合实践深入解析
2020/4/20 11:18:11
本文主要是介绍redux源码结合实践深入解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
背景
redux作为前端状体管理中最亮眼的那个仔,非常有必要弄清楚他的原理。本文将从源码结合实践一起来重新认识redux。纯干货分享!!!
redux相对来讲是相对比较复杂的状态管理工具。实现一个全局状态管理工具,通过一个全局变量和一些方法,即可实现的东西,那么为什么redux需要提出action,store,dispatch,reducer等一系列概念?提出这些概念的作用或者说动机是什么?希望读者能从这篇文章中深入理解这些概念存在的价值和意义。
export const store = createStore( reducer, applyMiddleware(sagaMiddleware) ); 复制代码
我们经常看到这段代码,本文将从以createStore作为入口顺藤摸瓜带你认识整个框架。下面源码是v3.7.2版本的代码。
createStore
首先来看createStore函数源码, 为了方便理解和阅读省略了很多无关的代码,大家在阅读的时候可以折叠起来看。
export default function createStore(reducer, preloadedState, enhancer) { // 如果只有两个参数,并且第二个参数是函数的话,将会传递给enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined // 省略一堆判断逻辑 return enhancer(createStore)(reducer, preloadedState) } // 一堆方法定义 dispatch({ type: ActionTypes.INIT }); return { dispatch, // 重点讲述 subscribe, // 重点讲述 getState, // 返回state的方法 replaceReducer, // 高级用法,目的在于分包时,动态更换reducer [$$observable]: observable } } 复制代码
- 从代码中可以看到store的返回值是一个对象,具有多种方法
- enhancer的作用是功能扩展,返回值是一个store, enhancer函数的写法举例
function myEnhancer(createStore){ return (reducer, preloadedState, enhancer) => { //创建store之前, do someSting const store = createStore(reducer, preloadedState, enhancer) //store之后, do something return store; } } 复制代码
- store创建之后,就会dispatch一个默认的初始action,来做初始化。这步操作可以类比与函数自执行,目的是为了让每个reducer返回他们默认的state构成初始全局state。
- 全局state其实就是一个普通对象函数,其他操作都是来辅助管理该state
dispatch
dispatch是我们的重头戏,后面还是介绍他,我们先看下,当我们dispatch({ type: 'INCREACE', payload: 1})会发生些什么呢。
function dispatch(action) { // 各种检查acton类型 try { isDispatching = true // currentState是原来的state // currentReducer就是一开始createStore时传入的reducer currentState = currentReducer(currentState, action) // reducer之后返回的是更新后的新的state } finally { isDispatching = false } // 更新监听者函数 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } 复制代码
- dispatch触发一个action,执行reducer,然后更新监听者,最后返回action本身。这里为什么要返回action呢?答案是为了中间件的链式复合,在中间件部分会详细解释。
- action的类型检查中要求,action必须是一个普通对象,必须有type属性
- reducer是一个函数,接收两个参数state和action,并返回新的state,初始化时,state可能是undefined,因此通过触发默认action,来返回reducer的初始state。reducer常见格式如下:
function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ] case 'COMPLETE_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } 复制代码
- reducer执行完成后,更新state,然后触listeners函数,没有任何参数,其中一个的应用是react-redux中的高阶组件connect更新机制,后面会深入解析react-redux,请持续关注哦!
subscribe
subscribe是一个简单的监听者模式,该函数主要是收集监听者。源码很简单如下
function subscribe(listener) { // 检查listener类型 let isSubscribed = true ensureCanMutateNextListeners() // 该函数会复制一份currentListeners // 保障更新期间其他listener不受影响 nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } // 省略部分错误检查 isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) currentListeners = null // 下次运行时 currentListeners会重新从nextListeners中取,可以看dispatch的代码 // 作者这样做的目的主要是为了防止dispatch执行期间发生subscribe或者unsubscribe引发异常错误 } } 复制代码
到这里,整个redux的核心功能就介绍的差不多了,但是redux的威力并没有体现出来,接下来我们将介绍redux的扩展功能中间件。
applyMiddleware
该函数是一个enhancer函数,由redux实现提供, 用来嵌入中间件,也是我们经常使用的一个工具函数。
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) // 特别注意这个dispatch是使用let赋值的 // 这个预定义是为了防止用户提前使用,此时无法触发其他中间件 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) // 这个dispatch方法不能在next函数前使用 } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } 复制代码
- 输入参数是一些列中间件,返回值是一个store对象(可以对照createStore的代码),dispatch函数进行了封装。
- compose函数实现了一个简单的洋葱模型,上一个函数的输入作为下一个函数的输出,后面会详细介绍。
- 从代码中发现,每个中间件会输入getState和dispatch对象,返回值需要满足compose函数要求。举例如下,下面例子中可以记录每个action到更新state所花费的时间。
function loggerMiddleware({getState, dispatch}){ // 这部分对应的是middleware(middlewareAPI) // 这块区域不能使用dispatch函数,否则会抛出错误!! return next => action => { console.time(action.type); const result = next(action); // result 对象是一个action类型的对象,如果中间件未修改过该值,则全等,一般来讲,action不应该被修改 console.timeEnd(action.type); return result; // 将会传入下一个中间中 } } 复制代码
在书写中间件的时候,我们发现内部闭包了多个函数,如果部分函数采用async等方式的话,就可以实现异步操作,解决副作用的问题,redux-thunk正是借用这种方式实现,感兴趣的同学可以学习下,代码只有14行,这里就不展开讨论了。
- next函数是上一个中间件的返回值,是上一个中间件封装后返回的dispatch,next(action)的作用相当于dispatch(action),他会触发后续的中间件,因此next命名比较形象
compose
compose是一个函数构造器,返回一个新的函数。类似数学中的函数f(x),g(x),h(x)复合为f(g(h(x)))。上一个函数的输出作为下一个函数的输入。
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))) } 复制代码
- 出于js单个返回值的限制,每个函数的参数只能有一个
- 如果参数x是一个值的时候,compose函数执行后也会得到一个值, 即 compose(a,b,c)(x)也会返回一个值。举例(九折后满500再减50):
function couponA(next) { if(next >= 500){ return next - 50; } return x; } function couponB(next){ return next * 0.9; } const discount = compose(couponA, couponB); discount(1000); // 850 复制代码
当参数是一个值的时候,无法实现回旋镖的形式。上述例子其实是一个简单的职责链模式,感兴趣的可以深入挖掘,在电商打折规则中特别实用
- 如果参数是一个函数的时候,每个中间件也返回一个函数,如applyMiddleware中的dispatch。由于dispatch是一个函数,可以利用函数调用时执行的特点,实现回旋镖型的中间件,如上述loggerMiddleware,可以记录dispatch所花费的时间。
- compose函数中处理函数是从右向左执行,即最后一个函数先执行。
combineReducers
这是一个工具函数,可以将多个reducer聚合起来,返回值是一个reducer(这是一个函数)
// reducers是一个 export default function combineReducers(reducers) { // 省略对reducers做了一堆检查 // 下面这句是为了好理解,我杜撰的,非真实源码 const finalReducers = {...reducers} const finalReducerKeys = Object.keys(finalReducers); 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] // 这里以key划分命名空间,previousStateForKey为指定key下的state const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { // 每个reducer都应该有返回值 const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } } 复制代码
- 使用combineReducers后,对应的state也具有与reducers对象具有相同的结构。
bindActionCreators
该函数是redux提供的一个工具函数,首先要弄清楚action和actionCreator的关系。action是一个普通对象,actionCreator是一个构造action对象的函数
bindActionCreator的目的是将actionCreator与dispatch结合构造出一个能够直接触发一系列变化的Action方法 bindActionCreators就是将多个actionCreator转化为Action方法
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } export default function bindActionCreators(actionCreators, dispatch) { // 省略一系列检查 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 } 复制代码
在实践中,结合reat-redux的connect的第二个参数mapDispatchToProps为例,展示actionCreators转化为可以直接运行的方法。
const actionCreators = { increase: (payload) => ({ type: 'INCREASE', payload }), decrease: (payload) => ({ type: 'DECREASE', payload }) } @connect( state => state, dispatch => ({ actions: boundActionCreators(actionCreators, dispatch) }) ) class Counter { render(){ <div> <button onClick={() => this.props.actions.increase(1)}>increase</button> <button onClick={() => this.props.actions.decrease(1)}>decrease</button> </div> } } 复制代码
总结
- redux管理状态是通过一个currentState对象来存储全局状态,但是将修改状态拆分为了dipatch(action)和reducer两部分,大大提高工具库的灵活性和想象空间。
- 理解并学会redux中间件的写法,更加深入了解compose函数
- redux相对比较复杂,但在其基础上衍生了大量的第三方工具库,足见其生命力,在实践中体会作者架构的深意。
- 为了便于理解,删除了很多类型判断,这些类型判断能够帮助开发者更好的调试代码,同样非常重要,大家在自己研究源码时,不要忽视这些细节。
- 文章中包含了自己大量的理解,描述和理解有不妥之处,请批评指正!!!
这篇关于redux源码结合实践深入解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南