Jak działa płytkie porównanie w reakcji


97

W tej dokumentacji React jest powiedziane, że

shallowCompare wykonuje płytkie sprawdzenie równości na bieżących obiektach props i nextProps, a także na bieżącym stanie i obiektach nextState.

Rzecz, której nie jestem w stanie zrozumieć, to to, że If It płytko porównuje obiekty, to metoda shouldComponentUpdate zawsze zwróci wartość true, ponieważ

Nie powinniśmy mutować stanów.

a jeśli nie modyfikujemy stanów, porównanie zawsze zwróci fałsz, a więc aktualizacja shouldComponent zawsze zwróci wartość true. Nie wiem, jak to działa i jak możemy to zmienić, aby zwiększyć wydajność.

Odpowiedzi:


132

Płytkie porównanie sprawdza równość. Porównując wartości skalarne (liczby, łańcuchy), porównuje ich wartości. Porównując obiekty, nie porównuje ich atrybutów - porównywane są tylko ich odniesienia (np. „Czy wskazują na ten sam obiekt?).

Rozważmy następujący kształt userobiektu

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

Przykład 1:

const user = this.state.user;
user.name = "Jane";

console.log(user === this.state.user); // true

Zauważ, że zmieniłeś nazwę użytkownika. Nawet przy tej zmianie obiekty są równe. Odniesienia są dokładnie takie same.

Przykład 2:

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

Teraz bez zmian właściwości obiektów są one zupełnie inne. Klonując oryginalny obiekt, tworzysz nową kopię z innym odniesieniem.

Funkcja Clone może wyglądać tak (składnia ES6)

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

Płytkie porównanie to skuteczny sposób wykrywania zmian. Oczekuje, że nie zmienisz danych.


Więc jeśli piszemy kod, to jeśli mamy wartości skalarne, to czy powinniśmy je zmutować, ponieważ jeśli je sklonujemy, sprawdzenie równości zwróci fałsz?
Ajay Gaur

30
@AjayGaur Chociaż ta odpowiedź może pomóc ci zrozumieć ścisłą równość (===) w JavaScript, ale nie mówi nic o funkcji shallowCompare () w React (myślę, że osoba odpowiadająca źle zrozumiała twoje pytanie). To, co robi shallowCompare (), znajduje się w dostarczonym przez ciebie dokumencie: iteruje na kluczach porównywanych obiektów i zwraca wartość true, gdy wartości klucza w każdym obiekcie nie są dokładnie równe. Jeśli nadal nie rozumiesz tej funkcji i dlaczego nie powinieneś mutować stanu, mogę napisać dla Ciebie odpowiedź.
sunquan


5
Ta odpowiedź opisuje różnicę między operatorami równości (==) i ścisłej równości (===) w JS. Chodzi o płytkie porównanie, które w Reakcie realizowane jest poprzez sprawdzenie równości wszystkich właściwości dwóch obiektów.
Mateo Hrastnik

@sunquan, czy możesz napisać odpowiedź w tej sprawie?
Ajay Gaur

35

płytkie porównanie ma miejsce wtedy, gdy właściwości porównywanych obiektów są wykonywane przy użyciu „===” lub ścisłej równości i nie prowadzą do głębszych porównań we właściwościach. np

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

Chociaż oba obiekty wydają się być takie same, game_item.teamsnie jest tym samym odniesieniem, co updated_game_item.teams. Aby 2 obiekty były takie same, powinny wskazywać na ten sam obiekt. W ten sposób skutkuje to aktualizacją ocenianego stanu

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

Tym razem każda z właściwości zwraca wartość true dla ścisłego porównania, ponieważ właściwość zespołów w nowym i starym obiekcie wskazuje na ten sam obiekt.

// 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_cupNieruchomość nie ścisła ocena jak 1930 jest liczbą natomiast game_item.first_world_cupjest ciągiem. Gdyby porównanie było luźne (==), to by minęło. Niemniej jednak spowoduje to również aktualizację stanu.

Dodatkowe uwagi:

  1. Przeprowadzanie głębokiego porównania jest bezcelowe, ponieważ znacząco wpłynęłoby na wydajność, gdyby obiekt stanu był głęboko zagnieżdżony. Ale jeśli nie jest zbyt zagnieżdżony i nadal potrzebujesz głębokiego porównania, zaimplementuj go w shouldComponentUpdate i sprawdź, czy to wystarczy.
  2. Zdecydowanie można bezpośrednio zmienić obiekt stanu, ale stan komponentów nie zostanie zmieniony, ponieważ w przepływie metody setState, która reaguje, implementuje punkty zaczepienia cyklu aktualizacji składnika. Jeśli aktualizujesz obiekt stanu bezpośrednio, aby celowo unikać haków cyklu życia komponentu, prawdopodobnie powinieneś używać prostej zmiennej lub obiektu do przechowywania danych, a nie obiektu stanu.

Czy nie oznacza to, że jeśli przekażę obiekt za pomocą właściwości lub porównam stan do następnego stanu, komponent nigdy nie wyrenderuje się ponownie, ponieważ nawet jeśli właściwości tego obiektu ulegną zmianie, nadal będzie wskazywał na ten sam obiekt, w wyniku czego fałszywe, a więc nie ponowne renderowanie?
javascripting,

@javascripting - to jest powód, dla którego sklonowałbyś (używając na przykład Object. assign ()) swoje obiekty, gdy się zmieniają, zamiast je mutować, aby React wiedział, kiedy zmieni się odniesienie i komponent wymaga aktualizacji.
Mac_W,

Jeśli prevObjzawiera klucz, którego newObjnie ma, porównanie zakończy się niepowodzeniem.
mzedeler

@mzedeler - nie będzie, ponieważ „for in” iteruje na newObj, a nie na prevObj. spróbuj uruchomić kod tak, jak jest w konsoli programisty przeglądarki. Co więcej, nie traktuj tej implementacji płytkiego porównania zbyt poważnie, to tylko po to, aby zademonstrować koncepcję
supi

a co z tablicami?
Juan De la Cruz

27

Płytkie porównywanie działa poprzez sprawdzenie, czy dwie wartości są równe w przypadku typów pierwotnych, takich jak łańcuch, liczby, aw przypadku obiektu po prostu sprawdza referencję . Więc jeśli porównasz płytko głęboko zagnieżdżony obiekt, po prostu sprawdzi odniesienie, a nie wartości wewnątrz tego obiektu.


11

Istnieje również starsze wyjaśnienie płytkiego porównania w Reakcie:

shallowCompare wykonuje płytkie sprawdzenie równości na bieżących obiektach props i nextProps, a także na bieżącym stanie i obiektach nextState.

Robi to poprzez iterację na kluczach porównywanych obiektów i zwracanie prawdy, gdy wartości klucza w każdym obiekcie nie są dokładnie równe.

UPD : Aktualna dokumentacja mówi o płytkim porównaniu:

Jeśli funkcja render () twojego komponentu React renderuje ten sam wynik przy tych samych właściwościach i stanie, możesz użyć React.PureComponent do zwiększenia wydajności w niektórych przypadkach.

Metoda powinnaComponentUpdate () w React.PureComponent tylko płytko porównuje obiekty. Jeśli zawierają złożone struktury danych, mogą generować fałszywie ujemne wartości dla głębszych różnic. Rozszerz PureComponent tylko wtedy, gdy spodziewasz się prostych właściwości i stanu, lub użyj forceUpdate (), gdy wiesz, że głębokie struktury danych uległy zmianie

UPD2: Myślę, że pojednanie jest również ważnym tematem dla zrozumienia płytkiego porównania.


czy nie powinno być „fałszem” wand returning true when the values
rahulg

2

Płytki równy fragment kodu @supi powyżej ( https://stackoverflow.com/a/51343585/800608 ) kończy się niepowodzeniem, jeśli prevObjma klucz, którego newObjnie ma. Oto implementacja, która powinna to wziąć pod uwagę:

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

Zauważ, że powyższe nie działa w Eksploratorze bez polyfillów.


Wygląda dobrze, ale w tym przypadku przekazanie dwóch NaN zwraca fałsz, podczas gdy w poprzedniej odpowiedzi jest to prawda.
Spadar zamknięty

0

Jest implementacja z przykładami.

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.