useState を書きながら学ぶ を書きながら学ぶ（React Hooks 入門シリーズ 4/6）

## ロードマップ

React 16.8 で追加された機能であるReactの`Hooks`について書いてあります。

書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。

- [React.memo / useCallback / useMemo](https://qiita.com/ryosuketter/items/c503d98e16c28f516c4d)
- [Context / useContext](https://qiita.com/ryosuketter/items/90bd9edd5d91b8daf5c9)
- [useRef](https://qiita.com/ryosuketter/items/f2cc554cc89fb41d5a0b)
- [useState](https://qiita.com/ryosuketter/items/40dfc139ed90ebffd284) :point_left_tone2: 今回
- [useEffect](https://qiita.com/ryosuketter/items/6551ee0d75a3da003980)
- [useReducer](https://qiita.com/ryosuketter/items/3d59f1c503fe27fa0cb9)

## はじめに

Reactの組み込みフックである`useState`の説明をします。

## useState とは

state と stateの更新関数を返すフックです。

このフックを利用すれば、コンポネント内でstate管理ができます。

state が 更新されるとコンポネントは再レンダリングされます。

### 構文

```js
const [state, setState] = useState(state初期値)
```

### 活用例...カウントアプリ

<details><summary>サンプルコード</summary><div>

```js:App.js
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);

  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
};

export default App;
```

</div></details>

### 活用例...カウントアプリ（setStateの更新関数の引数に関数を渡すバージョン）

<details><summary>サンプルコード</summary><div>

```js:App.js
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount((currentCount) => currentCount + 1);

  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
};

export default App;
```

</div></details>

これの何が嬉しいのか？ => ロジックをコンポネントの外に切り出せる

これによって、ロジックが外部に依存しにくくなります。また、共通化しやすくなるメリットもあります。

<details><summary>サンプルコード（setStateの引数に渡した関数を外部化）</summary><div>

```js:App.js
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount((currentCount) => currentCount + 1);

  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
};

export default App;
```

最初に書いたsetStateの引数だと、`setState(count + 1)`だと、外部に切り出せませんが

setStateの更新関数の引数に関数を渡すバージョンだと、コンポネント外に切り出せます。

```js:App.js
import React, { useState } from "react";

// setState内のロジックを外部化
const updateCount = (currentCount) => currentCount + 1;

const App = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount(updateCount);

  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
};

export default App;
```

</div></details>

### 活用例...オブジェクトをどう扱うか

本の情報を扱う object を state にして表示してみましょう。

<details><summary>サンプルコード</summary><div>

```js:App.js
import React, { useState } from "react";

const bookInfo = {
  title: "サピエンス全史",
  author: "ユヴァル・ノア・ハラリ",
  price: 2000
};

const App = () => {
  const [book, setBook] = useState(bookInfo);

  // 値が更新される関数
  const updateBookInfo = () => setBook({ ...book, price: book.price + 1 });

  // 値が更新されない関数
  const updateBookInfo2 = () => {
    book.price = book.price = 1;
    setBook(book);
  };

  return (
    <div className="App">
      <p>{book.title}</p>
      <p>{book.author}</p>
      <p>{book.price}</p>
      <button onClick={updateBookInfo}>update</button>
    </div>
  );
};

export default App;
```

</div></details>

仮に、`...book, `を消してしまい、関数`updateBookInfo`が以下のようだと...

```js
const updateBookInfo = () => setBook({ price: book.price + 1 });
```

setState 更新後の値は、`{price: 2001}` と、priceだけになるでしょう。これは、setStateが差分だけマージじゃなく置換だからです。

#### 注意点

buttonクリック時の関数を

- 関数`updateBookInfo`にすると、値は更新される
- 関数`updateBookInfo2`にすると、値は更新されない

原因は `updateBookInfo2` に渡された `book` が変更前と同じとみなされたからです。

React の state は `Object.is()` で変更の有無を判定しているらしいです。

- `Object.is()` が true なら 同じ（変更してない）と見なして Reactの場合コンポネントが再レンダリングされない
- `Object.is()` が false なら 変更したと見なして Reactの場合コンポネントが再レンダリングされる
 - だけど、stateの中身は更新されます。コンポネントが再レンダリングされないからユーザーの目には変わってないように見えます

![aea9d1cb006f9392d73740585cfdd435.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/8ffa720e-9621-73c4-4158-497bc39de968.gif)

`Object.is`に関しては、以下が参考になりました。

https://qiita.com/uhyo/items/d99f536332e03442c432#objectis

一方で、関数`updateBookInfo`は、スプレッド演算子を利用して新たなオブジェクトを生成しているので`Object.is()`の結果はfalseになりコンポネントが再レンダリングされます。

これは、`setState`関数が配列の場合でも同じです。

今回は以上です。

## 参考

https://ja.reactjs.org/tutorial/tutorial.html