In this documentation of React, it is said that
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
The thing which I am unable to understand is If It shallowly compares the objects then shouldComponentUpdate method will always return true, as
We should not mutate the states.
and if we are not mutating the states then the comparison will always return false and so the shouldComponent update will always return true. I am confused about how it is working and how will we override this to boost the performance.
Shallow compare does check for equality. When comparing scalar values (numbers, strings) it compares their values. When comparing objects, it does not compare their's attributes - only their references are compared (e.g. "do they point to same object?).
Let's consider following shape of user
object
user = {
name: "John",
surname: "Doe"
}
Example 1:
const user = this.state.user;
user.name = "Jane";
console.log(user === this.state.user); // true
Notice you changed users name. Even with this change objects are equal. They references are exactly same.
Example 2:
const user = clone(this.state.user);
console.log(user === this.state.user); // false
Now, without any changes to object properties they are completely different. By cloning original object you create new copy, with different reference.
Clone function might look as this (ES6 syntax)
const clone = obj => Object.assign({}, ...obj);
Shallow compare is efficient way to detect changes. It expect you don't mutate data.
shallow comparison is when the properties of the objects being compared is done using "===" or strict equality and will not conduct comparisons deeper into the properties. for e.g.
// 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.
Although both the objects appear to be same, game_item.teams
is not the same reference as updated_game_item.teams
. For 2 objects to be same, they should point to the same object.
Thus this results in the state being evaluated to be updated
// 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.
This time every one of the properties return true for the strict comparison as the teams property in the new and old object point to the same object.
// 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
The updated_game_item3.first_world_cup
property fails the strict evaluation as 1930 is a number while game_item.first_world_cup
is a string. Had the comparison been loose (==) this would have passed. Nonetheless this will also result in state update.
Additional Notes:
Shallow compare works by checking if two values are equal in case of primitive types like string, numbers and in case of object it just checks the reference. So if you shallow compare a deep nested object it will just check the reference not the values inside that object.
There is also legacy explanation of shallow compare in React:
shallowCompare performs a shallow equality check on the current props and nextProps objects as well as the current state and nextState objects.
It does this by iterating on the keys of the objects being compared and returning true when the values of a key in each object are not strictly equal.
UPD: Current documentation says about shallow compare:
If your React component's render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
React.PureComponent's shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed
UPD2: I think Reconciliation is also important theme for shallow compare understanding.
The shallow equal snippet by @supi above (https://stackoverflow.com/a/51343585/800608) fails if prevObj
has a key that newObj
doesn't have. Here is an implementation that should take that into account:
const shallowEqual = (objA, objB) => {
if (!objA || !objB) {
return objA === objB
}
return !Boolean(
Object
.keys(Object.assign({}, objA, objB))
.find((key) => objA[key] !== objB[key])
)
}
Note that the above doesn't work in Explorer without polyfills.
There is an implementation with examples.
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
©2020 All rights reserved.