Redux的文档里提到参考了Elm的架构,之前一直写Elm,对Redux的东西接受的也比较快,这里主要讨论react-redux
里的Provider
,connect
实现,探讨其中性能优化的部分。
基础
1
| import { Provider, connect } from "react-redux"
|
Component 组件 |
Presentational展示 |
Container 容器 |
数据来源 |
props |
store, ownProps |
修改数据 |
props提供callback |
dispatch action |
Provider
传入store,只需渲染根组件时用<Provider />
,使所有容器组件可访问store,不必层层传递。
使用
1 2 3 4 5 6 7 8
| let store = createStore(todoApp)
render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
|
原理
父组件通过getChildContext()
向context中传值{ store: this.props.store },而通过了contextType验证的子组件可以通过this.context.store
拿到store。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } }
constructor(props, context) { super(props, context) this[storeKey] = props.store }
render() { return Children.only(this.props.children) } }
Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, }
|
createProvider
允许控制store在context中的key值,存在多store才需要,同时connect
的options也要设置相应的storeKey
。但不推荐多store,比较鸡肋。。
1 2 3 4 5 6 7 8
| import { createProvider } from "react-redux"
const MY_KEY = "mystore" const Provider = createProvider(MY_KEY)
<Provider store={store} />
connect(.., .., .., { storeKey: MY_KEY })(App)
|
connect
简易实现
不通过connect
,直接context,contextTypes并注册监听。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class App extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); }
render() { const props = this.props; const { store } = this.context; const state = store.getState(); } }
App.contextTypes = { store: PropTypes.object }
|
使用
形成容器组件使用connnect()
而不是原生,连接React组件和store,以获得优化。主要两点作用:
从context拿到store,进而得到getState
dispatch
;
监听store的state和容器组件ownProps,判定是否组合并重渲染,并实现优化。
1 2 3 4 5 6
| const AppCtn = connect( mapStateToProps, mapDispatchToProps )(App)
export default AppCtn
|
参数
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps(state, [ownProps]): stateProp: 设置该参数时,使该组件可以监听redux store。
mapDispatchToProps(dispatch, [ownProps]): dispatchProps: action creator,若省略,默认注入dispatch
。
mergeProps(stateProps, dispatchProps, ownProps): props: 默认返回Object.assign({}, ownProps, stateProps, dispatchProps)
的结果,可用于将props绑定到action creator等,结果作为被包裹组件的props。
options(object)
如果为 true,当相关的state/props可能变化时,若经浅比较没变,不会重渲染或调用mapStateToProps
,mapDispatchToProps
,mergeProps
等。默认值为 true。
- areStatesEqual(next, prev): bool(Function)
用来比较store state的方法,默认值strictEqual(===)
,当mapStateToProps
操作代价大,而只与state一部分有关时可以使用。
- areOwnPropsEqual(Function)
比较ownProps,默认shallowEqual
。
- areStatePropsEqual(Function)
比较mapStateToProps
结果,默认shallowEqual
。
- areMergedPropsEqual(Function)
比较mergeProps
结果,默认shallowEqual
。
shallowEqual实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const hasOwn = Object.prototype.hasOwnProperty
function is(x, y) { if (x === y) { return x !== 0 || y !== 0 || 1 / x === 1 / y } else { return x !== x && y !== y } }
export default function shallowEqual(objA, objB) { if (is(objA, objB)) return true
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false }
const keysA = Object.keys(objA) const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) { if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false } }
return true }
|
可见对象比较最多只比较一层,无法精确比较嵌套对象。
简单的connect实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import React, { Component } from "react" import PropTypes from "prop-types"
const connect = (mapStateToProps, mapDispatchToProps, mergeProps, options) => (App) => { const mergeProps = mergeProps || ((...args) => Object.assign({}, ...args))
class Ctn extends Component { static contextPtops = { store: PropTypes.object }
constructor() { super() this.state = { allProps: {} } }
updateAllProps() { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} let newAllProps = { ...stateProps, ...dispatchProps, ...this.props } this.setState({ allProps: newAllProps }) }
componentWillMount() { const { store } = this.context updateAllProps() if (mapStateToProps) this.unsubscribe = store.subscribe(() => this.updateAllProps()) }
componentWillReceiveProps(nextProps) { shallowEqual(nextProps, this.props) || this.updateAllProps() }
shouldComponentUpdate(nextProps, nextState) { return !shallowEqual(nextState.allProps, this.state.allProps) }
componentWillUnmount() { this.unsubscribe && this.unsubscribe() }
render() { return <App {...this.state.allProps} /> } }
return Ctn }
|
总结
总的来说Redux的原理还是很简单的,它只是提供了一个状态管理的工具,而如何根据业务组织数据,做好性能优化才是重点。它借鉴了Elm的架构,但Elm这门函数式语言自带的数据不变性(Immutability),纯函数,静态类型,模式匹配以及强大的类型组合是Redux实现不了的,需要自己实现。