35
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-04

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

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

検証1

背景

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

検証内容

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

検証用コード

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

結果

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

考察

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

検証2

背景

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

検証内容

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

検証コード

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

結果

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

考察

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

検証3

背景

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

検証内容

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

検証コード

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

結果

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

考察

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

まとめ

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?