Как работает мелкое сравнение в React


97

В этой документации React сказано, что

shallowCompare выполняет поверхностную проверку равенства для текущих объектов props и nextProps, а также для объектов текущего состояния и nextState.

Я не могу понять, что если он неглубоко сравнивает объекты, тогда метод shouldComponentUpdate всегда будет возвращать true, так как

Мы не должны изменять состояния.

и если мы не изменяем состояния, то сравнение всегда будет возвращать false, и поэтому обновление shouldComponent всегда будет возвращать true. Я не понимаю, как это работает, и как мы переопределим это, чтобы повысить производительность.

Ответы:


132

Неглубокое сравнение действительно проверяет равенство. При сравнении скалярных значений (чисел, строк) сравнивает их значения. При сравнении объектов он не сравнивает их атрибуты - сравниваются только их ссылки (например, «указывают ли они на один и тот же объект?»).

Рассмотрим следующую форму 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?
Аджай Гаур

30
@AjayGaur Хотя этот ответ может помочь вам понять строгое равенство (===) в JavaScript, но он ничего не говорит вам о функции shallowCompare () в React (я думаю, ответчик неправильно понял ваш вопрос). То, что делает shallowCompare (), фактически содержится в предоставленном вами документе: выполняет итерацию по ключам сравниваемых объектов и возвращает истину, если значения ключа в каждом объекте не строго равны. Если вы все еще не понимаете эту функцию и почему вам не следует изменять состояние, я могу написать для вас ответ.
sunquan


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. Чтобы два объекта были одинаковыми, они должны указывать на один и тот же объект. Таким образом, это приводит к тому, что состояние оценивается как обновляемое.

// 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 для строгого сравнения, поскольку свойство team в новом и старом объекте указывает на один и тот же объект.

// 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, который реагирует, реализует перехватчики цикла обновления компонентов. Если вы обновляете объект состояния напрямую, чтобы сознательно избегать перехватов жизненного цикла компонента, то, вероятно, вам следует использовать простую переменную или объект для хранения данных, а не объект состояния.

Разве это не означает, что если я передаю объект через свойства или сравниваю состояние со следующим состоянием, компонент никогда не будет повторно визуализироваться, потому что даже если свойства этого объекта изменились, он все равно будет указывать на тот же объект, что приведет к false, значит, не повторный рендеринг?
javascripting

@javascripting - вот почему вы должны клонировать (используя, например, Object.assign ()) свои объекты, когда они изменяются, а не изменять их, чтобы React знал, когда ссылка изменится и компонент нуждается в обновлении.
Mac_W

Если prevObjсодержит ключ, newObjкоторого нет, сравнение не удастся.
mzedeler 08

@mzedeler - не будет, потому что "for in" повторяется в newObj, а не в prevObj. попробуйте запустить код в консоли разработчика браузера. Более того, пожалуйста, не относитесь к этой реализации поверхностного сравнения слишком серьезно, это просто для демонстрации концепции
supi

как насчет массивов?
Хуан Де ла Крус,

27

Неглубокое сравнение работает, проверяя , равны ли два значения в случае примитивных типов, таких как строка, числа, а в случае объекта оно просто проверяет ссылку . Поэтому, если вы неглубоко сравниваете глубоко вложенный объект, он просто проверяет ссылку, а не значения внутри этого объекта.


11

Существует также устаревшее объяснение неглубокого сравнения в React:

shallowCompare выполняет поверхностную проверку равенства для текущих объектов props и nextProps, а также для объектов текущего состояния и nextState.

Это достигается путем перебора ключей сравниваемых объектов и возврата true, если значения ключа в каждом объекте не строго равны.

UPD : Текущая документация говорит о неглубоком сравнении:

Если функция render () вашего компонента React отображает тот же результат при тех же свойствах и состоянии, вы можете в некоторых случаях использовать React.PureComponent для повышения производительности.

Метод shouldComponentUpdate () React.PureComponent лишь поверхностно сравнивает объекты. Если они содержат сложные структуры данных, это может привести к ложноотрицательным результатам для более глубоких различий. Расширяйте PureComponent только тогда, когда вы ожидаете иметь простые свойства и состояние, или используйте forceUpdate (), когда знаете, что глубокие структуры данных изменились.

UPD2: Я думаю , что сверка также важная тема для неглубокого сравнения понимания.


не должно ли это быть "ложным" вand returning true when the values
rahulg

2

Неглубокий равный фрагмент от @supi выше ( https://stackoverflow.com/a/51343585/800608 ) не работает, если 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])
  )
}

Обратите внимание, что это не работает в проводнике без полифиллов.


Выглядит хорошо, но в этом случае передача двух NaN возвращает false, тогда как в предыдущем ответе это правда.
Spadar Shut

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.