1月第3週の週報です。
FBCでの取り組み
今週は…
- 自作npmを開発した
- Reactの公式ドキュメントに取り組んだ
- リーダブルコード輪読会に参加した
自作npmを開発した
先週公開して、メンターさんにレビューをいただいていた自作npmが完成した。
完成したnpm
首都名クイズのCLIアプリをつくった。
【工夫した点】
- 大陸ごとに解答できるようにした
問題のソースとして用いたnpmパッケージが大陸ごとに国を整理していたので、その点も活かして解答できるようにした。 - 英語・日本語の2か国語対応
npmパッケージは世界に公開するため基本は英語としてつくった。
しかし、実際に私のnpmパッケージに辿り着く可能性が高いのは(つまり想定利用者が)日本人だと思ったので、日本語表示も可能とした。 - View機能をつけた
予習・復習を可能とするために、国ごとの首都名を一覧できるView機能をつけた。
こちらも大陸ごとに表示可能であり、日本語対応している。
【苦労した点】
- npmパッケージサイトへの公開自体は先週行った。
しかし公開時点では、READMEに示した方法でプログラムを実行できないという重大なバグがあった。
レビューの際にメンターさんからご指摘をいただいて発覚した。このバグはファイルの読み込みに相対パスを使用していたことによる。
原因がパスなので、自身の開発用PCではエラーが出なかったことで見逃したようだ。手持ちの別PCでも確認していればこのようなバグは防げたはず。
外部に公開するプログラムは必ず開発用PC以外(できる限りユーザに近い環境)で確認するようにしようと思った。
もちろん現在は修正済みである。 - i18n対応を最後にしたところ、アプリケーション全体に影響が出たので大変だった。
とはいえベースとなるロジックが固まっていたからこそ対応できた部分もあると感じるので、開発当初から並行して進めるべきだったのかはわからない。
翻訳作業はどうしたって作業量も多くなるし、取り組む際には覚悟が必要だと感じた。
【終えてみて】
もっと単純なアプリ(例えば首都名一覧が見れるだけとか)にすればプラクティス修了は早かったろうとも思う。
「できるだけ早く卒業する」という目的からすればそちらの選択が理には適っている。
だけど、そうしなかったおかげで今の自分的にはそこそこ満足できるものができた。
ものづくりってやっぱり楽しいと感じた。この感覚を得られたことも大きな収穫だ。
Reactの公式ドキュメントに取り組んだ
先週に引き続きReactの公式ドキュメントを読んだ。
先週は飽きがきたと書いたが、その後は(特にStateの理解が進んでからは)結構楽しんで進めることができた。
印象に残っているのは、エフェクトについて学んだとき。
「あ゛あ゛~~難しい〜~〜!!」と頭を抱えながらなんとかチャレンジ問題を解ききり次のページに進んだところ、見出しにドカンと「エフェクトは必要ないかもしれない」と書いてあった。
なんとなく、穴を掘って埋めるだけの刑罰を思い出した。
Reactのエフェクトとカスタムロジックに関する学習メモ
このメモはReact公式ドキュメントから読み取った内容ではありますが、本記事は技術記事ではありません。
故にこのメモには未検証の部分を含んでいます!
ご覧になる際はこの点ご留意願います。
エフェクト
useEffect
はレンダー結果が画面に反映し終わまで、コードの実行を遅らせる。- 第1引数で渡した関数をレンダー後に実行する
- 第2引数には依存関数を渡す
- ここでいう依存というのは、コールバック関数の判断の軸になっている要素のこと
- 第2引数がない場合、毎回のレンダー後に実行される
- 第2引数が空配列[]の場合、マウント時(コンポーネント出現時)のみ実行される
- 第2引数が[a, b]の場合、マウント時と、a か b の値が前回のレンダーより変わった場合に実行される
- つまり、依存値として指定されるのは、レンダー間で変わる可能性があるもののみ。
- 原則として
ref
は依存値に指定する必要がない。 useState
が返すset
関数も同じく、依存値として指定する必要はない。ref
もset
関数も「必要ない」だけであって、含めても問題はない。- 毎回同一であれば必ず省略できるわけではない。リンタがはっきりと判断できる必要がある。
マウントとクリーンアップ
マウントとは、画面に初めて表示されるときに対象のコードを実行するよう指示すること
- 大規模アプリにおけるコンポーネントはマウント・アンマウントが頻繁に発生する。
- 第2引数に[]を指定するだけでは、最初のマウント時に実行した処理を正しく終えていない場合、重複して処理がなされることになる。
- この問題に気づきやすいよう、Reactは初回マウント時だけもう1度マウントする(初回マウントはすぐにアンマウントされ、2回目のマウントが行われる)。
- これはStrict Modeの機能であり、Strict Modeを外せばこの挙動は起きない(非推奨)。
- 再マウントされても正しく動作するようエフェクトを修正しない限り問題の根本解決にはならない。
- これはStrict Modeの機能であり、Strict Modeを外せばこの挙動は起きない(非推奨)。
- クリーンアップ関数を返すようにすると解決する。
- クリーンアップ関数とは、useEffectのコールバック関数内に
return 関数
の形で書かれる関数であり、コンポーネントがアンマウントされる際に実行される
- クリーンアップ関数とは、useEffectのコールバック関数内に
- この問題に気づきやすいよう、Reactは初回マウント時だけもう1度マウントする(初回マウントはすぐにアンマウントされ、2回目のマウントが行われる)。
- レンダーによって引き起こされるべきではないものをエフェクトにしてはいけない。
- 逆にいえば、
useEffect
に渡す処理は、レンダーによって引き起こされるべきものに限るということだ。
- 逆にいえば、
- フェッチをエフェクトにする場合、クリーンアップ関数としてフェッチの中止、またはその結果を無視する必要がある。
- ここでは、結果を無視する方がより確実な方法となる。なぜならフェッチの中止をしても、その後に非同期ステップが連続する可能性があるから。
エフェクトの要否
- 外部システムの関与
- あり 同期したい場合はエフェクトが必要。
- なし エフェクトは不要。
- 不要なエフェクトを書かないことによるメリット
- 可読性の向上
- 実行速度の向上
- エラーが発生しにくくなる
- エフェクト不要となる場合2つ
- 既存の
props
やstate
から計算できるものはstate
に入れない。レンダー中に計算すべき。state
の章でも同じことが書いてあったはず。
不要なエフェクトの削除方法
- 重たい計算をキャッシュ・メモ化 (memoize)するには、
useMemo
フックでラップする。- 依存関数が変更されない限り、中の関数を再実行しないようReactに指示するもの
- ラップするのは純関数である必要
props
が変更されたときにstate
を変更する
- すべての
state
をリセットする - 一部の
state
を調整する
どこにロジックを保持させるか
- エフェクトを使ってイベントハンドラ間のロジックを共有するとバグの原因になる。
- コンポーネントがユーザに表示されたために実行されるべきコードにのみエフェクトを使用すべき
- なぜそのコードが実行されるのかを考える
- コンポーネントがユーザに表示されたために実行されるべきコードにのみエフェクトを使用すべき
- イベントハンドラとエフェクトのどちらにロジックを入れるべきか選択する際には、ユーザの観点からそれがどのようなロジックなのかを考える。
計算の連鎖
- 原則として、他の
state
に基づいてstate
の一部を調整するエフェクトを連結させてはならない。- 非常に効率が悪い
- コードが発展するにつれ、書いた「チェイン」が新しい要件に適合しないケースが出てくる
- レンダー中に計算できるものはそこで行い、イベントハンドラで
state
の調整を終わらせるべき - イベントハンドラ内で次の
state
を直接計算することができない場合は、エフェクトを連鎖させることが適切となりえる
アプリケーションの初期化
アプリが読み込まれるときに一度だけ実行されるべきロジックの配置
- 2 つの異なる state 変数を同期させたいと思ったら、代わりに
state
のリフトアップを試すべき- 全体として考える必要のある
state
が少なくなる
- 全体として考える必要のある
- 親と子が同じデータを必要としているのなら、親コンポーネントが取得して子に渡すべき
- これによりデータの追跡・予測可能性を高める
エフェクトのライフサイクル
- エフェクトのライフサイクルは、コンポーネントのライフサイクルとは異なる。
- エフェクトのライフサイクルについて考える上では、エフェクトの開始/終了という1サイクルのみにフォーカスすべき
- コンポーネントがマウント中なのか、更新中なのか、はたまたアンマウント中なのかに注目するのはややこしくなりやすいので❌
- エフェクトがクリーンアップ関数を返さない場合、空のクリーンアップ関数が返されたものとして扱う
- コード内の1つのエフェクトは、1つの独立した処理を表すべき
- あるエフェクトを削除しても、他のエフェクトのロジックが壊れない場合、それらのロジックは分割すべきである
- コンポーネント内のすべての値(props、state、コンポーネント本体の変数を含む)はリアクティブである
- リアクティブな値は再レンダー時に変更される可能性があるため、エフェクトの依存配列に含める必要がある
- リアクティブな値とは、その値が変更されたときに自動的に関連するコードやUIが更新されるような値のことを指す
- state変数は常にリアクティブである
- ミュータブルな値や、この値から導出される値は依存配列に含めることはできない
ref
は依存配列に含めることができるが、ref.current
は含めてはならない- ただし、
ref
は依存配列から省略できる
- ただし、
- リンタに引っかかる場合は次の3点を確認する
- エフェクトが1つの独立した同期の処理を表しているか
- 非リアクティブな部分を分離できないか
- オブジェクトや関数を依存配列に含めていないか
- リンタの提案に従ってバグが発生したとしてもリンタを無視すべきではない
- ルールを守りつつバグが発生しないよう修正すべき
- リンタを抑制する記述が既になされていないかどうかもチェック
エフェクトから依存値を取り除く
依存値を削除するには、それが依存値である必要がないことをリンタに「証明」する必要がある
- 例:対象の値をコンポーネント外へ移動する
依存配列を変更したい場合は、まず周囲のコードを変更する
- 周囲のコードの変更に合わせて、リンタが依存値の変更を指摘してくれる
ある値を依存値に含めることで問題が発生する場合、エフェクト内でその値を読み取らないようにし、代わりに更新用関数を渡す
- 更新用関数は、
setA(a => a + 1)
の形で、処理中のstate
の値を受け取りそこから次のstate
を導出する
- 更新用関数は、
変更に反応せず値を読み出したいだけの場合、当該ロジックをエフェクトイベントに移動する
- 安定版では未リリースの機能なので、現在のところは基本的に使えないと考えておく
オブジェクトや関数が依存値に含まれていて、かつ、親コンポーネントがレンダー中にそれらを作成している場合、親コンポーネントの再レンダーのたびに、エフェクトによる再接続が発生してしまう
- エフェクトの外側でオブジェクトや関数から情報を読み取っておけば依存値から取り除けるので、この問題を回避できる
- できればオブジェクト型や関数型が依存値となること自体を避けるべき
カスタムフックでロジックを再利用する
カスタムフックとして、アプリケーションの要求に合わせて独自のフックを作成することができる。
- ロジックをカスタムフックに抽出することのメリット
- フックの名前は
use
で始めて大文字を続ける必要がある(LCC) - 内部でフックを呼び出さない関数はフックである必要はない
- フックでない関数に
use
プレフィックスを使ってはならない(Reactがフックを判断する基準なので当然) - フックを呼び出さない関数もフックにすることはできる(非推奨)
- フックでない関数に
- カスタムフックは重複したロジックをまとめるが、カスタムフックを使用するコンポーネントごとに
state
は独立している- カスタムフックは、
state
自体ではなく、state
を扱うロジックを共有できるようにするためのもの
- カスタムフックは、
- カスタムフックも純粋である必要がある
- 複数のエフェクトに同一の処理がある場合でも、それらが独立した処理であるならば、統合することはできない。このような場合、カスタムフックはエフェクトごとの独立性を保ちながら重複する記述を削除できる
- エフェクトをカスタムフックに抽出する(隠す)ことで不要な依存値の追加を防げる
- カスタムフックは具体的かつ高レベルのユースケースに対して使うべき
- つまり、特定の目的に特化されているが、同時に複雑な処理や高度なロジックを含んでいるような場合に使えということ
- 良いカスタムフックとは、動作を制約することで呼び出し側のコードをより宣言的にするもの
- 【カスタムフックにエフェクトをラップするメリット】
リーダブルコード輪読会に参加
今週はコメントに関する内容が特に大きな学びになった。
書籍の例示に対して、「コメントする前にもっと変数化すれば読みやすくなるんじゃ?」という箇所があった。
この点について参加者の方々と話してみたところ 「変数が多くなるようならそのまま繋げて書いてコメントを付した方が読みやすくなる」という学びを得られた。
これまではコメントをつけずに読みやすいコードを書くことを意識していたが、そうするとどうしても変数が多くなってしまっていた。
しかし、変数定義が多くなれば
- 縦に長くなってしまい、それはそれで可読性は損なわれる
- 幾分かメモリを使う
というデメリットが生じる。
これらはいずれも輪読会参加者の方にご指摘いただいた。
縦の長さについては実際にコーディング中に考えたことがある。
しかし、その時はデメリットを認識するだけで終わってしまっていた。
上記の学びは、書籍の例示を読んだタイミングで気づかせていただけたから得られたのだと思う。
素晴らしい参加者の方々に感謝🙏✨
週間目標
学習時間
実績54.25h / 目標40h = 達成率136%
目標の達成状況
概ね順調。マラソン大会とJSメモアプリがやるやる詐欺になりつつあるので来週は必達とする。
達成
- Reactのインプット系プラクティスを修了する
- ベンチプレスに3日取り組む
- 毎日10秒瞑想する
- 1/23までに週報を公開する
- 毎日猫を吸う
未達成
来週の目標
- Reactのアウトプット系プラクティス課題を2つ提出する
- JSメモアプリを改善する(必達)
- マラソン大会にエントリーする(必達)
- ベンチプレスに3日取り組む
- 晴れの日はすべてランニングする
- 毎日10秒瞑想する
- 毎日猫を吸う
- 1/30までに週報を公開する
ベンチプレスは来週くらいからもう少し具体化してもいいかも…?
その他近況とか
つまらない質問
FBCには関係者用のDiscordサーバーがある。
その中には雑談部屋というものがあるんだけど、最近よくそこにメンターさんがいらっしゃる。
学習の合間に入ってみると、雑談部屋の名の通り、なんとはない雑談にも応じていただける(もちろんまじめな話にも)。
日頃からそのような距離感で接していただけるおかげで
- 「Discordのあのスレッドって生徒が書いてもいいんですかね?」
- 「Twitterにどんなこと書けばいいかわからんすわHAHAHA!!」
みたいなつまらない内容でも気軽に相談できて非常に助かっている。
FBCにはいろんな形で質問・相談できる制度があるが、「ちょっと気になるけど質問するほどのことでもないな」と流してしまうこともちょこちょこある。
でも、そういった問題が解決すると日々のストレスが予想以上に減ったりして、ひょっとすると集中力にも影響する。
そんなわけで、雑談部屋にメンターさんがいてくださるというのは、本当にありがたいことだなと感じている。
子どもがいるから
週初めの出だしが遅れがちである。月曜朝にだらだらしがち。
子どもがいると(そして組織で仕事をしていないと)どうしてもリズム維持が難しい部分があるけど、だからこそ学習時間を確保する工夫の考案と実践を怠ってはならない。
最近、ちょうどFBCで朝活に関する話題が上っていた。
メンターさん含め、子どもがいる方は寝かしつけ時に一緒に寝ることで朝型生活を身につけた方が多いらしい。
子どもがいるからこそ整えられるリズムもあるのだ。楽しんでいこう。
最後に
なんだかんだと書きましたが、猫が脚の間に入ってくるので最高の一週間でした。
来週も最高の一週間になるでしょう。
それではまた!