Neco Coder

FBCでの活動を中心に学んだことやそうでないことを発信します。

週報2024.1.15〜21

1月第3週の週報です。

FBCでの取り組み

今週は…

  • 自作npmを開発した
  • Reactの公式ドキュメントに取り組んだ
  • リーダブルコード輪読会に参加した

自作npmを開発した

先週公開して、メンターさんにレビューをいただいていた自作npmが完成した。

完成したnpm

capital-city-quiz - 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は依存値に指定する必要がない。
          • refに格納されているオブジェクトは毎回同一であり、レンダー間で変わる可能性がないから。
            • 必ず省略できるわけではない。refコンポーネントの引数であり、親コンポーネントから渡されたものである場合、その中身についてリンタは知る由もない。つまり、親コンポーネントからどのようなrefが渡されるかに「依存」しているのである。
        • useStateが返すset関数も同じく、依存値として指定する必要はない。
        • refset関数も「必要ない」だけであって、含めても問題はない。
        • 毎回同一であれば必ず省略できるわけではない。リンタがはっきりと判断できる必要がある。

マウントとクリーンアップ

マウントとは、画面に初めて表示されるときに対象のコードを実行するよう指示すること

  • 大規模アプリにおけるコンポーネントはマウント・アンマウントが頻繁に発生する。
  • 第2引数に[]を指定するだけでは、最初のマウント時に実行した処理を正しく終えていない場合、重複して処理がなされることになる。
    • この問題に気づきやすいよう、Reactは初回マウント時だけもう1度マウントする(初回マウントはすぐにアンマウントされ、2回目のマウントが行われる)。
      • これはStrict Modeの機能であり、Strict Modeを外せばこの挙動は起きない(非推奨)。
        • 再マウントされても正しく動作するようエフェクトを修正しない限り問題の根本解決にはならない。
    • クリーンアップ関数を返すようにすると解決する。
      • クリーンアップ関数とは、useEffectのコールバック関数内にreturn 関数の形で書かれる関数であり、コンポーネントがアンマウントされる際に実行される

  • レンダーによって引き起こされるべきではないものをエフェクトにしてはいけない。
    • 逆にいえば、useEffectに渡す処理は、レンダーによって引き起こされるべきものに限るということだ。
  • フェッチをエフェクトにする場合、クリーンアップ関数としてフェッチの中止、またはその結果を無視する必要がある。
    • ここでは、結果を無視する方がより確実な方法となる。なぜならフェッチの中止をしても、その後に非同期ステップが連続する可能性があるから。

エフェクトの要否

  • 外部システムの関与
    • あり 同期したい場合はエフェクトが必要。
    • なし エフェクトは不要。
  • 不要なエフェクトを書かないことによるメリット
    • 可読性の向上
    • 実行速度の向上
    • エラーが発生しにくくなる
  • エフェクト不要となる場合2つ
    1. レンダーのためのデータ変換 コンポーネントのトップレベルで実施すべき。
    2. ユーザイベントの処理 イベントハンドラで処理すべき。
  • 既存のpropsstateから計算できるものはstateに入れない。レンダー中に計算すべき。
    • stateの章でも同じことが書いてあったはず。

不要なエフェクトの削除方法

  • 重たい計算をキャッシュ・メモ化 (memoize)するには、useMemoフックでラップする。
    • 依存関数が変更されない限り、中の関数を再実行しないようReactに指示するもの
    • ラップするのは純関数である必要

props が変更されたときにstate を変更する

  • すべてのstateをリセットする
  • 一部のstateを調整する
    • エフェクトを削除してレンダー中に直接stateの調整を行う。
      • どのように行っても、propsや他のstateに基づいてstateを調整すると、データフローが理解しにくくなり、デバッグが難しくなる。
        • つまり、このパターンはほとんどのコンポーネントにおいて必要ない。
        • keyですべてのstateをリセットできないか、レンダー中にすべてを計算できないか、常に検討すべき。

どこにロジックを保持させるか

  • エフェクトを使ってイベントハンドラ間のロジックを共有するとバグの原因になる。
    • コンポーネントがユーザに表示されたために実行されるべきコードにのみエフェクトを使用すべき
      • なぜそのコードが実行されるのかを考える
  • イベントハンドラとエフェクトのどちらにロジックを入れるべきか選択する際には、ユーザの観点からそれがどのようなロジックなのかを考える。
    • 特定のユーザ操作によって引き起こされる場合は、イベントハンドラに保持すべき。- ユーザが画面上でコンポーネントを見ることによって引き起こされる場合は、エフェクトに保持すべき。

計算の連鎖

  • 原則として、他のstateに基づいてstateの一部を調整するエフェクトを連結させてはならない。
    • 非常に効率が悪い
    • コードが発展するにつれ、書いた「チェイン」が新しい要件に適合しないケースが出てくる
  • レンダー中に計算できるものはそこで行い、イベントハンドラstateの調整を終わらせるべき
  • イベントハンドラ内で次のstateを直接計算することができない場合は、エフェクトを連鎖させることが適切となりえる

アプリケーションの初期化

アプリが読み込まれるときに一度だけ実行されるべきロジックの配置

  • トップレベルのコンポーネントのエフェクトに配置する場合、すでに実行されたかどうかを追跡するためのトップレベルの変数が必要
  • モジュールの初期化中やアプリのレンダー前に実行するよう配置することもできる。

  • 2 つの異なる state 変数を同期させたいと思ったら、代わりにstateのリフトアップを試すべき
    • 全体として考える必要のあるstateが少なくなる
  • 親と子が同じデータを必要としているのなら、親コンポーネントが取得して子に渡すべき
    • これによりデータの追跡・予測可能性を高める

エフェクトのライフサイクル

  • エフェクトのライフサイクルは、コンポーネントのライフサイクルとは異なる。
  • エフェクトのライフサイクルについて考える上では、エフェクトの開始/終了という1サイクルのみにフォーカスすべき
    • コンポーネントがマウント中なのか、更新中なのか、はたまたアンマウント中なのかに注目するのはややこしくなりやすいので❌

  • エフェクトがクリーンアップ関数を返さない場合、空のクリーンアップ関数が返されたものとして扱う
  • コード内の1つのエフェクトは、1つの独立した処理を表すべき
    • あるエフェクトを削除しても、他のエフェクトのロジックが壊れない場合、それらのロジックは分割すべきである
  • コンポーネント内のすべての値(props、state、コンポーネント本体の変数を含む)はリアクティブである
    • リアクティブな値は再レンダー時に変更される可能性があるため、エフェクトの依存配列に含める必要がある
    • リアクティブな値とは、その値が変更されたときに自動的に関連するコードやUIが更新されるような値のことを指す
      • state変数は常にリアクティブである
  • ミュータブルな値や、この値から導出される値は依存配列に含めることはできない
    • refは依存配列に含めることができるが、ref.currentは含めてはならない
      • ただし、refは依存配列から省略できる
  • リンタに引っかかる場合は次の3点を確認する
    1. エフェクトが1つの独立した同期の処理を表しているか
    2. 非リアクティブな部分を分離できないか
    3. オブジェクトや関数を依存配列に含めていないか
  • リンタの提案に従ってバグが発生したとしてもリンタを無視すべきではない
    • ルールを守りつつバグが発生しないよう修正すべき
    • リンタを抑制する記述が既になされていないかどうかもチェック

      エフェクトから依存値を取り除く

  • 依存値を削除するには、それが依存値である必要がないことをリンタに「証明」する必要がある

  • 依存配列を変更したい場合は、まず周囲のコードを変更する

    • 周囲のコードの変更に合わせて、リンタが依存値の変更を指摘してくれる
  • ある値を依存値に含めることで問題が発生する場合、エフェクト内でその値を読み取らないようにし、代わりに更新用関数を渡す

    • 更新用関数は、setA(a => a + 1)の形で、処理中のstateの値を受け取りそこから次のstateを導出する
  • 変更に反応せず値を読み出したいだけの場合、当該ロジックをエフェクトイベントに移動する

    • 安定版では未リリースの機能なので、現在のところは基本的に使えないと考えておく
  • オブジェクトや関数が依存値に含まれていて、かつ、親コンポーネントがレンダー中にそれらを作成している場合、親コンポーネントの再レンダーのたびに、エフェクトによる再接続が発生してしまう

    • エフェクトの外側でオブジェクトや関数から情報を読み取っておけば依存値から取り除けるので、この問題を回避できる
    • できればオブジェクト型や関数型が依存値となること自体を避けるべき

カスタムフックでロジックを再利用する

カスタムフックとして、アプリケーションの要求に合わせて独自のフックを作成することができる。

  • ロジックをカスタムフックに抽出することのメリット
    • コンポーネント間のロジックの重複が減る
    • コンポーネント内のコードが「何をしたいのか」の記述になり、実装方法ではなく意図を表現するようになる。
      • 外部システムやブラウザAPIとのやり取りに関する面倒な詳細を隠蔽することができる
  • フックの名前はuseで始めて大文字を続ける必要がある(LCC
  • 内部でフックを呼び出さない関数はフックである必要はない
    • フックでない関数にuseプレフィックスを使ってはならない(Reactがフックを判断する基準なので当然)
    • フックを呼び出さない関数もフックにすることはできる(非推奨)
  • カスタムフックは重複したロジックをまとめるが、カスタムフックを使用するコンポーネントごとにstateは独立している
    • カスタムフックは、state自体ではなく、state を扱うロジックを共有できるようにするためのもの
  • カスタムフックも純粋である必要がある
  • 複数のエフェクトに同一の処理がある場合でも、それらが独立した処理であるならば、統合することはできない。このような場合、カスタムフックはエフェクトごとの独立性を保ちながら重複する記述を削除できる
  • エフェクトをカスタムフックに抽出する(隠す)ことで不要な依存値の追加を防げる
  • カスタムフックは具体的かつ高レベルのユースケースに対して使うべき
    • つまり、特定の目的に特化されているが、同時に複雑な処理や高度なロジックを含んでいるような場合に使えということ
    • 良いカスタムフックとは、動作を制約することで呼び出し側のコードをより宣言的にするもの
  • 【カスタムフックにエフェクトをラップするメリット】
    • エフェクトに出入りするデータの流れが非常に明確になる。
    • コンポーネントがエフェクトの実装そのものではなく、意図にフォーカスできるようになる
    • Reactが新しい機能を追加したときに、コンポーネントを変更せずにエフェクトを削除できるようになる

リーダブルコード輪読会に参加

今週はコメントに関する内容が特に大きな学びになった。

書籍の例示に対して、「コメントする前にもっと変数化すれば読みやすくなるんじゃ?」という箇所があった。
この点について参加者の方々と話してみたところ 「変数が多くなるようならそのまま繋げて書いてコメントを付した方が読みやすくなる」という学びを得られた。

これまではコメントをつけずに読みやすいコードを書くことを意識していたが、そうするとどうしても変数が多くなってしまっていた。
しかし、変数定義が多くなれば

  • 縦に長くなってしまい、それはそれで可読性は損なわれる
  • 幾分かメモリを使う

というデメリットが生じる。
これらはいずれも輪読会参加者の方にご指摘いただいた。

縦の長さについては実際にコーディング中に考えたことがある。
しかし、その時はデメリットを認識するだけで終わってしまっていた。
上記の学びは、書籍の例示を読んだタイミングで気づかせていただけたから得られたのだと思う。

素晴らしい参加者の方々に感謝🙏✨

週間目標

学習時間

実績54.25h / 目標40h = 達成率136%

目標の達成状況

概ね順調。マラソン大会とJSメモアプリがやるやる詐欺になりつつあるので来週は必達とする。

達成

  • Reactのインプット系プラクティスを修了する
  • ベンチプレスに3日取り組む
  • 毎日10秒瞑想する
  • 1/23までに週報を公開する
  • 毎日猫を吸う

未達成

  • Reactのアウトプット系プラクティス課題を1つ提出する
  • JSメモアプリを改善する(レビュー指摘事項対応)
  • ラソン大会にエントリーする

来週の目標

  • Reactのアウトプット系プラクティス課題を2つ提出する
  • JSメモアプリを改善する(必達)
  • ラソン大会にエントリーする(必達)
  • ベンチプレスに3日取り組む
  • 晴れの日はすべてランニングする
  • 毎日10秒瞑想する
  • 毎日猫を吸う
  • 1/30までに週報を公開する

ベンチプレスは来週くらいからもう少し具体化してもいいかも…?

その他近況とか

つまらない質問

FBCには関係者用のDiscordサーバーがある。
その中には雑談部屋というものがあるんだけど、最近よくそこにメンターさんがいらっしゃる。
学習の合間に入ってみると、雑談部屋の名の通り、なんとはない雑談にも応じていただける(もちろんまじめな話にも)。
日頃からそのような距離感で接していただけるおかげで

  • 「Discordのあのスレッドって生徒が書いてもいいんですかね?」
  • Twitterにどんなこと書けばいいかわからんすわHAHAHA!!」

みたいなつまらない内容でも気軽に相談できて非常に助かっている。
FBCにはいろんな形で質問・相談できる制度があるが、「ちょっと気になるけど質問するほどのことでもないな」と流してしまうこともちょこちょこある。
でも、そういった問題が解決すると日々のストレスが予想以上に減ったりして、ひょっとすると集中力にも影響する。

そんなわけで、雑談部屋にメンターさんがいてくださるというのは、本当にありがたいことだなと感じている。

子どもがいるから

週初めの出だしが遅れがちである。月曜朝にだらだらしがち。
子どもがいると(そして組織で仕事をしていないと)どうしてもリズム維持が難しい部分があるけど、だからこそ学習時間を確保する工夫の考案と実践を怠ってはならない。

最近、ちょうどFBCで朝活に関する話題が上っていた。
メンターさん含め、子どもがいる方は寝かしつけ時に一緒に寝ることで朝型生活を身につけた方が多いらしい。
子どもがいるからこそ整えられるリズムもあるのだ。楽しんでいこう。

最後に

なんだかんだと書きましたが、猫が脚の間に入ってくるので最高の一週間でした。

完璧な防寒対策を講じるお猫様

来週も最高の一週間になるでしょう。

それではまた!