今回は、Reactを使い始めてコンポーネントめっちゃ便利やん!使いまわせるやん!って調子に乗っていたら、値の更新(with 非同期処理)でハマったのでそこで学習した内容になります。
ズバリ、コンポーネントのライフサイクルを全く知らなかったのが原因でした。。
ってことで、ライフサイクルについてみていきたいと思います!
まず、Reactは仮装DOMってのを作ってそれをほんちゃんのDOM(HTML)に反映させて画面の操作をしておりまっする。なんでそんなことしてるんやって言われると、ググって欲しいんやけど、確か仮装DOM内を操作する方が、直接ほんちゃんDOMを操作するより速いんやってさ。
ってのも、仮装DOMを操作した後にほんちゃんのに反映させる際に、変更があった箇所のみを再レンダリングするかららしい。
これに対して、jQueryなんかは直接ほんちゃんDOMを操作していくんやが
この人は、単純なのはいいけど、複雑なものになってくるとDOMの操作に向いてないと個人的に思ってます。
タグの追加や削除でDOM操作していくんやけど、その過程でDOM構造が変わるから、操作したいタグがどこにあるのか見つけるのにも一苦労やったりする。。。(だって元のHTMLのDOM構造じゃなくなってるわけですからね)
やからSPAとかDOM操作めちゃんこやりまっせーってフロントは、jQueryよりReactとかVue、Angularなどのライブラリ使ってったほうが絶対いいですね!
ま、前置きはこのぐらいにして本題に入りたいと思います!
※本記事は、コンポーネント使って画面つくれますよって方向けになってます
コンポーネントは、ライフサイクルに関する関数をいくつか持ってます。
その全貌がこれだ!

一個ずつ説明していくより、見た方が早いんでお見せします!
まず、こんなReactアプリがあったとします!

App.jsは、子コンポーネントとしてHeader.jsとContents.jsを持ってます。
ページ番号が「0」から「4」まであって、デフォルト値は「0」です。
Header.jsでページ番号を切り替えるため、App.jsはページ番号をstateに保持しており、Header.js, Contents.jsにページ番号をpropsで渡します。
実際の画面はこんな感じ(初回アクセス時)

この条件で、ここにアクセスするとこうなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
APP============================== constructor APP============================== componentWillMount HEADER============================== constructor HEADER============================== componentWillMount CONTENTS============================== constructor CONTENTS============================== componentWillMount LOGINPAGE============================== constructor LLOGINPAGE============================== componentWillMount HEADER============================== componentDidMount LOGINPAGE============================== componentDidMount CONTENTS============================== componentDidMount APP============================== componentDidMount |
App.jsのconstructorがまず呼ばれて、続いてcomponentWillMount。
render()の中でHeader.jsとContents.jsを読んでいるため、次に
Header.jsのconstructor、componentWillMountと続きます。
そしてContents.jsのconstructor、componentWillMount。
Contents.jsは子コンポーネントとしてLoginPage.jsを持ってるので
LoginPage.jsのconstructor、componentWillMountと続く。
App.jsが持つ子コンポーネント全てのcomponentWillMountまでが完了したので
次はcomponentDidMountがHeader.js、Contents.jsと続きます。
※Contents.jsは子コンポーネントにLoginPage.jsを持ってるので、先にLoginPage.jsのcomponentDidMountが完了する。
で、子供たちのcomponentDidMountが無事に完了するのを見守ってから、いっちゃん最後にApp.jsのcomponentDidMountが完了する。
さあ、これで下の図の左上の部分は完了したことになる。

次は、実際にログインしてみる。
ログインすると非同期処理でサーバからデータを取得してきてページ番号を「1」に変える。「1」に変わることで画面はCurrentPageになる
実際の動きがこれ
このリストをサーバから非同期で取得してきています。(このアプリは作成途中なのであしからず)
では、ログはどのようになっているのでしょうか
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
APP============================== shouldComponentUpdate APP============================== componentWillUpdate HEADER============================== componentWillReceiveProps HEADER============================== shouldComponentUpdate HEADER============================== componentWillUpdate CONTENTS============================== componentWillReceiveProps CONTENTS============================== shouldComponentUpdate CONTENTS============================== componentWillUpdate CURRENTPAGE============================== constructor CURRENTPAGE============================== componentWillMount LOGINPAGE============================== componentWillUnmount HEADER============================== componentDidUpdate CURRENTPAGE============================== componentDidMount CONTENTS============================== componentDidUpdate APP============================== componentDidUpdate CURRENTPAGE============================== shouldComponentUpdate CURRENTPAGE============================== componentWillUpdate CURRENTPAGE============================== componentDidUpdate |
LoginPage.jsでは、ログインボタンをクリックするとApp.jsのstateのpageを0から1に更新するようにしているので、stateが更新されたApp.jsのshouldComponentUpdateが実行されます。(App.jsは親コンポーネントから何のpropsももらってないので、componentWillReceiveProps()は実行されません。
App.jsの子コンポーネントであるHeader.jsとContents.jsはApp.jsからpropsをもらってるため、componentWillReceiveProps()→shouldComponentUpdate()→componentWillUpdate()の順に実行されます。
で、Contents.jsの子コンポーネントは、LoginPage.jsからCurrentPage.jsに変わるため、CurrentPage.jsのconstructor、componentWillMount()が実行される。
そのあとに、LoginPageはアンマウント(componentWillUnmount())される。
これでDOM操作は完了したのでrendering処理が終わり、Header.jsのcomponentDidUpdate()が実行される。
それに続いて、Contents.js側も子コンポーネントであるCurrentPage.jsのcomponentDidMount()が実行され、Contents.js自身もcomponentDidUpdate()が実行される。
★CurrentPageはここで初めてDOMが生成されマウントされるので、実行するイベントはcomponentDidUpdate()ではなく、componentDidMount()になる
そして、はたまた、子供たちのDOMの更新が済んだのを待ってからApp.jsのcomponentDidUpdate()が実行されるのだ!
はい、ここで今回のハマりポイント!
App.jsのcomponentDidUpdate()の後に、まだCurrentPageのイベントが残ってますよね?
CurrentPage.jsの状態が変化したことでDOMの更新が行われています。
これ、CurrentPageに画面が変わった時にサーバから「現在のタスク」を取得する非同期処理を実施していて、取得した後にその内容をCurrentPageに反映させるためのものです。
ライフサイクルを知らなかった時は、ここでどうしても状態取得した内容がCurrentPageに反映されず四苦八苦してました笑
どうやってるかと言うと、CurrentPage.jsのcomponentDidMount()イベントが発火した際に、非同期でサーバからデータを取得し、CurrentPage.jsのstateを更新してます。
stateが更新されることで、コンポーネントの内容も更新されるので、
shouldComponentUpdate()、componentWillUpdate()、componentDidUpdate()が実行されるって感じです。
何ですけども!
正直この実装はいまいちで、非同期処理はcomponentWillMount()イベント内に書いた方が良さげです!
って言うのもcomponentDidMount()内に書いてしまっては、一度無駄にrenderingしていることになります。
無駄にrenderingした後に自身で自身のstateが更新されたっていってshouldComponentUpdatae()以下のイベントを実行しています。
ダメダメですね笑
ま、でもライフサイクルを勉強したことで一応意図した挙動にすることができました。
ミソは
renderingの前に非同期処理を実行すること!
おまけと言っては何ですが、実はReactにはAjax通信を簡単に実装できるsuperagentと言うライブラリがあるみたいです。笑
こいつの使い方ですが、例としてはこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//インポートする import request from 'superagent'; class Something extends Component { //コンストラクタはこんな感じ constructor(props){ super(props); //stateは必ず初期化(ここがnullなのを利用して非同期処理をします。 this.state = { items : null } } //非同期処理を実行するのはこのライフサイクルイベントで! componentWillMount(){ //Ajax通信 request.get('URL') .accept('application/json') .end( (err, res ) => { this.loadJSON( err, res ) }) } loadJSON( err, res ) { if (err) { console.log('ERROR'); } //Success this.setState({ items : res.body } ); } render () { if ( !this.state.item ){ return <div> Now On Loading...</div> } let options = this.state.items.map ( elem => { return <option value ={elem.price } key={elem.name}> { elem.name} </option> }) return ( <div> <select> { options } </div> ) } } |
superagent使えば、簡単にPromise使った非同期処理が実装できます!
callbackでズタズタになった僕には嬉しいライブラリです。
これからも精進していきます!
以上、React コンポーネントのライフサイクルでした! BYE
コメント