浅表比较如何在反应中起作用


96

React文档中,据说

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

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

我们不应该改变国家。

如果我们不改变状态,则比较将始终返回false,因此shouldComponent更新将始终返回true。我对它的工作方式以及如何将其覆盖以提高性能感到困惑。

Answers:


131

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

让我们考虑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?
Ajay Gaur

29
@AjayGaur尽管此答案可以帮助您理解JavaScript中的严格相等(===),但是它并不能告诉您关于React中的shallowCompare()函数的任何信息(我想回答者会误解了您的问题)。ShallowCompare()的作用实际上在您提供的文档中:循环访问要比较的对象的键,并在每个对象中的键值不严格相等时返回true。如果您仍然不了解此功能以及为什么不应该更改状态,我可以为您写一个答案。
sunquan '16


5
该答案描述了JS中的等于(==)和严格等于(===)运算符之间的区别。问题是浅层比较,在React中通过检查两个对象的所有道具之间的相等性来实现。
Mateo Hrastnik

@sunquan能否请你写一个答案?
阿杰·高尔

35

浅表比较是指使用“ ===”或严格等式完成比较对象的属性,并且不会对属性进行更深层的比较。例如

// 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.teams但是与的引用不同updated_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方法流中可以实现组件更新周期挂钩。如果直接更新状态对象以故意避免组件生命周期挂钩,则可能应该使用简单的变量或对象而非状态对象来存储数据。

并不是说如果我通过道具传递对象或将状态与下一个状态进行比较,则该组件将永远不会重新渲染,因为即使该对象的属性发生了变化,它仍将指向同一对象,因此,导致假,因此,不重新渲染?
javascript

@javascripting-这就是为什么您在对象更改时克隆(例如使用Object.assign())而不是对其进行突变的原因,因此React会在引用更改和组件需要更新时知道。
Mac_W

如果prevObj包含没有的键newObj,则比较将失败。
mzedeler

@mzedeler-不会因为“ for in”在newObj而不是prevObj上进行迭代。尝试按浏览器开发人员控制台中的方式运行代码。此外,请不要过于重视这种浅表比较的实现,这只是为了演示概念
Supi

数组呢?
Juan De la Cruz

27

浅比较通过检查基本值(例如字符串,数字)和对象的情况下两个值是否相等来进行工作,它仅检查引用。因此,如果您浅比较深层嵌套的对象,它将只检查引用而不是该对象内部的值。


11

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

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

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

UPD当前文档中有关浅比较的内容:

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

React.PureComponent的shouldComponentUpdate()仅对对象进行比较。如果这些数据包含复杂的数据结构,则可能会产生假阴性,从而产生更大的差异。仅当您期望具有简单的道具和状态时才扩展PureComponent,或者当您知道深层数据结构已更改时才使用forceUpdate()

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


它不应该是“假”and returning true when the values
吗?

2

如果没有键,则上述@supi(https://stackoverflow.com/a/51343585/800608)的浅等号片段将失败。这是一个应该考虑到的实现:prevObjnewObj

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。
Spadar

0

有示例的实现。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.