2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
読解 Flutter Hooks(全1記事)
リンクをコピー
記事をブックマーク
Kyohei Ito氏:「読解Flutter Hooks」というタイトルで、発表します。簡単に自己紹介です。伊藤恭平と言います。GitHubやTwitterでは、KyoheiG3という名前で活動しています。主にiOSのアプリの開発をやっていますが、だいたい4、5ヶ月くらい前からFlutterをやり始めました。
今日の内容ですが、Hooksの使い方などはやらずに、Hooksの動きを理解するところを目標にしようかと思っています。そのため、今日の発表は、コードが多めになってしまいますが、よろしくお願いします。
簡単にHooksの説明というか、振り返りをします。もともとはReact.jsの16.8、19年2月以降に追加された機能で。Flutterにも、有名なRemi Rousseletさんが、2018年12月に初回コミットしていました。
「どうしてHooksを使ったほうがいいの?」という話ですが、ExampleというReactのコンポーネント。これはJSのコードです。
見たらなんとなくわかると思いますが、Exampleというclassを定義して、その中でstateというオブジェクトを作って。下のほうを見ていくと、ボタンをクリックしたらその中でsetStateを呼び出して、stateのcountを+1しているような実装がなんとなくわかるのではないかと思います。
次は、Hookを使った実装です。useStateを使っていますが、一番大きな違いとしては、Example、classで定義されていたものがfunctionに変わっています。そのfunctionの中で、useStateをconstで定義して、returnの中はほぼ一緒ですが、onClickの中でsetCountを呼び出して、countを+1するような感じになっています。
Hooksを使うと、状態の管理などをclassを使わず、単体で行えるようになります。Flutterをやっている方ならピンとくると思います。Flutterも同じような実装があるというか。Reactコンポーネントのclassの部分がいわゆるStatefulWidgetで、functionの実装がStatelessWidgetになるんじゃないかと思います。
これが今言ったとおりです。Flutterで書くと、Example、classでStatefulWidgetで定義して、Stateの中でCountを持って、buildの中でボタンを押したらsetStateを呼んで、countを+1する。
それをStatelessWidgetでHooksを使う。これ、HookWidgetをStatelessWidgetと読み換えてもらって。buildの中でuseStateを定義して、ボタンを押したらcountを1+する。
このように、状態の管理などをStatefulWidgetを使わず、同じように単体で行えました。実際どういう動き、どういう仕組みになっているのかです。readmeを見ると、こんなソースコードが書いてあります。ちょっと要領を得ないというか、難しいというか(笑)。抽象化されすぎていて難しいので、実際の中のコードを読んでみましょう。主な登場人物として、HookWidgetとHookElementが出てきます。
先ほどのExampleのStatelessWidgetの例です。Exampleのソースコード、class自体はHookWidgetを継承しています。そのため、HookWidgetの中身を見てみると、普通にStatelessWidgetで宣言されていて、createElementというメソッドでStatelessHookElementをreturmしています。
StatelessHookElementはHookElementのmixinを適用しているので、その中を見ると、これもめちゃくちゃソースコードを端折っていますが、メインとなるのはこのbuildというメソッドの中。
buildのメソッドの中は何をやっているかと言うと、Hooksという配列からオブジェクトを取り出しています。1発目にnullが入りますが、その直後にcurrentHookElementに自分自身を突っ込んで、tryの中でsuperのbuildを呼び出す。その直後、finallyの中で、currentHookElementにnullを突っ込んでいると。
どういうことかと言うと、要するにsuperのbuildを呼び出している間だけstaticの変数のcurrentHookElementに自分自身を突っ込んでいます。呼び出しが終わったら、nullを入れて参照できないようにしています。
またExampleのclassに戻ります。実際にbuildしているuseStateの中を、ちょっと見てみたいと思います。これもチョイチョイ端折っていますが、useStateは中でuseというメソッドを呼び出して、引数としてStateHookのインスタンスを渡しています。
useは何を呼び出しているかと言うと、Hookのstaticメソッドです。その中で先ほど出てきたcurrentHookElementを参照して、その_useというメソッドを呼び出しています。currentHookElementには先ほどのbuildを実行中のelementが入っているため、_useを問題なく呼び出せます。
では_useはどうなっているのか。これは、先ほどのHookElementの中身です。_useの中で、currentHookStateは1回目はnullが入っているため、if文が3つあるうちの一番上に入ってきて。createHookStateをして、それを変数に保持して、Hooksにそれ自身を追加しています。
if文はいったんスルーしてもらって、下のほうでHookStateをbuildして、その結果をresultとして返しています。ちなみに、とばしたif文の中身が何かと言うと、ランタイムタイプが違っていたらデバック時にしか基本的に起こり得ませんが、デバックだと、ホットリロード的なことを行うと、ランタイムタイプが変わってしまう可能性がある。
実装で、例えばuseStateを違うHookに変えてしまった場合に起こり得ますが、リリースビルドの時にもしここに入ってしまったら、処理が落ちるようになっています。次は“実際に引数に渡されている値が変わった”というif文ですが、その時は普通にアップデートされたり、Stateがもう1回作り直されたりする処理が走るようになっています。
useStateの中にグローバルのuseが使われているなど、ちょっと回りくどい実装になっていたと思います。HookWidget自体にuseのメソッドがあれば解決しそうな気がしますが、遠回りの処理をしている理由の1つとして、HookBuilderの存在があるのではと。
HookBuilderはHookWidgetを継承しています。先ほどのExampleのclassではHookWidgetを継承していましたが、あそこはStatelessWidgetなどを使ったときに、useのHooksのメソッドを呼び出すためのクロージャーを実行するWidgetになっています。
こういう実装にしておくと、build中のelementをどこでも参照できる状態になります。例えばuseContextというメソッドがあります。これもグローバルでどこからでも呼べますが、build中であれば持っているcurrentHookElementをreturnして、Contextを参照するという、便利なこともできるようになっています。
ここまでがだいたいWidgetの中身です。HookとHookStateが、実際にuseメソッドの実態になります。ここからは、先ほどから出てきているuseStateをちょっと見てみたいと思います。
これもuseを呼び出し、StateHookを作って呼び出しています。StateHookは何をやっているかと言うと、Hookのclassを継承して、イニシャルデータを保持して、createStateを作って返しています。
HookStateを継承していますが、この中ではinitHookでValueNotifierを作ってaddlistenerして。listenerが呼ばれたらsetStateを実行して、その下でdisposeが呼ばれたときに、そのstateをdisposeするような実装になっています。
これらのライフサイクル以外にも、いろいろなライフサイクルのメソッドがありますが、このあたりはreadmeを見てもらえればわかると思います。例えば、下から4つ目くらいにmarkMayNeedRebuildというメソッドがありますが、こういうメソッドをStateの中で呼ぶと、実際にWidgetがRebuildされる処理も行えるようになります。
最初から用意されているHooksもいくつかありますが、そのあたりもreadmeを見てもらえればと思います。
あとはCustom Hookです。Hookを自分で作れて、その作り方ですが、大きく2つ。classとfunctionを使って作れます。FixedExtentScrollControllerというものの、Hookがなかったので用意してみたんですけれども。
useStateと一緒ですね。initialでItemを受け取って、createStateでHookStateを継承しているclassを返す。initHookの中でFixedExtentScrollControllerのインスタンスを作って、buildというメソッドでcontrollerをreturnして、disposeが呼ばれたらcontrollerをdisposeするような処理を書けば完成です。
メソッドが直接呼ばれないようにするために、グローバルに一応1つメソッドを用意して、そこから呼び出すような処理にするといいかと思います。
あともう1つ、これもreadmeから持ってきましたが、functionでHookを作れます。メソッドの中で、ほかにもうすでに用意されているHookのメソッドを使って、Hookを作れるのです。これは、useStateを使って、その値が変わったときにログを吐き出すHookになっています。
一応いくつかルールがあるため、簡単に説明すると、名前は、useというプレフィックスを使いましょうとなっています。ここは決まりのようなものです。
あとは、Hookを使うときに、buildの条件分岐の中でuseを呼び出すことは禁止されています。これはelementに配列でHookが保持されている状態ですが、if文で配列の呼び出しの順番が変わってしまうと処理がおかしくなってしまうので、これはNGになっています。
あとはホットリロードについてです。ホットリロードも基本的にデバック中であれば普通に動くように作られていて。例えばこういうuseA、B、Cみたいなメソッドを順番に呼ぶような実装にしていたとして。開発の途中で、0というuseBの引数を変えたとしても、特に問題なく値が保持され、動かせるようになっています。
useBの実装がいらなかったからと途中で消してしまった場合は、useAの値は参照が保持されたままになりますが、useCに関しては値が破棄され、もう1回新しくStateが作り直されます。
以上です。ありがとうございました。
関連タグ:
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05