【问题标题】:React: how to compare current props.children with new oneReact:如何将当前的 props.children 与新的进行比较
【发布时间】:2015-02-28 16:31:24
【问题描述】:

嗨,

我正在构建组件,该组件仅充当其他生成内容的包装器并使用第三方库。该库与组件的props.children 一起使用。到目前为止一切都很好,但是这个第三方库在应用或在元素上刷新时有点滞后。因为刷新这个库的唯一原因是当props.children 发生变化时,我试图弄清楚如何比较shouldComponentUpdate 中的this.props.childrennextProps.children。我在想 PureRenderMixin 应该做这项工作,但对我来说它不起作用。即使我只更改 state.listName,组件也会重新渲染,如下例所示。

<div>
  List name '{this.state.listName}'
  <br />
  <MyComponent>
    <ul>
      {listOfLi}
    </ul>
  </MyComponent>
</div>

有什么办法,如何管理props.children 的比较或任何其他选项如何做类似的事情? 感谢您的帮助!

【问题讨论】:

    标签: javascript compare reactjs


    【解决方案1】:

    您可以使用子 key 属性,React 建议应该给子数组以唯一标识它们。因为每个孩子都有一把钥匙,所以你可以可靠地判断孩子是否随着道具的变化而改变(这就是钥匙的全部意义!)。如果新旧密钥不匹配,则它们已更改。

    React.render(<App><Child key='1'/><Child key='2'/></App>, document.body)
    

    并在每次更新之前检查您想要检查的 App 组件是否有子项更改

    shouldComponentUpdate(nextProps){
       var oldKeys = this.props.children.map( child => child.key);
       var newKeys = nextProps.children.map( child => child.key);
    
       //compare new and old keys to make sure they are the same
    }
    

    请注意,这并不能告诉您每个孩子的内容是否发生了变化,如果您想知道 nothing 是否在此点下方的整棵树发生了变化

    作为更进一步的优化,我们知道孩子永远不会因为状态改变而改变,所以我们实际上可以在 componentWillReceiveProps() 中进行比较,只需设置一些状态属性,如 childrenHaveChanged

    【讨论】:

    • 优雅的解决方案,但并不完全适合我。我也必须检查内部组件。我试图制作一个函数,它遍历所有孩子和他们的孩子,并从(div,span ...)、className 和 style 生成某种足迹。对 dom 位置、大小等有影响的属性。但我仍然不满意。我想我将不得不手动更新
    • 这对单个根子元素也不起作用。
    • 这个答案不准确。 React 使用 key prop 来有效地管理由操作项目列表引起的更新,例如插入、删除和重新排序。只有当您的集合由具有这些键值的相同数量的对象组成时,比较键值才会告诉您。对象本身可能是完全不同的数据。
    • 使用 React16,您可以用 Fragment 包装 listOfLi 并在上面放一个键,然后只比较 Fragment 键
    【解决方案2】:

    正如 Matt S 指出的那样,接受的答案是一种脆弱的解决方法,并且取决于 key 的非标准使用。除了他列出的列表示例之外,如果您的 ids 保持不变,但在它们所代表的资源中修改了某些字段,即使使用 key={id} 之类的东西也会失败。

    issue 包含有关该主题的良好讨论,并以更稳定的workaround 结束。本质上,您可以简化children 属性,让您可以运行深度比较。您可以使用React.Children 实用程序来编写简化方法:

    // Flattens all child elements into a single list
    const flatten = (children, flat = []) => {
        flat = [ ...flat, ...React.Children.toArray(children) ]
    
        if (children.props && children.props.children) {
            return flatten(children.props.children, flat)
        }
    
        return flat
    }
    
    // Strips all circular references and internal fields
    const simplify = children => {
        const flat = flatten(children)
    
        return flat.map(
            ({
                key,
                ref,
                type,
                props: {
                    children,
                    ...props
                }
            }) => ({
                key, ref, type, props
            })
        )
    }
    

    然后您可以使用shouldComponentUpdateReact.memo 来防止重新渲染:

    const MyComponent = ({ children }) => (
        <div>{ children }</div>
    )
    
    export default React.memo(MyComponent, (prev, next) => (
        JSON.stringify(simplify(prev.children)) ===
        JSON.stringify(simplify(next.children))
    ))
    

    这些实用程序 + JSON.stringify 只是一种方法,the comment 中提到的方法类似,您还可以利用像 lodash.isequal 这样的实用程序进行深入比较。不幸的是,我不知道有任何一两个衬里可以进行此比较,但如果您知道更简单稳定的方法,请发表评论!

    【讨论】:

      【解决方案3】:

      你描述的设计和行为有些不对劲。您应该很少(如果有的话)必须关注执行children 的手动差异。这应该留给 React。如果您使用的库在每次组件更新时都阻塞,无论是因为 children 还是其他一些状态/属性更改,它都不是响应式编写的,您应该寻找一个不同的响应式编写的库。或者,您可以通过打开问题或提交拉取请求来帮助修复开源项目中的该行为。

      做你想做的事并不容易,因为它不应该是必要的。 React 非常非常擅长处理协调,并且只会在实际发生更改并更改相关 DOM 的状态时才会呈现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-04
        • 1970-01-01
        相关资源
        最近更新 更多