Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strict Equality (===) versus Shallow Equality Checks in React-Redux

I'm studying how Hooks APIs provided by React-Redux-v.7.1 works, and saw it mentioned in Equality Comparisons and Updates (https://react-redux.js.org/api/hooks#equality-comparisons-and-updates) that:

"As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector()."

I'm wondering why strict equality is better than shallow equality that used by connect()? Then I looked into their equality comparisons:

The default strict equality check of useSelector() is simply checking a===b

const refEquality = (a, b) => a === b

And the equality checks in react-redux/src/connect/connect.js is using Object.is() and also other checks, which is same as that in react.

// The polyfill of Object.is()
function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}

According to the description of Object.is() in MDN: "Object.is does no type conversion and no special handling for NaN, -0, and +0 (giving it the same behavior as === except on those special numeric values)."

I have no idea why a === b is better than a series of equality checks. (It's my first time asking questions here, apologize for rudeness or lack of information)

like image 389
Shelly Hsueh Avatar asked Oct 03 '19 04:10

Shelly Hsueh


People also ask

What is shallow equal in react redux?

​ React-Redux uses shallow equality checking to determine whether the component it's wrapping needs to be re-rendered. To do this, it assumes that the wrapped component is pure; that is, that the component will produce the same results given the same props and state.

What is shallow equality 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.

What is shallow comparison and deep comparison in react?

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.

What is the difference between shallow comparison and deep comparison of objects?

What you call "shallow equal" is identity: two references (i.e. objects) are identical if they are the very same instances. If you know what pointers are in other languages, you can compare identity to pointer equality. What you call "deep equal" is equality: two objects a and b are equal if a.


1 Answers

With connect, mapStateToProps returns a composite object of all of the state being selected from the store, so a shallow comparison across its keys makes sense. With useSelector, the pattern is often to only return a single value for each invocation of useSelector, similar to the way that the useState hook only handles a single value instead of all of the state values. So if each call to useSelector returns a value directly then a strict equality check makes sense vs a shallow comparison. A short example should make this more clear.

import {connect} from 'react-redux';

const mapStateToProps = state => (
  {keyA: state.reducerA.keyA, keyB: state.reducerB.keyB}
);
export default connect(mapStateToProps)(MyComponent);

Here mapStateToProps is called every time the store changes in any way, so the return value will always be a new object, even if keyA and keyB do not change. So a shallow comparison is used to decide if a re-render is needed.

For the hooks case:

import {useSelector} from 'react-redux';

function MyComponent(props) {
  const keyA = useSelector(state => state.reducerA.keyA);
  const keyB = useSelector(sate => state.reducerB.keyB);
  ...
}

Now the result of the useSelector hooks are the individual values from the store, not a composite object. So using strict equality as the default here makes sense.

If you want to use only a single useSelector hook that returns a composite object, the docs you linked to have an example of using the shallowEqual equality function: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates

like image 169
azundo Avatar answered Sep 22 '22 17:09

azundo