【问题标题】:How does shallow compare work in react浅比较如何在反应中起作用
【发布时间】:2016-07-05 05:10:34
【问题描述】:

在React的this documentation中,据说

shallowCompare 对当前 props 和 nextProps 对象以及当前 state 和 nextState 对象执行浅层相等检查。

我无法理解的是,如果它对对象进行浅层比较,那么 shouldComponentUpdate 方法将始终返回 true,因为

我们不应该改变状态。

如果我们不改变状态,那么比较总是会返回 false,因此 shouldComponent 更新总是会返回 true。我对它是如何工作的以及我们将如何覆盖它以提高性能感到困惑。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    浅比较确实检查是否相等。在比较标量值(数字、字符串)时,它会比较它们的值。比较对象时,它不比较它们的属性 - 只比较它们的引用(例如,“它们是否指向同一个对象?”)。

    让我们考虑user对象的以下形状

    user = {
      name: "John",
      surname: "Doe"
    }
    

    示例 1:

    const user = this.state.user;
    user.name = "Jane";
    
    console.log(user === this.state.user); // true
    

    请注意,您更改了用户名。即使有这种变化,对象也是平等的。引用完全相同。

    示例 2:

    const user = clone(this.state.user);
    console.log(user === this.state.user); // false
    

    现在,如果没有对对象属性进行任何更改,它们就完全不同了。通过克隆原始对象,您可以创建具有不同引用的新副本。

    克隆函数可能如下所示(ES6 语法)

    const clone = obj => Object.assign({}, ...obj);
    

    浅比较是检测更改的有效方法。它希望您不要改变数据。

    【讨论】:

    • 所以如果我们正在编写代码,那么如果我们有标量值,那么我们是否应该改变它们,因为如果我们克隆它们,相等性检查将返回 false?
    • @AjayGaur 虽然这个答案可以帮助你理解 JavaScript 中的严格相等(===),但它并没有告诉你关于 React 中的 shallowCompare() 函数的任何信息(我猜回答者误解了你的问题)。 shallowCompare() 所做的实际上是在您提供的文档中:迭代正在比较的对象的键,并在每个对象中的键的值不严格相等时返回 true。如果你还是不明白这个函数以及为什么不应该改变状态,我可以为你写一个答案。
    • 这个答案描述了 JS 中相等 (==) 和严格相等 (===) 运算符之间的区别。问题是关于浅比较,在 React 中是通过检查两个对象的所有 props 之间的相等性来实现的。
    • @sunquan 你能写一个答案吗?
    【解决方案2】:

    React 中还有浅比较的legacy explanation

    shallowCompare 对当前 props 和 nextProps 对象以及当前 state 和 nextState 对象执行浅层相等检查。

    它通过迭代被比较对象的键并在每个对象中的键值不严格相等时返回 true 来实现这一点。

    UPDCurrent documentation 说浅比较:

    如果你的 React 组件的 render() 函数在给定相同的 props 和 state 的情况下呈现相同的结果,你可以在某些情况下使用 React.PureComponent 来提升性能。

    React.PureComponent 的 shouldComponentUpdate() 只是对对象进行浅层比较。如果这些包含复杂的数据结构,则可能会为更深层次的差异产生假阴性。仅在您希望拥有简单的 props 和 state 时扩展 PureComponent,或者在您知道深层数据结构已更改时使用 forceUpdate()

    UPD2:我认为Reconciliation也是浅比较理解的重要主题。

    【讨论】:

    • and returning true when the values中不应该是“假”
    【解决方案3】:

    浅比较是当被比较对象的属性使用“===”或严格相等完成时,不会对属性进行更深入的比较。例如

    // a simple implementation of the shallowCompare.
    // only compares the first level properties and hence shallow.
    // state updates(theoretically) if this function returns true.
    function shallowCompare(newObj, prevObj){
        for (key in newObj){
            if(newObj[key] !== prevObj[key]) return true;
        }
        return false;
    }
    // 
    var game_item = {
        game: "football",
        first_world_cup: "1930",
        teams: {
             North_America: 1,
             South_America: 4,
             Europe: 8 
        }
    }
    // Case 1:
    // if this be the object passed to setState
    var updated_game_item1 = {
        game: "football",
        first_world_cup: "1930",
        teams: {
             North_America: 1,
             South_America: 4,
             Europe: 8 
        }
    }
    shallowCompare(updated_game_item1, game_item); // true - meaning the state
                                                   // will update.
    

    虽然这两个对象看起来相同,但game_item.teamsupdated_game_item.teams 的引用不同。对于两个相同的对象,它们应该指向同一个对象。 因此,这导致被评估的状态被更新

    // Case 2:
    // if this be the object passed to setState
    var updated_game_item2 = {
        game: "football",
        first_world_cup: "1930",
        teams: game_item.teams
    }
    shallowCompare(updated_game_item2, game_item); // false - meaning the state
                                                   // will not update.
    

    这一次每个属性都返回 true 以进行严格比较,因为新旧对象中的 teams 属性指向同一个对象。

    // Case 3:
    // if this be the object passed to setState
    var updated_game_item3 = {
        first_world_cup: 1930
    }
    shallowCompare(updated_game_item3, game_item); // true - will update
    

    updated_game_item3.first_world_cup 属性未通过严格评估,因为 1930 是一个数字,而 game_item.first_world_cup 是一个字符串。如果比较松散(==),这将过去。尽管如此,这也会导致状态更新。

    补充说明:

    1. 进行深度比较毫无意义,因为如果状态对象嵌套较深,则会显着影响性能。但如果它不是太嵌套并且您仍然需要深度比较,请在 shouldComponentUpdate 中实现它并检查是否足够。
    2. 您绝对可以直接改变状态对象,但组件的状态不会受到影响,因为它在 setState 方法流程中 react 实现了组件更新周期挂钩。如果您直接更新状态对象以故意避免组件生命周期挂钩,那么您可能应该使用简单的变量或对象来存储数据,而不是状态对象。

    【讨论】:

    • 这不是说如果我通过 props 传递一个对象或将状态与下一个状态进行比较,组件将永远不会重新渲染,因为即使该对象的属性发生了变化,它仍然会指向同一个对象,因此,导致错误,因此,不重新渲染?
    • @javascripting - 这就是为什么当你的对象改变而不是改变它们时你会克隆(例如使用 Object.assign())你的对象,这样 React 就会知道引用何时改变并且组件需要更新.
    • 如果prevObj 包含newObj 没有的键,则比较将失败。
    • @mzedeler - 它不会因为“for in”迭代 newObj 而不是 prevObj。尝试在浏览器开发者控制台中运行代码。此外,请不要太认真地对待这种浅比较的实现,这只是为了展示这个概念
    • 数组呢?
    【解决方案4】:

    浅比较的工作原理是检查两个值在原始类型的情况下是否相等,如字符串、数字和在对象的情况下,它只检查引用。因此,如果您对深层嵌套对象进行浅层比较,它只会检查引用而不是该对象内的值。

    【讨论】:

      【解决方案5】:

      如果prevObj 有一个newObj 没有的键,上面@supi (https://stackoverflow.com/a/51343585/800608) 的浅等sn-p 会失败。这是一个应该考虑到这一点的实现:

      const shallowEqual = (objA, objB) => {
        if (!objA || !objB) {
          return objA === objB
        }
        return !Boolean(
          Object
            .keys(Object.assign({}, objA, objB))
            .find((key) => objA[key] !== objB[key])
        )
      }
      

      请注意,以上内容在没有 polyfill 的 Explorer 中不起作用。

      【讨论】:

      • 看起来不错,但在这种情况下,传递两个 NaN 返回 false,而在之前的答案中返回 true。
      【解决方案6】:

      有一个带有示例的实现。

      const isObject = value => typeof value === 'object' && value !== null;
      
      const compareObjects = (A, B) => {
        const keysA = Object.keys(A);
        const keysB = Object.keys(B);
       
        if (keysA.length !== keysB.length) {
          return false;
        }
       
        return !keysA.some(key => !B.hasOwnProperty(key) || A[key] !== B[key]);
      };
      
      const shallowEqual = (A, B) => {
        if (A === B) {
          return true;
        }
       
        if ([A, B].every(Number.isNaN)) {
          return true;
        }
        
        if (![A, B].every(isObject)) {
          return false;
        }
        
        return compareObjects(A, B);
      };
      
      const a = { field: 1 };
      const b = { field: 2 };
      const c = { field: { field: 1 } };
      const d = { field: { field: 1 } };
      
      console.log(shallowEqual(1, 1)); // true
      console.log(shallowEqual(1, 2)); // false
      console.log(shallowEqual(null, null)); // true
      console.log(shallowEqual(NaN, NaN)); // true
      console.log(shallowEqual([], [])); // true
      console.log(shallowEqual([1], [2])); // false
      console.log(shallowEqual({}, {})); // true
      console.log(shallowEqual({}, a)); // false
      console.log(shallowEqual(a, b)); // false
      console.log(shallowEqual(a, c)); // false
      console.log(shallowEqual(c, d)); // false

      【讨论】:

        【解决方案7】:

        接受的答案对某些人来说可能有点误导。

        user = {
          name: "John",
          surname: "Doe"
        }
        
        const user = this.state.user;
        user.name = "Jane";
        
        console.log(user === this.state.user); // true
        

        此语句特别是“注意您更改了用户名。即使进行此更改,对象也是相等的。它们的引用完全相同。”

        当您对 javascript 中的对象执行以下操作时:

        const a = {name: "John"};
        const b = a;
        

        改变两个变量中的任何一个都会改变它们,因为它们具有相同的引用。这就是为什么它们总是彼此相等 (==, ===, Object.is())。

        现在对于 React,以下是浅比较函数: https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js

        /**
         * Performs equality by iterating through keys on an object and returning false
         * when any key has values which are not strictly equal between the arguments.
         * Returns true when the values of all keys are strictly equal.
         */
        function shallowEqual(objA: mixed, objB: mixed): boolean {
          if (is(objA, objB)) {
            return true;
          }
        
          if (typeof objA !== 'object' || objA === null ||
              typeof objB !== 'object' || objB === null) {
            return false;
          }
        
          const keysA = Object.keys(objA);
          const keysB = Object.keys(objB);
        
          if (keysA.length !== keysB.length) {
            return false;
          }
        
          // Test for A's keys different from B.
          for (let i = 0; i < keysA.length; i++) {
            if (
              !hasOwnProperty.call(objB, keysA[i]) ||
              !is(objA[keysA[i]], objB[keysA[i]])
            ) {
              return false;
            }
          }
        
          return
        

        对于非基元(对象),它会检查:

        1. 如果第一个对象与第二个对象相等(使用Object.is())。
        2. 如果不是,它检查第一个对象中的每个键值对是否等于(使用Object.is())第二个对象。这是为第一级键完成的。如果对象的键值是另一个对象,则此函数不会在对象的深度进一步检查相等性。

        【讨论】:

        【解决方案8】:

        很容易理解。首先需要了解纯组件和常规组件,如果一个组件有即将到来的 props 或状态正在改变,那么它将再次重新渲染该组件。 如果不是,那么不是。 在常规组件中 shouldComponentUpdate 默认为 true。而在纯组件中,只有状态随 diff 值变化的时候。

        那么现在什么是浅层组件或浅层? 让我们举一个简单的例子。 让 a = [1,2,3], 设 b = [1,2,3],

        a == b ==> 浅以为假, a == c ==> 浅 相信它。 c 有任何 diff 值。

        现在我想你可以理解了。具有浅分量的常规分量和纯分量的差异 如果你喜欢它,也喜欢分享和订阅我的 youtube 频道 https://www.youtube.com/muosigmaclasses

        谢谢。

        【讨论】:

          【解决方案9】:

          我觉得没有一个答案真正解决了您问题中的关键部分,答案只是解释了浅比较是什么(无论它们是指 JavaScript 默认浅比较,它是 ===== 运算符的结果或 React 的 shallowCompare() 函数)

          为了回答你的问题,到目前为止,我对 React 的理解让我相信确实是的通过不直接改变状态,那么shouldComponentUpdate 将始终返回 true,因此总是会导致重新渲染我们在setState 中传递了哪些对象,即使传递给setState 的对象与当前状态中存储的值相同

          示例:

          假设我有一个具有当前状态和功能的 React.Component:

          this.state = {data: {num: 1}} // current state object
              
          foo() { // something will cause this function to called, thus calling setState
                 this.setState( {data: {num: 1}} ); // new state object
          }
          

          您可以看到 setState 传递了相同的对象(按值),但是普通的 React 不够聪明,无法意识到该组件不应该更新/重新渲染。

          为了克服这个问题,您必须实现您的 shouldComponentUpdate 版本,您自己在其中对您认为应该考虑的 state/props 元素进行深入比较。

          查看this article on lucybain.com,它简要回答了这个问题。

          【讨论】:

            猜你喜欢
            • 2019-09-06
            • 1970-01-01
            • 1970-01-01
            • 2012-07-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多