之前一直在探索 React 相关的东西,手上有个 SPA 项目,于是准备上 Redux 试试水。Redux 本身和 React 并没有之间的关联,它是一个通用 Javscript App 模块,用做 App State 的管理。要在 React 的项目中使用 Redux,比较好的方式是借助 react-redux 这个库来做连接,这里的意思是,并不是没有 react-redux,这两个库就不弄一起用了,而是说 react-redux 提供了一些封装,一种更科学的代码组织方式,让我们更舒服地在 React 的代码中使用 Redux。
之前仅通过 Redux 文档来了解 react-redux,在一段时间的实践后准备翻一翻源代码,顺便做些相关的总结。我看的代码的 npm 版本为 v4.0.0,也就是说使用的 React 版本是 0.14.x。
react-redux 提供两个关键模块:Provider 和 connect。
ProviderProvider 这个模块是作为整个 App 的容器,在你原有的 App Container 的基础上再包上一层,它的工作很简单,就是接受 Redux 的 store 作为 props,并将其声明为 context 的属性之一,子组件可以在声明了 contextTypes 之后可以方便的通过 this.context.store 访问到 store。不过我们的组件通常不需要这么做,将 store 放在 context 里,是为了给下面的 connect 用的。
这个是 Provider 的使用示例:
connect
- // config app root
- const history = createHistory()
- const root = (
- <Provider store={store} key="provider">
- <Router history={history} routes={routes} />
- </Provider>
- )
- // render
- ReactDOM.render(
- root,
- document.getElementById('root')
- )
这个模块是算是真正意义上连接了 Redux 和 React,正好它的名字也叫 connect。
先考虑 Redux 是怎么运作的:首先 store 中维护了一个 state,我们 dispatch 一个 action,接下来 reducer 根据这个 action 更新 state。
映射到我们的 React 应用中,store 中维护的 state 就是我们的 app state,一个 React 组件作为 View 层,做两件事:render 和响应用户操作。于是 connect 就是将 store 中的必要数据作为 props 传递给 React 组件来 render,并包装 action creator 用于在响应用户操作时 dispatch 一个 action。
好了,详细看看 connect 这个模块做了什么。先从它的使用来说,它的 API 如下:
- connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps 是一个函数,返回值表示的是需要 merge 进 props 的 state。默认值为 () => ({}),即什么都不传。
- (state, props) => ({ }) // 通常会省略第二个参数
mapDispatchToProps 是可以是一个函数,返回值表示的是需要 merge 仅 props 的 actionCreators,这里的 actionCreator 应该是已经被包装了 dispatch 了的,推荐使用 redux 的 bindActionCreators 函数。
- (dispatch, props) = >({ // 通常会省略第二个参数
- ...bindActionCreators({...ResourceActions
- },
- dispatch)
- })
更方便的是可以直接接受一个对象,此时 connect 函数内部会将其转变为函数,这个函数和上面那个例子是一模一样的。
mergeProps 用于自定义 merge 流程,下面这个是默认流程,parentProps 值的就是组件自身的 props,可以发现如果组件的 props 上出现同名,会被覆盖。
- (stateProps, dispatchProps, parentProps) => ({
- ...parentProps,
- ...stateProps,
- ...dispatchProps
- })
options 共有两个开关:pure 代表是否打开优化,详细内容下面会提,默认为 true,withRef 用来给包装在里面的组件一个 ref,可以通过 getWrappedInstance 方法来获取这个 ref,默认为 false。
connect 返回一个函数,它接受一个 React 组件的构造函数作为连接对象,最终返回连接好的组件构造函数。
然后几个问题:
我们把 connect 返回的函数叫做 Connector,它返回的是内部的一个叫 Connect 的组件,它在包装原有组件的基础上,还在内部监听了 Redux 的 store 的变化,为了让被它包装的组件可以响应 store 的变化:
- trySubscribe() {
- if (shouldSubscribe && !this.unsubscribe) {
- this.unsubscribe = this.store.subscribe(: :this.handleChange) this.handleChange()
- }
- }
- handleChange() {
- this.setState({
- storeState: this.store.getState()
- })
- }
但是通常,我们 connect 的是某个 Container 组件,它并不承载所有 App state,然而我们的 handler 是响应所有 state 变化的,于是我们需要优化的是:当 storeState 变化的时候,仅在我们真正依赖那部分 state 变化时,才重新 render 相应的 React 组件,那么什么是我们真正依赖的部分?就是通过 mapStateToProps 和 mapDispatchToProps 得到的。
具体优化的方式就是在 shouldComponentUpdate 中做检查,如果只有在组件自身的 props 改变,或者 mapStateToProps 的结果改变,或者是 mapDispatchToProps 的结果改变时 shouldComponentUpdate 才会返回 true,检查的方式是进行 shallowEqual 的比较。
所以对于某个 reducer 来说:
- export default (state = {}, action) => {
- return { ...state } // 返回的是一个新的对象,可能会使组件reRender
- // return state // 可能不会使得组件reRender
- }
另外在 connect 的时候,要谨慎 map 真正需要的 state 或者 actionCreators 到 props 中,以避免不必要的性能损失。
最后,根据 connect 的 API 我们发现可以使用 ES7 decorator 功能来配合 React ES6 的写法:
- @connect(
- state => ({
- user: state.user,
- resource: state.resource
- }),
- dispatch => ({
- ...bindActionCreators({
- loadResource: ResourceActions.load
- }, dispatch)
- })
- )
- export default class Main extends Component {
- }
来源: http://www.jb51.net/article/129684.htm