Redux运用中的几个点:
- Redux三大设想准绳
- Create Store
- Redux middleware
- combineReducer
- Provider与Connect
- Redux流程梳理
- Redux设想特征
1. Redux三大设想准绳
1. 单一数据源
在传统的 MVC 架构中,我们能够依据须要建立无数个 Model,而 Model 之间能够互相监听、触发事宜以至轮回或嵌套触发事宜,这些在 Redux 中都是不许可的。由于在 Redux 的头脑里,一个运用永久只需唯一的数据源。
实际上,运用单一数据源的优点在于全部运用状况都保留在一个对象中,如许我们随时能够提掏出全部运用的状况举行耐久化(比方完成一个针对全部运用的立即保留功用)。另外,如许的设想也为服务端衬着供应了能够。
2. 状况是只读的
在 Redux 中,我们并不会本身用代码来定义一个 store。取而代之的是,我们定义一个 reducer,它的功用是依据当前触发的 action 对当前运用的状况(state)举行迭代,这里我们并没有直接修正运用的状况,而是返回了一份全新的状况。
Redux 供应的 createStore 要领会依据 reducer 天生 store。末了,我们能够应用 store. dispatch
要领来到达修正状况的目标。
3.状况修正均由纯函数完成
在 Redux 里,我们经由历程定义 reducer 来肯定状况的修正,而每一个 reducer 都是纯函数,这意味着它没有副作用,即接收肯定的输入,必定会取得肯定的输出。
如许设想的优点不仅在于 reducer 里对状况的修正变得简朴、地道、可测试,更有意义的是,Redux 应用每次新返回的状况天生酷炫的时刻游览(time travel)调试体式格局,让跟踪每一次由于触发 action 而转变状况的结果成为了能够。
2.Create Store
我们从store的降生最先提及。create store函数API文档以下:
createStore(reducer, [initialState], enhancer)
能够看出,它接收三个参数:reducer、initialState 和 enhancer 。Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 类似,它也许可你经由历程复合函数转变 store 接口。
再来看看他的返回值:
{
dispatch: f (action),
getState: f (),
replaceReducer: f (nextReducer),
subscribe: f (listener),
Symbol(observable): f ()
}
store的返回值就是一个一般对象,内里有几个常常使用的要领:
- dispatch:就是我们最常常使用的dispatch要领,派发action。
- getState:经由历程该要领,我们能够拿到当前状况树state。
- replaceReducer:这个要领重要用于 reducer 的热替代,下面引见该要领。
- subscribe:增加一个变化监听器。每当 dispatch(action)的时刻就会实行,state 树中的一部份能够已变化。
- observable:观察者形式,用于处置惩罚定阅关联。
这里挑几个要领引见:
getState
在完成基础的参数校验以后,在 createStore 中声明以下变量及 getState 要领:
var currentReducer = reducer
var currentState = initialState
var listeners = [] // 当前监听 store 变化的监听器
var isDispatching = false // 某个 action 是不是处于分发的处置惩罚历程当中
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState
}
getState要领就是简朴返回当前state,假如state没有被reducer处置惩罚过,他就是initialState。
subscribe
在 getState 以后,定义了 store 的另一个要领 subscribe:
function subscribe(listener) {
listeners.push(listener)
var isSubscribed = true
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
Store 许可运用store.subscribe
要领设置监听函数,一旦 State 发作变化,就自动实行这个函数。
明显,只需把 View 的更新函数(关于 React 项目,就是组件的render
要领或setState
要领)放入listen
,就会完成 View 的自动衬着。你能够会觉得新鲜,彷佛我们在 Redux 运用中并没有运用 store.subscribe 要领?事实上,
React Redux 中的 connect 要领隐式地帮我们完成了这个事情。
store.subscribe
要领返回一个函数,挪用这个函数就能够消除监听。
dispatch
dispatch是redux的中心要领:
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
}
listeners.slice().forEach(listener => listener())
return action
}
推断当前是不是处于某个 action 的分发历程当中,这个搜检重要是为了防止在 reducer 平分发 action 的状况,由于如许做能够致使分发死轮回,同时也增加了数据活动的庞杂度。
确认当前不属于分发历程当中后,先设定标志位,然后将当前的状况和 action 传给当前的reducer,用于天生最新的 state。这看起来一点都不庞杂,这也是我们反复强调的 reducer 事情历程——纯函数、接收状况和 action 作为参数,返回一个新的状况。
在取得新的状况后,顺次挪用一切的监听器,关照状况的变动。须要注重的是,我们在关照监听器变动发作时,并没有将最新的状况作为参数通报给这些监听器。这是由于在监听器中,我们能够直接挪用 store.getState() 要领拿到最新的状况。
终究,处置惩罚以后的 action 会被 dispatch 要领返回。
replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
这是为了拿到一切 reducer 中的初始状况(你是不是还记得在定义 reducer 时,第一个参数为previousState,假如该参数为空,我们供应默许的 initialState)。只需一切的初始状况都胜利猎取后,Redux 运用才井井有条地最先运作。
3.Redux middleware
It provides a third-party extension point between dispatching an action, and the moment it reaches
the reducer
它供应了一个分类处置惩罚 action 的时机。在middleware 中,你能够校阅阅兵每一个流过的 action,挑选出特定范例的action 举行相应操纵,给你一次转变 action 的时机。
通例的同步数据流形式的流程图以下:
差别营业需求下,比方实行action之前和以后都要打log;action触发一个异步的要求,要求返来以后衬着view等。须要为这一类的action增加大众的要领或许处置惩罚,运用redux middleware流程图以下:
每一个 middleware 处置惩罚一个相对自力的营业需求,经由历程串连差别的 middleware 完成变化多样的功用。比方上面的营业,我们把处置惩罚log的代码封装成一个middleware,处置惩罚异步的也是一个middleware,二者串连,却又互相自力。
运用middleware以后,action触发的dispatch并非本来的dispatch,而是经由封装的new dispatch,在这个new dispatch中,根据递次顺次实行每一个middleware,末了挪用原生的dispatch。
我们来看下logger middleware怎样完成的:
export default store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action);
}
这里代码非常简约,就是在next挪用下一个middleware之前和以后,离别打印两次。
Redux 供应了 applyMiddleware 要领来加载 middleware,该要领的源码以下:
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return function (next) {
return function (reducer, initialState) {
let store = next(reducer, initialState);
let dispatch = store.dispatch;
let chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
}
}
}
个中compose源码以下:
function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
运用的时刻,以下:
const newStore = applyMiddleware([mid1, mid2, mid3, ...])(createStore)(reducer, initialState);
ok,相干源码已就位,我们来细致剖析一波。
函数式编程头脑设想 :middleware 的设想有点特别,是一个层层包裹的匿名函数,这实际上是函数式编程中的
currying,它是一种运用匿名单参数函数来完成多参数函数的要领。applyMiddleware 会对 logger 这个middleware 举行层层挪用,动态地将 store 和 next 参数赋值。currying 的 middleware 构造的优点重要有以下两点。
- 易串连:currying 函数具有耽误实行的特征,经由历程不停 currying 构成的 middleware 能够积累参数,再配合组合(compose)的体式格局,很轻易构成 pipeline 来处置惩罚数据流。
- 同享 store: 在 applyMiddleware 实行的历程当中,store 照样旧的,然则由于闭包的存在,applyMiddleware 完成后,一切的 middleware 内部拿到的 store 是最新且雷同的。
给 middleware 分发 store:newStore建立完成以后,applyMiddleware 要领连续取得了3个参数,第一个是 middlewares 数组[mid1, mid2, mid3, …],第二个是 Redux 原生的 createStore ,末了一个是 reducer。然后,我们能够看到 applyMiddleware 应用 createStore 和 reducer 建立了一个 store。而 store 的 getState要领和 dispatch 要领又离别被直接和间接地赋值给 middlewareAPI 变量 store:
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
然后,让每一个 middleware 带着 middlewareAPI 这个参数离别实行一遍。实行完后,取得 chain数组 [f1, f2, … , fx, …, fn],它保留的对象是第二个箭头函数返回的匿名函数。由因而闭包,每一个匿名函数都能够接见雷同的 store,即 middlewareAPI。
middlewareAPI 中的 dispatch 为何要用匿名函数包裹呢?
我们用 applyMiddleware 是为了革新 dispatch,所以 applyMiddleware 实行完后,dispatch 是变化了的,而 middlewareAPI 是 applyMiddleware 实行平分发到各个 middleware 的,所以必须用匿名函数包裹 dispatch,如许只需 dispatch 更新了,middlewareAPI 中的 dispatch 运用也会发作变化。
组合串连 middleware:这一层只需一行代码,倒是 applyMiddleware 英华之地点dispatch = compose(...chain)(store.dispatch);
,个中 compose 是函数式编程中的组合,它将 chain 中的一切匿名函数 [f1, f2, … , fx, …, fn]组装成一个新的函数,即新的 dispatch。当新 dispatch 实行时,[f1, f2, … , fx, …, fn],从右到左顺次实行。
compose(…funcs) 返回的是一个匿名函数,个中 funcs 就是 chain 数组。当挪用 reduceRight时,顺次从 funcs 数组的右端取一个函数 fx 拿来实行,fx 的参数 composed 就是前一次 fx+1 实行的结果,而第一次实行的 fn(n 代表 chain 的长度)的参数 arg 就是 store.dispatch。所以,当 compose 实行完后,我们取得的 dispatch 是如许的,假定 n = 3:
dispatch = f1(f2(f3(store.dispatch))));
这时候挪用新 dispatch,每一个 middleware 就顺次实行了。
在 middleware 中挪用 dispatch 会发作什么:经由 compose 后,一切的 middleware 算是串连起来了。但是另有一个题目,在分发 store 时,我们提到过每一个 middleware 都能够接见 store,即 middlewareAPI 这个变量,也能够拿到 store 的dispatch 属性。那末,在 middleware 中挪用 store.dispatch() 会发作什么,和挪用 next() 有区分吗?如今我们来申明二者的差别:
const logger = store => next => action => {
console.log('dispatch:', action);
next(action);
console.log('finish:', action);
};
const logger = store => next => action => {
console.log('dispatch:', action);
store.dispatch(action);
console.log('finish:', action);
};
在分发 store 时我们诠释过,middleware 中 store 的 dispatch 经由历程匿名函数的体式格局和终究compose 完毕后的新 dispatch 保持一致,所以,在 middleware 中挪用 store.dispatch() 和在其他任何地方挪用的结果一样。而在 middleware 中挪用 next(),结果是进入下一个 middleware,下图就是redux middleware最著名的洋葱模子图。
4.combineReducer
假如一个项目过大,我们一般按模块来写reducer,然则redux create store只接收一个reducer参数,所以我们须要兼并reducer。这里就用到了redux供应的combineReducer
辅佐函数:
combineReducers({
layout,
home,
...asyncReducers
})
这个函数用起来很简朴,就是传入一个对象,key是模块reducer对应的名字, 值是对应reducer。值是一个function,相称因而一个新的reducer,源码以下:
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
if (process.env.NODE_ENV !== 'production') {
var unexpectedKeyCache = {}
}
var sanityError
try {
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
if (process.env.NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
源码不是许多,撤除一些考证代码,剩下的就是说:return一个function,我们临时称谓他combination,就相称因而与一个总的reducer,每次action都邑走到combination中,combination会遍历输入的reducer,将action放到每一个reducer中实行一下,盘算出返回结果就是nextState,nextState于previousState假如!==申明转变了,返回nextState,不然返回实行之前的state。
这也诠释了差别模块actionType假如雷同的话,两个模块的reducer都邑走一遍的题目,在actionType称号前面加上模块前缀即可解决题目。
5. Provider与Connect
Provider与Connet组件都是React-Redux供应的中心组件,二者看起来功用一样,都是协助容器组件猎取store中的数据,然则道理与功用却差别。
Provider
Provider组件在一切组件的最外层,其接收store作为参数,将store里的state运用context属性向下通报。部份源码:
export default class Provider extends Component {
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
render() {
const { children } = this.props
return Children.only(children)
}
}
应用context这个属性,Provider一切子组件均能够拿到这个属性。
Connect
connect完成的功用是将须要关联store的组件和store的dispatch等数据混合到一块,这块就是一个高阶组件典范的运用:
import hoistStatics from 'hoist-non-react-statics'
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
// ...
return function wrapWithConnect(WrappedComponent) {
// ...
class Connect extends Component {
// ...
render() {
// ...
if (withRef) {
this.renderedElement = createElement(WrappedComponent, {
...this.mergedProps,
ref: 'wrappedInstance'
})
} else {
this.renderedElement = createElement(WrappedComponent,
this.mergedProps
)
}
return this.renderedElement
}
}
// ...
return hoistStatcis(Connect, WrappedComponent);
}
}
照样先从他的四个参数提及:
1.mapStateToProps
connect 的第一个参数定义了我们须要从 Redux 状况树中提取哪些部份看成 props 传给当前组件。一般来说,这也是我们运用 connect 时常常传入的参数。事实上,假如不传入这个参数,React 组件将永久不会和 Redux 的状况树发生任何关联。详细在源代码中的表现为:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
const shouldSubscribe = Boolean(mapStateToProps)
// ...
class Connect extends Component {
// ...
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
this.handleChange()
}
}
// ...
}
}
mapStateToProps
会定阅 Store,每当state
更新的时刻,就会自动实行,从新盘算 UI 组件的参数,从而触发 UI 组件的从新衬着。
mapStateToProps
的第一个参数老是state
对象,还能够运用第二个参数,代表容器组件的props
对象。
这块的源码相对较简朴:
const mapState = mapStateToProps || defaultMapStateToProps
class Connect extends Component {
computeStateProps(store, props) {
if (!this.finalMapStateToProps) {
return this.configureFinalMapState(store, props)
}
const state = store.getState()
const stateProps = this.doStatePropsDependOnOwnProps ?
this.finalMapStateToProps(state, props) :
this.finalMapStateToProps(state)
if (process.env.NODE_ENV !== 'production') {
checkStateShape(stateProps, 'mapStateToProps')
}
return stateProps
}
configureFinalMapState(store, props) {
const mappedState = mapState(store.getState(), props)
const isFactory = typeof mappedState === 'function'
this.finalMapStateToProps = isFactory ? mappedState : mapState
this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1
if (isFactory) {
return this.computeStateProps(store, props)
}
if (process.env.NODE_ENV !== 'production') {
checkStateShape(mappedState, 'mapStateToProps')
}
return mappedState
}
}
这块道理很简朴,举行一些参数校验,推断第一个参数mapStateToProps返回值是不是为function,假如是递归挪用,不是的话算出返回值。假如没传这个参数,默许给{}。
我们能够会迷惑为何传给 connect 的第一个参数本身是一个函数,react-redux 还许可这个函数的返回值也是一个函数呢?
简朴地说,如许设想能够许可我们在 connect 的第一个参数里应用函数闭包举行一些庞杂盘算的缓存,从而完成效力优化的目标
当我们运用的时刻:
const mapStateToProps = (state, props) => ({
home: state.home,
layout: state.layout
});
运用ownProps
作为参数后,假如容器组件的参数发作变化,也会激发 UI 组件从新衬着
2.mapDispatchToProps
人如其名,它接收 store 的 dispatch 作为第一个参数,同时接收 this.props 作为可选的第二个参数。应用这个要领,我们能够在 connect 中方便地将 actionCreator 与 dispatch 绑定在一起(应用 bindActionCreators 要领),终究绑定好的要领也会作为 props 传给当前组件。这块的源码与mapStateToProps一样,就不贴了。
bindActionCreator
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
3.mergeProps
前两个参数返回的对象,都要跟组件本身的props merge一下,构成一个新的对象赋值给对应组件,我们能够在这一步做一些处置惩罚,这个参数就是干这个的,该参数署名:
mergeProps(stateProps, dispatchProps, ownProps): props
默许状况假如没传该参数,返回Object.assign(ownProps, stateProps, dispatchProps)
。
4.options
假如指定这个参数,能够定制 connector 的行动。
- [
pure = true
] (Boolean): 假如为 true,connector 将实行shouldComponentUpdate
而且浅对照mergeProps
的结果,防止不必要的更新,条件是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默许值为 true。 - [
withRef = false
] (Boolean): 假如为 true,connector 会保留一个对被包装组件实例的援用,该援用经由历程getWrappedInstance()
要领取得。默许值为 false。
这个connect组件还干了一件事,状况缓存推断。当store变了的时刻,前后状况推断,假如状况不等,更新组件,而且完成事宜分发。
6. Redux流程梳理
上面讲了大批的函数源码,这么些函数之间的关联:
初始化阶段:
- createStore建立一个store对象
- 将store对象经由历程参数给Provider组件
- Provider组件将store经由历程context向子组件通报
- Connect组件经由历程context猎取到store,存入本身的state
- componentDidMount内里定阅store.subscribe事宜
更新数据阶段:
- 用户事宜触发
- actionCreator天生action交给dispatch
- 实际上交给了封装后的中间层(compose(applyMiddleware(…)))
- 要求顺次经由历程每一个中间件,中间件经由历程next举行下一步
- 末了一个中间件将action交给store.dispatch
- dispatch内部将action交给reducer实行
- combineReducer将每一个子reducer实行一遍算出新的state
- dispatch内部挪用一切定阅事宜
- Connect组件handleChange事宜触发推断新state和旧state是不是===
- 而且推断新的state是不是与mapStateToProps shallowEqual
- 不等则setState触发更新
7.Redux设想技能
- 匿名函数&&闭包运用
redux中心函数大批运用了匿名函数和闭包来完成数据同享和状况同步。
- 函数柯里化运用
运用函数柯里化s完成参数复用,本质上是下降通用性,进步适用性。
- 中心状况读取是拷贝而不是地点
关于state这类中心状况运用getState()盘算出新的state,而不是直接返回一个state对象。
- 观察者定阅者是中心完成
运用观察者定阅者形式完成数据相应。
- context这个api的运用
日常平凡开辟不常打仗的api完成Provider与Connect通讯。