propsにobjectを使用するとReact.memoがメモ化できない

# propsにobjectを渡すとメモ化してくれない

```jsx

import React, { useState, memo } from "react";
import ReactDOM from "react-dom";

function App() {
  const [cnt, setCnt] = useState(1);
  const obj = { hoge: "hoge" };
  return (
    <div className="App">
      {cnt}
      <button onClick={() => setCnt(cnt + 1)}>button</button>
      <Test arr={obj} />
    </div>
  );
}

const Test = memo(props => {
  console.log("test", props.arr);
  return <div>test</div>;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

```

上記の例で行くと、buttonを押下したときに`test`は表示して欲しくない。
けど、実際にはボタンを押すたびに`test`が表示されるのでメモ化できていないということになる。
{hoge: "hoge"}を毎回渡しているのでpropsは変化していないにも関わらず。

## React.memoのshallowEqual
React.memoは第二引数に比較用の関数を渡さなければshallowEqualが実行される。
shallowEqual、、名前だけ聞くとオブジェクトの一階層の値が同じであれば同じobjectと判断してくれそうに感じる。
が、上にあげた例の通り実際には違うobjectと判断する。

shallowEqualはReactが用意した関数なので、中身をのぞいてみる。
https://github.com/facebook/react/blob/cbbc2b6c4d0d8519145560bd8183ecde55168b12/packages/shared/shallowEqual.js

オブジェクトの比較は以下でやってそうである。

```javascript
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
  return false;
}


// Test for A's keys different from B. 
for (let i = 0; i < keysA.length; i++) {
  if (
    !hasOwnProperty.call(objB, keysA[i]) ||
    !is(objA[keysA[i]], objB[keysA[i]])
  ) {
    return false;
  }
}
```

なるほど、やっぱりオブジェクトの中身まで見てくれてるじゃん。
と、思ったがよく見るとそうではなかった。
`props = {arr: {hoge: 'hoge'}};`の`Object.keys(props)`の結果は`['arr']`である。
なので、`!is(objA[keysA[i]], objB[keysA[i]]`が必ず`false`となるのです:point_up:
ちなみに、isは同じファイルの中に書いてある比較用関数。今回のケースでは`===`でobject同士が比較されてfalseとなる。

```javascript
const hoge = {hoge: 'hoge'};
const fuga = {hoge: 'hoge'};

hoge === fuga; // false
```

結果、shallowEqualはfalseを返し、React.memoが思った様に動作してくれないのでした。

## useMemoで回避
上記例でいうと、 `const obj`のあたりをuseMemoでメモ化した値を使う様にすることでReact.memoは思った通りに動作してくれる様になる。


## 備考
ちなみに、React.memoを使わずにuseMemoだけで行けると思って試した下記はダメだった。

```jsx

import React, { useState, memo, useMemo } from "react";
import ReactDOM from "react-dom";

function App() {
  const [cnt, setCnt] = useState(1);
  const obj = { hoge: "hoge" };
  console.log("app");
  return (
    <div className="App">
      {cnt}
      <button onClick={() => setCnt(cnt + 1)}>button</button>
      <Test arr={obj} />
    </div>
  );
}

const Test = props => {
  return useMemo(() => { // ここだけ変えた
    console.log("test", props.arr);
    return <div>test</div>;
  }, [props.arr]);
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

ボタンを押下するたびにApp()が実行されて、objが毎回生成されるのでprops.arrに詰めるobjの参照が毎回違うためです。

---

## さらに備考

下記は同じ様に見えて違う

```jsx


function App() {

.....~~
.....~~

return (
    <div className="App">
      {cnt}
      <button onClick={() => setCnt(cnt + 1)}>button</button>
      <Test arr={obj} ccc={obj2} /> {/* 渡すのはどちらもuseMemoでメモ化したもの */}
    </div>
  );
}




// memoされる
const Test = memo(({ arr, ...rest }) => {
  console.log("test", arr, rest);
  return <div>test</div>;
});

// memoされない
const Test = ({ arr, ...rest }) => {
  return useMemo(() => {
    console.log("test", arr, rest);
    return <div>test</div>;
  }, [arr, rest]);
};

```

React.memoの方は`props`と`props`で比較を行う。
だから、rest parametersを使用していようがいまいが関係ない。
useMemoの方は`arr`と`arr`、`rest`と`rest`を比較する。
arrの比較は同じと判断されるが、restはTestが実行されるたびに毎回作成されるので参照先が違う。

参照: https://aloerina01.github.io/blog/2018-10-25-1


---

メモ

useMemoの比較は以下でやっているっぽい。
https://github.com/facebook/react/blob/901d76bc5c8dcd0fa15bb32d1dfe05709aa5d273/packages/react-reconciler/src/ReactFiberHooks.js#L299

Object.isのpolyfillを自分で用意して`===`で比較しテイるぽい。
shallowEqualは使用していなさそうだけど、結局やってることは全く同じに見える。