[React]setState実行時のrenderメソッドの動きを検証する

# reactのsetStateによるrenderメソッド実行の動きを考える

- setStateとrerenderの動きを検証してみた
    - 検証はReactNativeで行ったがReactでも同じであろう
- 内部の動きはソースを追ってみたけどよく分からなかった
    - 詳しい記事などあれば教えて下さい

## 検証1

### 背景

- React.Componentを継承したReactコンポーネントでは、`setState`を呼び出してstateを更新することで、renderメソッドが実行されて再レンダリングされると理解している
- そうなると、一連の処理の中で`setState`を何度も呼ぶとrenderメソッドが呼ばれまくってレンダリングされまくるんじゃないかという疑問があった

### 検証内容

- ボタンをクリックしたらsetStateを1000回呼んで見る
- renderメソッドにconsole.logを仕掛けといてrenderが何回呼ばれたか確認する

### 検証用コード

```jsx:App.js
import React, { Component } from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";

export default class App extends Component {
  state = { now: "ready" };

  onPress = () => {
    [...Array(1000)].forEach(() => {
      console.log("---");
      this.setState({ now: new Date().toLocaleTimeString() });
    });
  };

  render() {
    console.log("renderメソッド");
    console.log(this.state);
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.onPress}>
          <Text>スタート</Text>
        </TouchableOpacity>
        <Text>{this.state.now}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center"
  }
});
```

### キャプチャ

![demo1.gif](https://qiita-image-store.s3.amazonaws.com/0/175213/ef48e4b5-b165-08c3-5534-ae8026a456fb.gif)

### 結果

- renderメソッドは一度だけしか呼ばれなかった
- ループが1000回まわっている => `setState`が1000回呼ばれていることも確認できる
- ボタンを押してから画面の表示内容が変更されるまで間がある

### 考察

- `setState`を呼ぶ度にrenderメソッドが呼ばれる訳ではなさそう
- 一連の処理が終わってから最後にrenderが呼ばれ描画内容が更新されるっぽい

## 検証2

### 背景

- 検証1の結果を受けて、`onPress`の先頭でstateAを`setState`し、重い処理のあと最後にstateBを`setState`したとする
- その時にstateAの更新結果はすぐに反映されずに待たされてしまうのか？と疑問に思った

### 検証内容

- 以下の流れで動作させる
    - ボタンをクリックしたら`start`というstateに時刻をsetする
    - `console.log`を5000回呼ぶ(時間のかかる処理ならなんでもよかった)
    - 最後に`stop`というstateに時刻をsetする

### 検証コード

```jsx:App.js
export default class App extends Component {
  state = { start: "", stop: "" };

  onPress = () => {
    this.setState({ start: new Date().toLocaleTimeString() });
    [...Array(5000)].forEach(() => {
      console.log("---");
    });
    this.setState({ stop: new Date().toLocaleTimeString() });
  };

  render() {
    console.log("renderメソッド");
    console.log(this.state);
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.onPress}>
          <Text>スタート</Text>
        </TouchableOpacity>
        <Text>start: {this.state.start}</Text>
        <Text>stop: {this.state.stop}</Text>
      </View>
    );
  }
}
```

### キャプチャ

![demo2.gif](https://qiita-image-store.s3.amazonaws.com/0/175213/1d52e9e7-b36c-28df-d36f-4168cb8a2e7e.gif)

### 結果

- 最初にsetしたstartというstateはすぐに反映されなかった
- 処理が全部終わってからまとめて画面に反映された

### 考察

- 処理が全部終わった後にrenderメソッドが呼ばれその時点のstateが反映されてるっぽい
- (あまりないとは思うが)重い処理を同期的に実行する場合、setStateの結果が画面に反映されるのは処理が終了してからだと覚えておいた方が良い

## 検証3

### 背景

- 検証2の結果を受けて、重い処理をしているとstateの反映が待たされると分かった
- だが、通信処理中にローディングを出すような時は一連の処理の中で`loading`のようなstateを切り替えてそれが反映されてるよなと思い出した
- 非同期処理を実行するとそこで一度renderが呼ばれるんじゃないかと予想されるのでそれを試す

### 検証内容

- `loading`というstateがtrueだと画面を暗くしてインジケータを出すようにする
- onPressの先頭で`loading`にtrueをsetする
- 3秒待つだけの非同期処理を行う
- `loading`にfalseをsetする

### 検証コード

```jsx:App.js
import React, { Component } from "react";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import Spinner from "react-native-loading-spinner-overlay";

export default class App extends Component {
  state = { loading: false };

  timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

  onPress = async () => {
    this.setState({ loading: true });
    console.log("start");

    await this.timeout(3000);

    console.log("done");
    this.setState({ loading: false });
  };

  render() {
    console.log("renderメソッド");
    console.log(this.state);
    return (
      <View style={styles.container}>
        {/* visibleがtrueだと画面が暗くなってインジケータが出る */}
        <Spinner
          visible={this.state.loading}
          overlayColor="#rgba(0, 0, 0, .2)"
        />
        <TouchableOpacity onPress={this.onPress}>
          <Text>スタート</Text>
        </TouchableOpacity>
      </View>
    );
  }
}
```

### キャプチャ

![demo3.gif](https://qiita-image-store.s3.amazonaws.com/0/175213/795dbdca-3632-83f9-546d-4445c8572d46.gif)

### 結果

- 想定通り非同期処理が実行されたタイミングで一度renderメソッドが呼ばれた
- ちなみに`timeout`の引数を0にした場合もコンソールの出力内容は同じ

### 考察

- 非同期処理が実行されるとrenderメソッドが呼ばれる
- 非同期処理のコールバック後の処理が完了するとまたrenderメソッドが呼ばれる

## まとめ

- setStateを呼ぶとすぐにrenderメソッドが呼ばれるものと思っていたがそんなことはなかった
- setStateが散財すると見通しが悪くなってしまうので、この挙動を頭に入れて無駄なコードを書かないようにしたい