浅层比较如何在反应中工作

How does shallow compare work in react

本文关键字:工作 比较      更新时间:2023-09-26

在这个 React 文档中,据说

shallowCompare 对当前 props 和 nextProps 对象以及当前状态和 nextState 对象执行浅相等性检查。

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

我们不应该改变状态。

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

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

让我们考虑以下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);

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

浅层比较是指使用"==="或严格相等性完成被比较的对象的属性,并且不会对属性进行更深入的比较。

例如
// 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引用不同。要使 2 个对象相同,它们应指向同一对象。因此,这会导致正在评估的状态被更新

// 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 实现了组件更新周期钩子。如果您直接更新状态对象以有意避免组件生命周期挂钩,那么您可能应该使用简单的变量或对象来存储数据,而不是状态对象。

浅表比较的工作原理是检查两个值是否相等,如果是字符串、数字等基元类型如果是对象,它只检查引用。因此,如果您浅层比较深层嵌套对象,它只会检查引用而不是该对象中的值。

在 React 中也有浅层比较的遗留解释:

shallowCompare 对当前 props 和 nextProps 对象以及当前状态和 nextState 对象执行浅相等性检查。

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

UPD:当前文档说浅比较:

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

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

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

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

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() )与第二个对象的键值对相等。这是针对第一级密钥完成的。如果对象具有值为另一个对象的键,则此函数不会进一步检查对象深度的相等性。

花了一段时间才真正知道shallow compare===是两回事,尤其是在阅读下文中的 redux 文档时。

但是,当将操作调度到 Redux 存储时,useSelector() 仅在选择器结果看起来与上一个结果不同时才强制重新渲染。从 v7.1.0-alpha.5 开始,默认比较是严格的 === 引用比较。这与 connect() 不同,connect() 对 mapState 调用的结果使用浅相等性检查来确定是否需要重新渲染。这对你应该如何使用 useSelector() 有几个含义。

严格平等

因此,一步

一步地,Javascript语言非常一致地定义了严格的平等===,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality

它的作用是按值比较两个项目(如果它们是原始的),然后通过引用(如果它们是对象)进行比较。当然,如果两个对象的类型不同,它们将永远不匹配。

浅比较

浅层可能不是该语言的内置功能。这里的几个答案向我们指出了实现的一些变化,https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js

这个想法是按值比较两个项目,如果它们是原始的。但是对于非基元,我们低一级。对于对象,如果两个对象之间的键不同,我们说它们不相同。如果键下的值不同,我们说它们也不相同。

总结

这意味着,浅层比较检查比严格的相等===更多,尤其是在涉及对象时。快速浏览可能会表明,===不会做太多的猜测工作。

如果上面

(https://stackoverflow.com/a/51343585/800608) 的浅相等代码段@supi prevObj具有newObj没有的键,则会失败。下面是一个应该考虑到这一点的实现:

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

请注意,上述内容在没有填充项的资源管理器中不起作用。

有一个带有示例的实现。

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

很容易理解。 首先需要了解纯组件和常规组件,如果组件有即将到来的 props 或状态正在更改,那么它将再次重新渲染组件。如果不是,那就不是。在常规组件中,默认情况下应该组件更新为真。在纯组件中,只有状态随差异值变化的时间。

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

a ==

b ==> 浅取假,a == c ==> 浅表地认为这是真的。c 具有任何差异值。

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

谢谢。

我觉得没有一个答案实际上解决了你问题中的关键部分,答案只是解释了什么是浅层比较(无论它们是否意味着 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版本,在这个版本中,你自己对你认为应该考虑的状态/道具元素进行深度比较。

查看这篇关于 lucybain.com 的文章,简要回答了这个问题。