【问题标题】:Redux, Reselect and ImmutableJS causing unnecessary renders on child componentsRedux、Reselect 和 ImmutableJS 导致子组件不必要的渲染
【发布时间】:2017-03-30 04:28:20
【问题描述】:

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

...

// 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 函数中进行深度相等检查。这很昂贵,而且似乎不是一个体面的解决方案。

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

我在这里遗漏了什么关键吗?

【问题讨论】:

  • 您发布的代码看起来是正确的,应该按预期运行。请提供使用这些选择器并将您的Components 连接到store 的代码。

标签: reactjs redux react-redux reselect


【解决方案1】:

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

  1. 致电mapStateToPropsmapDispatchToProps
  2. 浅比较结果props
  3. 重新渲染 Component 以防新的 props 与之前的不同。

这样mapStateToProps 将在每次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)
...

【讨论】:

    【解决方案2】:

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

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

    其次,redux connect 函数默认返回一个纯容器,遇到相同的切片不会重新渲染。然而,mapStateToProps 将被调用,因为整个状态和可能的 ownProps 都发生了变化。

    为了更精细的控制,通过在connect函数的第四个参数中添加areStatesEqualareOwnPropsEqual谓词属性,可以将同一容器的渲染直接链接到状态和ownProps的特定切片的更改(更好称为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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-20
      • 2017-10-01
      • 1970-01-01
      • 2020-03-01
      相关资源
      最近更新 更多