Як реагує робота з поверхневим порівнянням


96

У цій документації 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?
Аджай Гаур

29
@AjayGaur Хоча ця відповідь може допомогти вам зрозуміти сувору рівність (===) в JavaScript, але вона нічого не говорить про функцію shallowCompare () в React (я думаю, відповідач неправильно зрозумів ваше запитання). Те, що робить shallowCompare (), насправді є у наданому вами документі: ітерація на клавішах об’єктів, що порівнюються, і повернення істини, коли значення ключа в кожному об’єкті не є строго однаковими. Якщо ви все ще не розумієте цієї функції і чому вам не слід мутувати стан, я можу написати вам відповідь.
sunquan

7
Це неправда. побачити це. github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/…
Bright Lee,

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.

Цього разу кожне із властивостей повертає істину для суворого порівняння, оскільки властивість команд у новому та старому об’єкті вказує на той самий об’єкт.

// 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

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

а як щодо масивів?
Хуан Де ла Круз,

27

Помітне порівняння працює, перевіряючи, чи рівні два значення у випадку примітивних типів, таких як рядок, числа, а у випадку об'єкта - просто перевірка посилання . Отже, якщо ви поверхнево порівняєте глибоко вкладений об’єкт, він просто перевірить посилання, а не значення всередині цього об’єкта.


11

У React також є застаріле пояснення неглибокого порівняння:

shallowCompare виконує неглибоку перевірку рівності поточного об'єкта props та nextProps, а також поточного стану та об'єктів nextState.

Це робиться шляхом ітерації на клавішах об’єктів, що порівнюються, і повернення істини, коли значення ключа в кожному об’єкті не є строго однаковими.

UPD : Поточна документація говорить про неглибоке порівняння:

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

React.PureComponent's shouldComponentUpdate () лише неглибоко порівнює об’єкти. Якщо вони містять складні структури даних, це може спричинити помилкові негативи для глибших відмінностей. Розширюйте 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])
  )
}

Зауважте, що вищезазначене не працює в Explorer без поліфілів.


Виглядає добре, але в цьому випадку передача двох повертань 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.