Redux,Reselect和ImmutableJS对子组件造成不必要的渲染

时间:2017-03-30 04:28:20

标签: reactjs redux react-redux reselect

基于我刚刚阅读并重新阅读的所有Redux和Reselect文档,如果thing.toJS()返回的不可变映射不等于前一个,则只应执行getThing()处理

...

// Selector

import { createSelector } from 'reselect'

const getThing = (state, thingId) => {
    return state.entities.getIn(['things', thingId])
}

const makeThingSelector = () => {
    return createSelector(
        [getThing],
        (thing) => {
            return thing.toJS()
        }
    )
}

export default makeThingSelector

...

// Container

const makeMapStateToProps = () => {
    return (state, ownProps) => {
        const { thingId } = ownProps
        const things = select.makeThingsSelector()(state, thingId)

        return {
            hasNoThings: things.length === 0,
            things
        }
    }
}

const Container = connect(
    makeMapStateToProps,
    mapDispatchToProps
)(Component)
...

除非我有一个孩子'智能'组件,否则这是正确的。在这种情况下,当父级触发渲染时,子组件容器中调用的选择器始终处理该值,无论结果是否为新。

我一直在尝试将ImmutableJS API封装在我的选择器中,但这意味着每次父母更新时都要避免在这些嵌套组件上重新渲染我必须在shouldComponentUpdate函数中进行深度相等检查。这很昂贵,似乎不是一个合适的解决方案。

应用程序状态已规范化,因此状态树的更新部分不是子组件所依赖的状态部分的分层父级。

我在这里错过了什么关键吗?

2 个答案:

答案 0 :(得分:3)

store次更新react-redux执行以下步骤(将所有内部复杂性放在一边):

  1. 致电mapStateToPropsmapDispatchToProps
  2. 浅析比较结果props
  3. 重新呈现Component以防新props与之前的mapStateToProps不同。
  4. 这种方式{/ 1}}将在每个store更新时通过设计调用。下面的代码行也是如此:

    ...
    const things = select.makeThingsSelector()(state, visitId)
    ...
    

    正如您所见,每次有效阻止任何记忆时都会创建新的reselect选择器(reselect中没有全局状态,每个选择器都会发生记忆)。

    您需要做的是更改您的代码,以便在mapStateToProps的每次调用时使用同一个选择器:

    const thingSelector = select.makeThingsSelector();
    
    ...
    
    const makeMapStateToProps = () => {
        return (state, ownProps) => {
            const { visitId } = ownProps
            const things = thingSelector(state, visitId)
    
            return {
                hasNoThings: things.length === 0,
                things
            }
        }
    }
    

    更新:我也认为没有理由使用工厂风格的makeThingsSelectormakeMapStateToProps。为什么不这样做:

    ...
    
    // Selector
    
    export default createSelector(
      [getThing],
      (thing) => thing.toJS()
    );
    
    ...
    
    // Container
    
    const mapStateToProps = (state, ownProps) => {
      const { visitId } = ownProps
      const things = select.thingsSelector(state, visitId)
      return {
        hasNoThings: things.length === 0,
        things
      }
    }
    
    const Container = connect(
        mapStateToProps,
        mapDispatchToProps
    )(Component)
    ...
    

答案 1 :(得分:1)

由于此应用程序中的redux状态使用ImmutableJS数据结构,因此Reselect可能不是必需的。

首先,ImmutableJS仅操作受更改操作影响的数据结构的切片,因此对较大状态的所有更改可能不会影响将切片传递给容器。

第二,redux connect函数默认情况下会返回一个纯容器,并且遇到相同的分片时将不会重新渲染。但是,mapStateToProps会被调用,因为整个状态以及自己的属性可能都已更改。

为了更好地进行控制,可以通过向第四个参数添加ownPropsareStatesEqual谓词属性,将同一容器的呈现直接链接到对状态的特定切片和areOwnPropsEqual的更改。连接功能(最好称为options对象)。

const mapStateToProps = ({ entities }, { thingId }) => {
    const things = entities.getIn(['things', thingId]).toJS();
    return {
        hasNoThings: things.length === 0,
        things
   };
};

const Container = connect(
    mapStateToProps,
    mapDispatchToProps,
    undefined, {
        areOwnPropsEqual: (np, pp) => np.thingId === pp.thingId,
        areStatesEqual: (ns, ps) => ns.entities.get(‘things’).equals(
            ps.entities.get(‘things’)
        )
    }
)(Component);

如果这两个谓词都是正确的,那么不仅容器及其子级都不会重新呈现,mapStateToProps甚至都不会被调用!