通过 Redux 文档来了解 react-redux, 在一段时间的实践后准备翻一翻源代码, 顺便做些相关的总结. 我看的代码的 NPM 版本为 v4.0.0, 也就是说使用的 React 版本是 0.14.x.
react-redux 提供两个关键模块: Provider 和 connect.
Provider
Provider 这个模块是作为整个 App 的容器, 在你原有的 App Container 的基础上再包上一层, 它的工作很简单, 就是接受 Redux 的 store 作为 props, 并将其声明为 context 的属性之一, 子组件可以在声明了 contextTypes 之后可以方便的通过 this.context.store 访问到 store. 不过我们的组件通常不需要这么做, 将 store 放在 context 里, 是为了给下面的 connect 用的.
这个是 Provider 的使用示例:
- // 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')
- )
- connect
这个模块是算是真正意义上连接了 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.
从它的使用来说, 它的 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 组件的构造函数作为连接对象, 最终返回连接好的组件构造函数.
React 组件如何响应 store 的变化?
为什么 connect 选择性的 merge 一些 props, 而不是直接将整个 state 传入?
pure 优化的是什么?
我们把 connect 返回的函数叫做 Connector, 它返回的是内部的一个叫 Connect 的组件, 它在包装原有组件的基础上, 还在内部监听了 Redux 的 store 的变化, 为了让被它包装的组件可以响应 store 的变化:
- trySubscribe() {
- if (shouldSubscribe && !this.unsubscribe) {
- this.unsubscribe = this.store.subscribe(::this.handleChange)
- this.handleChange()
- }
- }// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- handleChange () {// 面向 1-3 年前端人员
- 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)// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- })// 面向 1-3 年前端人员
- )// 帮助突破技术瓶颈, 提升思维能力
- export default class Main extends Component {
- }
结语
感谢您的观看, 如有不足之处, 欢迎批评指正.
来源: http://www.qdfuns.com/article/51117/efd1ce085e2fddaf2c8ed7c0d594af9c.html