Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理,也就是说Redux不单单只能在React中使用,可以在Vue、Angular等框架中当成状态容器来使用,也可以单独使用,如同JQuery就是一个库,而不是像Vuex这种需要依赖Vue的状态管理容器。
参考:https://zhuanlan.zhihu.com/p/20597452
在学习Redux前先看下Redux架构的工作流程图。Store代表容器,UI表示视图,订阅了State;Dispatch则起到发布Action更新State,订阅了State的视图就会更新页面。在这个过程中,State是不能被直接修改的,只能通过Dispatch发布Action更新State,然后Action会被Reducer接受并进一步判断操作,执行对State的处理,最终返回新的状态,然后同步到视图层上,整个过程是单向数据流的。
1. 基础示例应用
让我们看一个Redux应用最小的示例 - 计数器应用程序;示例里使用了script标签去加载了Redux库,用了基础的JS、HTML去写UI
将示例的代码按步骤拆开,可以看到首先是定义了状态的默认值
// 应用状态初始状态值 const initialState = { count: 0 }
接着,定义了一个reducer方法,接受两个固定形式参数,一个是初始状态,另外一个是描述发生了什么的action对象。当应用启动时,还没有任何状态,所以我们提供initialState
作为该 reducer 的默认值。
// 创建一个“reducer”函数来确定应用程序中发生某些事情时的新状态 function reducer(state = initialState, action) { // Reducers 通常会查看发生的action 的 type 来决定如何更新状态 switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: // 如果 reducer 不关心这个action type,原样返回现有状态 return state } }
紧接着通过将reducer作为参数传递给createStore创建store实例,如果存在多个reducer,需要通过combineReducers合并处理;然后在将返回的函数作为createStore的实参创建store实例;最后是定义Action对象。
// 如果存在多个reducer,需要合并处理 // const combineReducer = Redux.combineReducers({ reducer }); // var store = Redux.createStore(combineReducer); // 3. 创建 store 对象 var store = Redux.createStore(reducer); // 4. 定义 action var increment = { type: "increment" }; var decrement = { type: "decrement" };
最后就是通过dispatch发布action更新state
// 5. 获取按钮 给按钮添加点击事件 document.getElementById("plus").onclick = function () { // 6. 触发action store.dispatch(increment); }; document.getElementById("minus").onclick = function () { // 6. 触发action store.dispatch(decrement); }; // 7. 订阅 store store.subscribe(() => { // console.log(store.getState().reducer.count); // 合并多个reducer // 获取store对象中存储的状态 // console.log(store.getState()); document.getElementById("count").innerHTML = store.getState().count; });
一个完成的示例就完成了,虽然内容有限,但是它确实展示了真正的 Redux 应用程序的所有工作部分,并且无论是多复杂的过程,都是基于以上基础进行扩展的。
2. Redux的核心API
createStore:接受函数参数,创建Store状态容器;
combineReducers:如果有多个reducer,通过combineReducers合并;
bindActionCeators:bindActionCeators函数接受action和dispatch,并返回一个对象,实际是将dispatch和每一个action建立了关系;
applyMiddleware:接受函数参数;使用包含自定义功能的middleware来扩展Redux的方式,比如用到的redux-thunk、redux-saga;
compose:接受多个函数,并且从右到左组合成最终函数返回;当需要多个middleware的时候需要用到它
getState:Store实例上的函数,可以获取状态;
subscribe:订阅状态,是Store实例上的函数,接受一个回调函数作为参数,当容器中的状态发生改变就会触发回调;
dispatch:发布action,Store实例上的函数,接受action对象;
3. createStore原理
createStore接受reducer创建Store状态容器,那么createStore到底是怎么实现的?,让我们从源码上来理解下
function createStore(reducer, preloadedState, enhancer) { // ... 省略 function dispatch(){} function subscribe(){} function replaceReducer(){} function getState(){} function observable(){} dispatch({ type: ActionTypes.INIT }) // 默认会发起一次 return { dispatch, subscribe, replaceReducer, getState, [$$observable]: observable, } }
reducer也就是自定义的reducer;preloadedState会作为自定义reducer的默认状态,如果自定义的reducer有传入了默认值,也会以preloadedState优先;enhancer注册插件,比如后续会讲到的redux-thunk、redux-saga;然后createStore函数最终返回一个对象,到这里清楚的知道,我们调用的dispatch、subscribe、getState其实就是返回的对象的属性值。
从上面我们可以清除的知道,createStore函数其实只做了两件事,声明函数和发起一次dispatch({ type: ActionTypes.INIT }),那么dispatch具体又做了哪些事,请往下看;
function dispatch(action) { // ... 省略 try{ isDispatching = true // currentState是preloadedState currentState = currentReducer(currentState, action) }finally{ isDispatching = false } // nextListeners 是 subscribe 发布的回调函数,每执行dispatch就会执行 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
currentReducer是我们传递的reducer(赋值处理,没有任何改动),这里也就解释了为什么自定义的reducer必须要有两个函数,同时,每当执行dispatch发布action的时候,也会处理subscribe发布的回调函数,并且,通过getState函数可以拿到最新的计算结果,从上面可以知道,reducer的结果最终会赋值给currentState,getState函数其实就是将currentState返回了。
我们也可以通过replaceReducer替换之前注册reducer,只需要将自定义的reducer传入replaceReducer中即可实现,replaceReducer的实现也很简单
function replaceReducer(nextReducer) { // ... 省略 currentReducer = nextReducer // reducer 替换成新的了 dispatch({ type: ActionTypes.REPLACE }) }
至于observable,还没有明确的说明,作者也只是提及了和ES提案有关:https://github.com/tc39/proposal-observable
4. combineReducers && bindActionCeators使用和原理
1. combineReducers
随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分,这也是前面说的如果存在多个reducer情况,需要通过combineReducers合并,跟着下面的例子,深入了解下combineReducers的使用和工作原理。
combineReducers接受一个对象形参,也就是我们自定义的reducer函数,并且最终返回一个函数,返回的函数同样接受两个形参(state、action,是不是和自定义reducer一样),那么combineReducers是如何工作的呢?让我们跟着源码来分析。
function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] // ... 省略 if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) // This is used to make sure we don't warn about the same // keys multiple times. let unexpectedKeyCache let shapeAssertionError try{ assertReducerShape(finalReducers) // 验证reducers是否正常返回结果 } catch (e) { shapeAssertionError = e } return function combination(state = {}, action) { // ... 省略 } }
从上面可以分析得出combineReducers做了四件事,1. 收集reducer到finalReducers,2.定义了unexpectedKeyCache和shapeAssertionError,3. 执行assertReducerShape,为了验证自定义reducer是否正常返回结果,最后就是返回combination函数,也就是传入createStore其实就是combination函数,可以看到combination函数和自定义reducer很相似,都是接受一个默认状态和action,那combination又是如何工作的?
function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } // .. 省略 let hasChanged = false const nextState = {} // 遍历执行每个reducer 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) // ... 省略 nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : state }
其实combination就是遍历了每个reducer得到新的状态,然后保存到nextState,而previousStateForKey就是上一次状态,是createStore中的currentState根据key值得到,action就是通过dispatch发布的行为,reducer接受到然后执行state的处理,最终返回nextState覆盖currentState,以上就是combination的原理。
2. bindActionCreators
一般情况下开发者是可以直接在在Store实例上调用dispatch,不过当需要将action creator往下传到组件上时,并且不像让组件察觉到Redux的存在,甚至不希望直接把dispatch或者Redux Store传给它时,这时候就需要bindActionCreators出场了。可能上面描述有些复杂,结合下面的内容会更加好理解。
跟着例子来看下bindActionCreators;
根据上面的两个例子对比,在结合上面那句话,可以知道,bindActionCreators的一个作用就就是规避了我们直接在组件上使用dispatch来派发action,只需要将自定义的action和dispatch作为bindActionCreators的实参,最后返回对象,这样我们只需要在组件内调用同action同名的函数就行,也可以传入一个函数,不过该函数就只能是一个action函数,并且也只能是一个action。让我们通过源码来分析下bindActionCreators的工作流程吧
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } // ...省略 // 传入对象 const boundActionCreators = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
根据上面的源码知道bindActionCreators不仅可以接受函数,也可以接受;并且都通过bindActionCreator处理了一层,所以 如果传入的是函数,就会当成是处理单个action,如果是对象,处理的是多个action,并将最终的结果返回,如此一来也就解释了我们开头那段话。
5. redux如何处理异步?
我们知道dispatch发布action后,reducer会立即计算并返回新的state,这是同步的过程,如上图所示,但如果想在 Action 发起之后, 过一段时间再执行 reducer 计算 state, 即 异步计算 state, 该如何操作呢?或许可以直接在组件中的异步函数函数中使用dispatch,如同例2一样,需要显示的在组件中注入dispatch,不过事实上是异步函数也可能是我们状态管理的一部分,更好的管理方式应该是交给redux来管理会更合适;
上图和首图的区别在于上图增加了中间件(middleware),首先是dispatch 发布的值先到达 middleware,middleware调用完成后再 dispatch 一个正真的 action 对象;
所以middleware是一种作为扩展redux应用的方式存在的,它可以通过包装store的dispatch来达到自己想要的目的,也即是说middleware是通过扩展store来实现自己想要的功能,它允许dispatch action 时执行额外的逻辑;暂停、修改、延迟、替换或停止 dispatch 的 action;编写可以访问 dispatch 和getState的额外代码;教dispatch 如何接受除普通 action 对象之外的其他值,例如函数和 promise,通过拦截它们并 dispatch 实际 action 对象来代替。
如果有多个middleware可以一起使用,形成middleware链表,每个middleware都不需要关心前后middleware的信息。
reducer有多种异步 middleware,每一种都允许你使用不同的语法编写逻辑。最常见的异步 middleware 是 redux-thunk
、redux-saga
、 redux-observable
;
根据例3,redux使用中间件的需要先通过applyMiddleware注册,然后再将结果传给createStore,那applyMiddleware又是如何工作的?
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))) } function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) 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) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware其他就是返回了一个函数,然后接收createStore返回新函数,新函数接收自定义reducer,并返回对象;这里的dispatch是经过处理的,这也就解释了开头的那段话:middleware允许dispatch action 时执行额外的逻辑;暂停、修改、延迟......
1. 组合串联middleware
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 重写dispatch dispatch = middleware(middleware(middleware(store.dispatch))) dispatch = compose(...chain)(store.dispatch)
applyMiddleware重点在于compose重写了dispatch,compose将多个middleware串成一个函数,上一个middleware和下一个middleware通过next建立关系,并将结果对dispatch重新赋值,每次执行dispatch虽然是从最外层的middleware开始的,但是会将action通过next传下一个middleware,最后一个middleware中才是真正触发store.dispatch,然后将上一个middleware的结果作为下一个middleware的参数,这样就依次执行了每个middleware,所以redux中间件模型也被称为洋葱模型。
6. 扩展
Redux Toolkit 是 Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集。它旨在成为标准的 Redux 逻辑开发模式。
标签:随笔,函数,middleware,reducer,dispatch,action,Redux,store From: https://www.cnblogs.com/goather/p/17318003.html