stateとライフサイクル
クラスに変換
5ステップで関数コンポーネントをクラスコンポーネントに変換する。
https://ja.reactjs.org/docs/state-and-lifecycle.html#converting-a-function-to-a-class
function Clock(props) { return ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); }
React.Componet
を継承する同名の ES6 クラスを作成render()
と呼ばれる空のメソッドを 1 つ追加- 関数の中身を
render()
メソッドに移動 render()
内のprops
をthis.props
に書き換え- 空になった関数を削除
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString ()}.</h2> </div> ); } }
同一の DOM ノード内で Clock
コンポーネントをレンダーしている限り、 Clock
クラスのインスタンスは1つだけ使われる。
ローカル state
やライフサイクルメソッドなどが利用できるようになる。
state を追加する
3ステップで date
を props
から state
に移す。
render()
メソッド内のthis.props.date
をthis.state.date
に書き換えthis.state
の初期状態を設定するコンストラクタを追加<Coloc />
要素からdate
プロパティを削除
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
現在の時刻と共に「更新されない」ページが表示された。
ふとした疑問が、「現在の時刻」とは、 DOMContentLoaded イベントだろうか?または、 load イベントだろうか?
初心者な私は確認方法がわからない。
ライフサイクルメソッドを追加
マウント : 最初にコンポーネントが DOM として描画されるとき。出力が DOM にレンダーされた「後」に実行される アンマウント : コンポーネントが生成した DOM が削除されるとき
componentDidMound() { // } componentWillUnmound() { // }
タイマーをセットアップするのは、 componentDidMound()
ライフサイクルメソッド内です。
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); }
タイマーの後片付けは componetWillUnmount()
ライフサイクルメソッド内で行う。
componentWillUnmound() { clearInerval(this.timerID) }
コンポーネントのローカル state 更新を行うために this.setState()
を追加する。
tick() { this.setState({ date: new Date(), });
毎秒ごとに時間を刻み続ける。
メソッドが呼び出される順序にそって簡単に振り返ってみましょう https://ja.reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
丁寧な解説がなされている。コンポーネントの一生について疑問が生じた際は、読みにいくことになりそう。
state を正しく利用する
setState()
の3つの重要な事柄
state
を直接変更しない
this.state.comment = 'Hello';
代わりに setState()
を使う
this.setState({comment: 'Hello'})
時計の例では次のように使用しました。
tick() { this.setState({ date: new Date() }); }
でも待ってください。 constructor
では、次のように書いています。
constructor(props) { super(props); this.state = {date: new Date()}; }
初期化時は例外なのだろうか?
this.state に直接代入してよい唯一の場所はコンストラクタです。
読み落とすところでした。
更新は非同期に行われる可能性がある
this.props
と this.state
は非同期に更新されるため、それらの値に依存してはならない。
更新に失敗することのあるコード
this.setState({ counter: this.state.counter + this.props.increment, });
オブジェクトではなく、関数を受け取る setState()
を使う。
setState()
- 第1引数:直前の
state
- 第2引数:更新が適用される時点での
props
this.setState((state, props) => ({ counter: state.counter + props.increment }));
更新はマージされる
setState()
を呼び出した場合、与えられたオブジェクトを現在の state
にマージする。
JavaScript Primer で学んだ オブジェクトのマージ 。ほとんど理解できなかった嫌な記憶が蘇った。
別々の setState()
呼び出しで、変数を独立して更新できる。
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }) }) }
以上が setState()
の3つの重要な事柄だそうです。
データは下方向に伝わる
コンポーネントはその子コンポーネントに props
として自身の state
を渡してよい。
<FormattedDate date={this.stae.date} />
FormattedDate
コンポーネントは props
経由で date
を受け取るが、Clock
クラスの state
から来たのか、 props
から来たのかわからない。
function FormattedDate(props) { return <h2>It is {props.date.toLocaleTimeString()}.</h2> }
単一方向 とか 単一方向データバインディング と呼ばれる。
3つの<Clock>
コンポーネントをレンダーする App
コンポーネントを作成。
function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
<Clock />
はそれぞれインスタンスを作るので、独立はしているはず。カプセル化もされているはず。
時代は Functionコンポーネント
React は現時点では、 Hooks を使って Functionコンポーネントを定義することが一般的です。
Clock
クラスコンポーネントをファンクションコンポーネントに書き換えに挑戦します。
時計の現在の時刻と、それを更新するために useStae
が必要になります。
ステートフルな値と、それを更新するための関数を返します。
const [date, setDate] = React.useState(new Date());
副作用を有する可能性のある命令型のコードを受け付けます。 DOM の書き換え、データの購読、タイマー、ロギング、あるいはその他の副作用を、関数コンポーネントの本体(React のレンダーフェーズ)で書くことはできません。 代わりに useEffect を使ってください。
useEffect
時とともに DOM の書き換えが必要なタイマーのためにある。間違いない!
tick()
関数を1秒間隔で呼び出します。
React.useEffect(() => { const timerID = setInterval(() => tick(), 1000);
Clock
コンポーネントが DOM
から削除される場合を考慮した処理を追加します。
公式サイトで指摘されなければ、削除処理が必要なことに気づかないだろうな!
return function cleanup() { clearInterval(timerID); };
tick()
関数を実装します。現在時刻の状態を更新する必要があるので、 setDate()
を利用する。
const tick = () => setDate(new Date());
Clock
クラスでは、レンダーの中では state
の date
属性でした。
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
Clock
ファンクションでは、 date
は次のように書くことができます。 date
は useState()
で定義されたので、 ステートそのものだからでしょうか?
わかっていない。
<h2>It is {date.toLocaleTimeString()}.</h2>
完成したコードを掲載する。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Don't use this in production: --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> const Clock = () => { const [date, setDate] = React.useState(new Date()); // componentDidMountとcomponentWillUnmountを置き換え React.useEffect(() => { const timerID = setInterval(() => tick(), 1000); return async function cleanup() { await clearInterval(timerID); }; }); // 更新関数に現在の時刻を渡す const tick = () => setDate(new Date()); return ( <div> <h1>Hello, world!</h1> <h2>It is {date.toLocaleTimeString()}.</h2> </div> ); }; ReactDOM.render(<Clock />, document.getElementById('root')); </script> </body> </html>