React は”ユーザインターフェース構築のための JavaScript ライブラリ”を自称しています。実際 React を使うと素の JavaScript に比べてユーザインタフェースを簡単に多くに正確に作れ、変更もしやすいです。
そんな React には state という仕組みがあります。
state とライフサイクル – React
state は”コンポーネント”の状態を表現する仕組みです。もっといえば画面の状態を表現します。具体的な挙動では React のコンポーネントは自身の持つ state が変わった時に画面を再構築(再レンダリング)する、という挙動があります。この state とは現画面の状態という設計が setState を代入の代わりとして考えた時、罠になります。具体的には次です。
this.state1Increment = () => {
this.setState({ state1: this.state.state1 + 1 });
// ↓の記述では↑の setState で渡した state1 でなく state1Increment 実行開始時の state1 が state1Copy にセットされます。
this.setState({ state1Copy: this.state.state1 });
};
クリックしてソースコード全体を展開
import React from "react";
import "./styles.css";
class SomeClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
state1: 0,
state1Copy: 0
};
this.state1Increment = () => {
this.setState({ state1: this.state.state1 + 1 });
this.setState({ state1Copy: this.state.state1 });
};
}
render() {
return (
<div className="col">
<div>state1: {this.state.state1}</div>
<div>state1copy: {this.state.state1Copy}</div>
<button onClick={this.state1Increment}>state1++</button>
</div>
);
}
}
function SomeFunctionComponent() {
const [state1, setStat1] = React.useState(0);
const [state1Copy, setState1Copy] = React.useState(0);
const state1Increment = () => {
setStat1(state1 + 1);
setState1Copy(state1);
};
return (
<div className="col">
<div>state1: {state1}</div>
<div>state1copy: {state1Copy}</div>
<button onClick={state1Increment}>state1++</button>
</div>
);
}
export default function App() {
return (
<div className="App">
<h2>クラスコンポーネント</h2>
<SomeClassComponent />
<h2>関数コンポーネント</h2>
<SomeFunctionComponent />
</div>
);
}
state1 に値を setState でセットする、とした直後に state1Copy に state1 の値をセットする、というコードです。一見 state1Copy に直前に setState でセットするとした値が代入されると思うかもしれませんが、されません。setState でセットした結果をその setState 実行時から次のレンダリングまでに参照しようとすると一つ前の結果が返ってきます。関数コンポーネントの場合、useState で渡される現状態はプリミティブな値として渡されるので「それはそうだろう」という納得の結果ですがクラスコンポーネントでは裏側で変更可能なオブジェクトのプロパティ(extends 元やインスタンスを参照する React のエンジン部とかでそれなりに好き勝手出来る場所)で state を見るため想定と異なる動作と思う時があります。
ローカル変数を用意する、setState には既に定まった値を入れる、など色々な記述でこの挙動に対応できますが一番は React の思想に付き合うことだと思います。state に関しての話は公式ドキュメントの次が詳しいです。
コンポーネントの state – React