为什么我们需要reselect
为什么我们需要reselect遇到的问题先看下下面的一个组件 import React,{ Component } from 'react' import { connect } from 'react-redux' class UnusedComp extends Component { render() { const { a,b,c,fab,hbc,gac,uabc } = this.props return ( <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{fab}</h6> <h6>{hbc}</h6> <h6>{gac}</h6> <h6>{uabc}</h6> </div> ) } } function f(x,y) { return a + b } function h(x,y) { return x + 2 * y } function g(x,y) { return 2 * x + y } function u(x,y,z) { return x + y + z } 这个UnusedComp 组件关心这样的几个props: a,f(a,b),h(b,c),g(a,u(a,其中f,h,g,u分别是一个函数。 关于这几个计算的值, 我们应该怎么处理呢? 把数据直接计算在redux第一种, 我们把所有值存在redux, 所有store的结构大概是这样的: store = { a:1,b:1,c:1,fab: 2,// a + b hbc: 3,// b + 2c gac: 3,// 2a + c uabc: 3 // a + b + c } 这样我们的组件简单了, 只需要直接取值渲染就好 switch(action.type) { case 'changeA': { return { ...state,a: action.a,fab: f(action.a,state.b),gac: g(action.a,state.c) uabc: u(action.a,state.b,state.c) } } case 'changeB': { ... } case 'changeC': { ... } } 我们的reducer 函数非常复杂了, 我们每更新一个状态值。 都得维护与这个值相关的值, 不然就会有数据不一致。 reducer 只存最基本状态为了保证数据流的清晰, 更新的简单。 我们只把最基本的状态存储在redux。store的结构和redcuer函数如下: store = { a:1,} ... switch(action.type) { case 'changeA': { return { ...state,a: action.a } } ... } 此刻组件可能是这样的: class UnusedComp extends Component { render() { const { a,c } = this.props const fab = f(a,b) const hbc = h(b,c) const gac = g(a,c) const uabc = u(a,c) return ( <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{fab}</h6> <h6>{hbc}</h6> <h6>{gac}</h6> <h6>{uabc}</h6> </div> ) } } 或者这样的: class UnusedComp extends Component { componentWillReciveProps(nextProps) { const { a,c } = this.props this.fab = f(a,b) this.hbc = h(b,c) this.gac = g(a,c) this.uabc = u(a,c) } render() { const { a,c } = this.props return ( <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{this.fab}</h6> <h6>{this.hbc}</h6> <h6>{this.gac}</h6> <h6>{this.uabc}</h6> </div> ) } } 对于第一种情况, 当组件ownProps(组件自身属性, 非redux传递),或者setState 的时候 都会执行计算。 让数据逻辑离开组件! // 可以写成函数式组件 class UnusedComp extends Component { render() { const { a,uabc } = this.props return ( <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{fab}</h6> <h6>{hbc}</h6> <h6>{gac}</h6> <h6>{uabc}</h6> </div> ) } } function mapStateToProps(state) { const {a,c} = state return { a,fab: f(a,hbc: h(b,gac: g(a,uabc: u(a,c) } } UnusedComp = connect(mapStateToProps)(UnusedComp) 组件很简单, 接收数据展示就可以了。 看似很美好! 我们知道当store数据被改变的时候, 会通知所有connect的组件(前提是没被销毁)。 在考虑一种情况, 假设UnusedComp还有 x,z 状态属性, 存在redux。 这3个属性就是简单的3个值, 只用来展示。 可是当x, y, z改变的时候,也会触发计算。 这里发生的计算不管是在render里面计算, 还是willReciveProps,还是mapStateToProps里 都无法避免。 精确控制计算仿佛我们依据找到了 方法:
现实很残酷! 实际上x,z这种属性, 一定大量存在。 光是这一点就会导致大量的无效计算。 之前讨论的3种方式 (render, willRecive,mapStateToProps)无法避免这种计算。 另外mapStateToProps 还会被其他store的值改变影响 ,毕竟react-router + 单 容器组件 组件 这种组织方式只是最美好的情况。 我们有些业务就是处于性能的考虑,没有销毁之前路由的组件, 用我们自己的路由。有些页面也不是 单容器组件,尴尬!! 明显的, 我们是知道 x, y, z的变化是不需要计算的, 而a,b, c变化是需要计算的。 如何描述给程序呢?另外 mapStateToProps 这种方式还带来了好处, 我们在描述的时候,不会侵入组件!!。 最原始的描述: let memoizeState = null function mapStateToProps(state) { const {a,c} = state if (!memoizeState) { memoizeState = { a,c) } } else { if (!(a === memoizeState.a && b === memoizeState.b) ) { // f should invoke memoizeState.fab = f(a,b) } if (!(b === memoizeState.b && c === memoizeState.c) ) { // h should invoke memoizeState.hbc = h(b,c) } if (!(a === memoizeState.a && c === memoizeState.c) ) { // g should invoke memoizeState.gac = g(a,c) } if (!(a === memoizeState.a && b === memoizeState.b && c === memoizeState.c) ) { // u should invoke memoizeState.uabc = u(a,c) } memoizeState.a = a memoizeState.b = b memoizeState.c = c } return memoizeState } 首选, 我们知道fab的值与a,b 有关, 所以当a,b 有变化的时候,f需要重新执行。 其他同理, 这样的话函数一定是只在必要的时候执行。 使用reselectreselect 解决了我们上面的那个问题, 我们也不必每次用这个最原始的描述了, 对应的reselect描述是这样的 import { createSelector } from 'reselect' fSelector = createSelector( a => state.a,b => state.b,(a,b) => f(a,b) ) hSelector = createSelector( b => state.b,c => state.c,(b,c) => h(b,c) ) gSelector = createSelector( a => state.a,c) => g(a,c) ) uSelector = createSelector( a => state.a,c) => u(a,c) ) ... function mapStateToProps(state) { const { a,c } = state return { a,fab: fSelector(state),hbc: hSelector(state),gac: gSelector(state),uabc: uSelector(state) } } 在 createSelector 里面我们先定义了 input-selector 函数, 最后定义了 值是如何计算出来的。 selector保证了,当input-selector 返回结果相等的时候,不会计算。 最后如果 你是react-router 并且是 单 容器组件。 那么可能在 mapStateToProps里面计算,性能问题并不大。 而且性能不应该是我们第一要考虑的东西, 我们首先要考虑的是简单性,尤其是组件的简单性。 当我们的业务复杂到需要考虑性能的时候, reselect是我们不错的选择! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |