🔥

フロントエンド開発で気をつけていること忘備録

2022/09/05に公開

はじめに

本業がSIerの身分からフロントエンドに足を踏みいれてしばらくが立ちました。自分の忘備録も兼ねて、私がフロントエンド開発で気をつけていることを技術要素ごとにまとめてみました。

HTML

  • 用途に応じた適切なタグを利用する。SEO対策やアクセシビリティ向上の効果があり、開発者にとってもHTMLの構造を把握しやすくなる。
  • aタグやimgタグのalt属性やrole属性、label属性などを適切に設定することでアクセシビリティの向上を図る。

http://www.htmq.com/html5/

CSS

  • 余計なプロパティは書かないこと。画面の表示に影響がなくとも、後から追加した要素に予期せぬ悪影響を与えるリスクある。また、後でcssを見返したとき理解に苦労する。(普通はプロパティ1つ1つにコメントは入れないと思うのでなおさら)
  • 複数のプロパティを記述する順番を統一する。 (Vscodeの拡張機能でできる)
  • 余白の取り方について、兄弟要素間にはmarginを、親子要素間にはpaddingを原則として使用する。
  • cssフレームワークを利用する場合、最終的にどのようなcssに変換されているのか理解する。プロパティの衝突で期待通りの表示にならない時、頼りになるのはブラウザのデベロッパーツールとcssの知識になる

現在、Reactと共に使用するCSSの選択肢が多く選定が難しい状況にある。個人的にはTailwindcssとCSS Moduleの組み合わせが好み。

  • Tailwindcssはクラス名とcssがほぼ1対1で紐づくので、設定されているcssのプロパティを把握しやすい。いつでも素のcssにスイッチできる。
  • CSS Moduleのメリットとして以下が挙げられる。ただし、将来deprecatedになる可能性が示唆されていることが心配。
    • ファイル単位がcssのスコープとなるため管理がしやすく、クラス名も長くなりにくい。
    • コンポーネントの実装とスタイル定義をファイルで分離できるため可読性や保守性の観点からも良い。
    • パフォーマンスの面ではCSS in JSより良いとされている。

参考

https://zenn.dev/irico/articles/d0b2d8160d8e63

Javascript

Javascriptの歴史は一度読んでおくと良い。React、Webpack、Babel、Redux,Typescriptなど、現在のフロントエンド開発の前線で活躍している技術の理解を深めることができる。その技術がどのような問題を解決しようとしてるのかを理解することは大切。

ReactやNext.jsなどのフレームワークやTypescriptを利用する場合でも、最終的にはJavascriptに変換されて動作するため、Javascriptの理解は必須と考える。

  • 変数は可能な限りconstを使用し、変数のスコープを縮める。
  • 処理に使用する一時的な変数は使い回さない。
  • 命名は汎用的な名前を避けて具体的にする。多少長くなっても良いので名前に情報を詰め込む。変数名や関数名を読むだけで処理内容が伝わるのがベスト。
  • コメントには、コードからは読み取れない理由や背景を説明する。情報の密度を高く簡潔に述べる。
  • 1行で2つ以上の仕事をしない。1行づつ段階を踏んで処理する。
  • コンポーネントや関数の挙動を変える引数以外は引数にしない。テスト時の条件分岐が増えて手間が増える。
  • if、switch、forのネストは浅くする、多くても2ネスト程度まで。やむを得ない場合は該当部分を別出しする。
  • elseは早期returnに代替できないか検討する。
  • 非同期処理は.then()とasnyc/awaitの両方のやり方を理解しておく。使用するライブラリによってどちらの対応も迫られることになる。
  • テストコードを想定しながらコードを書くと自然と良いコードになる。その機能の仕様が洗い出され、必要最小限のコードとinterfaceを持った機能になる。(これはテスト駆動開発と呼ばれる)
  • httpのリクエストとレスポンスのオプションやデータ構造は把握しておく。

参考

https://www.oreilly.co.jp/books/9784873115658/
https://zenn.dev/antez/books/568dd4d86562a1/viewer/21602b

パッケージマネージャ

MacOSでフロントエンド開発を行う環境におけるパッケージマネージャの依存関係は以下の通り。

  1. Homebrew
    MacOSとしてのパッケージマネージャ
  2. Nodebrew
    Homebrewでインストールする。Nodejsのパッケージマネージャ、nodejsのバージョンを管理・切り替えを行う。
    利用するライブラリでサポートされているnodeJSのバージョンに注意する。現在の最新バージョンは18系だが、例えばヘッドレスCMSのstrapiは14系と16系が公式でサポートされている。
  3. npm
    Nodejsで使用するライブラリ等のパッケージマネージャ。nodejsはサーバーサイドで動作する言語だが、フロントエンドのJavascriptのパッケージマネージャとしても使用できる。
    --save--save-devを適切に使い分ける。
  4. yarn
    npmでインストールする。npmと同じ役割を持つパッケージマネージャ。npmよりもパフォーマンスやバージョン管理方法が優れていると言われているが、基本的にはどちらを利用しても問題ない。

Typescript

  • "strict": true としてチェックを厳格にする
  • any型は原則使用しない。
  • コードが設計書になるように記述する。型の意味や役割までコメントで記述すると良し。
  • 解決に時間のかかっているエラーがあり、その原因がTypescriptによるものとわかっている場合は、any型の使用や部分的にエラーを無効化することも良しとする。Typescriptによって品質と開発効率は高められるが、それに固執していても動くものは出来上がらない。目的と手段を履き違えない。
  • tsconfig.jsonのbaseUrl: '.'と設定しておくと、imoprtのパスがルートディレクトリからの相対パスとなり理解しやすくなるため、おすすめの設定。import hoge from ../../../lib/expamleとか階層を溯らずに済む。
  • 多くのライブラリにはTypescript用の型定義ファイルが用意されており、npm install --save-dev @types/ライブラリ名でインストールできる。

参考

https://typescriptbook.jp/reference/values-types-variables/any

セキュリティ

WEBアプリケーション開発で考慮すべきセキュリティのキーワードとしては以下ページが参考になる。
セキュリティに強いエンジニアになりたい。

独立行政法人 情報処理推進機構 安全なWebサイトの作り方
https://www.ipa.go.jp/security/vuln/websecurity.html

OWASP(Open Web Application Security Project)
https://owasp.org/Top10/ja/

ブラウザ側に秘密情報を持たせない

認証情報やAPIの秘密鍵などの秘密情報はブラウザ側に持たせてはならない(Javascriptの変数にハードコーディングしたり環境変数で埋め込むなど)。原則はサーバーサイドに持たせて隠蔽する。

クロスサイトスクリプティング(XSS)

  • ユーザー入力値のサニタイジングを行う
  • 入力文字種類や文字数の制限を設ける
  • urlの先頭をチェックしてhttpまたはhttpsの場合のみリンクを表示する。

CSRF(Cross-site Request Forgery)

  • 改ざん検証が可能なJWT(Json Web Token)を利用した認証を検討する。

その他セキュリティ

フロントエンド開発と直接関係ないが気をつけていること

  • フリーWifi環境でログインが必要なサイトには絶対に接続しない。DNSの仕組みを理解していれば、不正なサイトに誘導される危険性と、ユーザー側の発見の困難さがあいまって、非常に危険な行為だとわかる。SSIDだけでは安全かどうか判断できない。
  • フィッシングメールに気をつける。最近のGmailは優秀で警告してくれる場合もあるが、リンクをクリックする際は差出人のメールアドレスと URLが正規のものかよく確認する。フィッシングメールが原因で他要素認証まで破られた事例もある。(auじぶん銀行の事件とか)
  • OSやアプリのアップデート系は放置しない。(機能変更が中心のメジャーアップデート時は様子見で良い)
  • パスワードは絶対に使い回さない(パスワードリスト攻撃の被害増加)。パスワード管理ツールに頼っていいしそれは必要経費と考える。

Git(バージョン管理)

Gitによるバージョン管理の方法(ブランチモデル)として、git-flowGitHub FlowGitLab Flowなどが挙げられる。これらのモデルの考え方ベースとして、使いやすいルールを決めれば良いと思う。
gitコマンドを打つときに不安があれば必ず自分の環境で検証する。

参考文献

https://havelog.aho.mu/develop/git/e513-git_branch_model.html
https://www.ninton.co.jp/archives/3304
https://dev.classmethod.jp/articles/introduce-git-flow/

React

パフォーマンス最適化

  • ブラウザーの開発ツールのパフォーマンスタブやネットワークタブでボトルネックの発生場所を見つける。
  • コンポーネントにconsole.logを入れレンダリングの回数が必要最小限であるか確認する。
  • 主な原因の例として以下が挙げられる。根本原因を特定し個別に対応する。
    • 不要なレンダリングが発生している
    • APIのレスポンス時間の待ち時間が長い
    • ダウンロードするデータサイズが大きい
  • 調べた結果、バックエンドやネットワーク側の問題だったということもある。

ライフサイクル

クラスコンポーネントのライフサイクルは大きく分けて3つの期間がある。

  • Mounting(マウント時)
    コンポーネントが表示されるまでの期間のこと。
  • Updating(更新時)
    Componentが表示され、Stateの更新を行う期間のこと。
  • Unmounting(マウント解除時)
    コンポーネントが破棄されるときの期間。

現在ではクラスコンポーネントより関数コンポーネントが主流となっているが、関数コンポーネントにおいてもuserEffectのHookにより、クラスコンポーネントのライフサイクルと同じことが実現できる。

  • Mount時:useEffectのコールバック関数を実行する
  • Update時:userEffectの第2引数の依存変数が変化するたびにコールバック関数を実行する
  • Unmount時:userEffectのreturnのコールバック関数を実行する

参考

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
https://qiita.com/102Design/items/f829911cd76734cbc882

コンポーネントが再描写される条件

  • propsが更新されたとき
  • stateが更新されたとき
  • 親のコンポーネントが再レンダリングされた時

コンポーネントの設計

  • propsや状態管理の項目数を増やしすぎない。
  • 大規模になるほどテストの条件分岐の組み合わせが掛け算で増加し、管理が大変になる。
  • コンポーネントの分割方法については以下の考え方も有効だが、実際は100%その通りにコンポーネントを分割することは難しい。(propsのバケツリレーが多くなり記述量が増加する)。柔軟にルールを考え、プロジェクト内でそのルールが一貫していれば良い。
    • Atomic Design
    • Presentation Component / Conteiner Component

Hooks

useState

  • setState関数を実行後、即座にはstateに反映されない。再レンダリング後に更新後のstateを利用できる。
  • オブジェクトや配列などの参照型変数は、その中身を更新するだけでは再レンダリングが発生しない。参照を更新する必要がある。
  • 現在の状態から更新後の状態を生成する場合は、setState関数のコールバック関数の引数(prevState)を使う。

useEffect

  • 第2引数の依存変数を適切に設定し、不要なレンダリングまたは無限リンダリングが発生しないようにする。
  • コンポーネントがunmountされても残り続けるイベントやサーバーへのリスナーなどは、クリーンアップ関数で削除すること。
  • 本当にuseEffectが必要な処理であるか検討する。通常の関数で代替できるのであればそうする。

useContext

  • Contextを更新する際にProvider配下のコンポーネントに不要なレンダリングを発生させてしまう。回避テクニックもあるがコードの記述量が多くなる。
  • 最初から状態管理ライブラリを導入して良いかもしれない。ReduxToolkitなら導入のハードルはそう高くない。

useMemo

  • メモ化の仕組みを理解しておくこと。
    Reactはコンポーネントのレンダリング時に、コンポーネント内の処理を全て実行してreturn文の中で要素を構築して返す。useMemoは関数の結果を保持するためのフックで、何度実行しても結果が同じ場合の値を保存(メモ化)再利用する。
    初回レンダリング時はuseMemoコールバック関数が実行されその結果をメモ化する。次回レンダリング以降は第2引数の依存変数が更新されない限りはコールバック関数を実行せず、メモ化された値を再利用する。

useCallback

  • react.memo()で最適化対象のコンポーネントをラップすることを忘れずに
  • 最初からミリミリにレンダリングの最適化はしなくて良いと考えている。レスポンスの悪化が気になってきたら原因を特定してuseMemoやuseCallbackでパフォーマンスを最適化する。

useRef

DOMの提供するAPIを直接使いたいときに使用する。

参考文献
https://zenn.dev/abeshi/articles/bb9c3b1f24719c
https://qiita.com/seira/items/42576765aecc9fa6b2f8

状態管理

Redux

ReduxはFluxアーキテクチャを実装した状態管理のライブラリである。
Reduxによる状態(state)の更新の流れは次のようになる。

  1. 事前にコンポーネントが利用したいstoreのstateを定義しておく。(useSelector関数)
  2. コンポーネントがActionをReducerに渡す。これをdispatchという。例えばonclickのイベントハンドラでdispatch(action名)として実行する。
  3. Reducerは渡されたActionと現在のstateに応じてStore内のStateを更新する。
  4. コンポーネントはstateの変更を検知したら再レンダリングされる。


出典: https://redux.js.org/tutorials/essentials/part-5-async-logic#thunks-and-async-logic

ReduxToolKitの利用も公式で推奨されている。管理したいstateの単位でSliceファイルを作成し,Reducer、Action、Stateを1ファイル内に記述する。これにより記述量の削減と見通しが良くなり、Redux導入のハードルがかなり下がった。

Chromeの拡張機能のRedux DevToolsを用いれば、状態遷移の流れが視覚化されデバックの助けになる。

その他

最近ではFacebook社のReciolや、Vercel社のSWRなど、状態管理をよりシンプルに実現するライブラリが登場している。そちらもキャッチアップしていきたい。

参考文献

https://qiita.com/kondo97/items/b1d7031f89334912fe97

https://qiita.com/knhr__/items/5fec7571dab80e2dcd92

https://techblog.roxx.co.jp/entry/2019/06/12/134154

https://redux.js.org/tutorials/essentials/part-5-async-logic#thunks-and-async-logic

https://ugo.tokyo/try-redux/

レンダリング方法

CSR(Client Side Rendering)

クライアントのリクエストに対して空のHTMLとJavaScriptを返し、クライアント側でJavaScriptでデータの取得や要素の生成を行いレンダリングして表示する。

  • クライアント側で全ての要素を構築するため初回表示が遅くなる
  • サーバー上には空のHTMLを置くため、SEOやOGPなどに対応できず広告面で不利になる。

SSR(Server Side Rendering)

サーバー側のJavascript実行環境でリクエストに応じてページを生成しHTMLを返す方法。CSRよりUXの向上が期待できる。

  • サーバー側にNode.js等の実行環境が必要となる。Vercel社以外にデプロイする際は環境構築に一手間かかる。
  • クラアント側とサーバー側で処理が分散してしまう、

SSG(Static Site Generation)

ビルド時に必要なデータを全て取得し、全てのページのレンダリングを行う。ビルド後はWebpackでバンドルされた静的なファイルをサーバーから配信するだけとなり、UXの大きな向上見込める。

  • ビルド後に動的に内容を変更することはできないため、更新頻度が高いWEBサイトには不向き。
  • WebHookを利用して ある程度はビルドの自動化ができる。
  • 1ページ内でSSGとSSGを混在させることはできない。

参考

https://qiita.com/hiroki-yama-1118/items/b3388c5dcb155e2e367d
https://www.webstaff.jp/guide/trend/nextjs/

Next.js

メリット

  • ファイルベースルーティング、ページ単位でファイルを作成しSPAのルーティングが表現できる。
  • 画像の最適化。WebP形式への自動変換し埋め込んでくれる。画像コンテンツの最適化はレスポンス向上の効果が高い。
  • CSR/SSG/SSRをページ毎に使い分けることができ、UXの最適化がしやすい
  • Webpack、Babel、React、Typescript、開発用サーバ等の環境構築が済んだ状態から開発をスタートできる。

注意点

  • ブラウザ側とサーバー側の処理を1つのファイル内で記述するため、コードを書く際はどちら側で処理させるものか意識する。特にwindow、cookie、localstrage等の機能はブラウザ側のみ利用できる機能。
  • サーバ側とクラアント側の環境変数が1つのファイルで管理されるため、秘密情報を誤ってクライアント側に埋め込まないように注意する。
  • 確かにNext.jsは便利だが依存を強めすぎると他のフレームワークの選択肢を失う。他の技術のキャッチアップを欠かさないこと。

WEB API

バックエンド側の話も混ざるが、API設計で気をつけること。

  • ユーザー側の視点で、APIを使って実現したい目的を明確にする。機械に人間が合わせるのではなく、機械が人間に寄り添うことが好ましい。
  • なるべく短い文字数にする。ただし人間がひと目で理解できない省略は控える。例えば、 /user/123/u/123とかusr/123に省略しない。(linuxを触っている人はusrは理解できるが、そうでない人も多い)
  • 原則大文字は使用せず小文字のみ。ハイフンはOK、アンダースコアはNG。URLの最後にスラッシュを付けない
  • サーバー側のアーキテクチャの都合を入れない。APIの利用者にとっては不要な情報であり、そのアーキテクチャ固有の脆弱性を突いた攻撃を受けるリスクを高めるため。
  • 適切なHTTPメソッドを利用する。GET、POST、PUT、DELETE。
  • URLのリソース名は単数形と複数形を適切に使い分ける。users/123/settings/profile
  • リソース名は動詞ではなく名詞で表現する。リソースの操作はHTTPメソッドで表現する。
  • APIの呼び出し回数は必要最小限にする。ただし、1つのレスポンスのデータサイズが大きくなりすぎないようにも注意する。
  • リソースの指定でクエリパラメータかパスパラメータのどちらにするか迷う場合、パラメータを省略しても成り立つ場合はクエリパラメータで、そうでない場合はパスパラメータにする。
  • データ形式はJavascriptと相性がよくデータ量も小さいJSONを基本にする。JSONのネストの深さは必要最小限にする。
  • 適切なレスポンスのステータスコードを利用する。。クライアント側ではレスポンスコードに応じた処理を実装する。
  • サーバーから受け取ったデータはそのまま格納するのではなく、フロントエンド側の処理に必要最小限の項目に絞り格納する。そうすることで、サーバー側の仕様変更があったとしても影響範囲が最小限に抑えられる。

参考

https://qiita.com/Amtkxa/items/2c5df130e44e8e8d4a6b
https://zenn.dev/nakaryo/articles/87b15866d67efe

人としての振る舞い方

エンジニアとして仕事をする上で気をつけていること。

  • 報連相は欠かさずに、タイムリーにしよう。問題の無いプロジェクトなど存在しない。
  • 事実と意見は分けて伝えよう。
  • 天才でなくて良い、少し努力できる凡人でいい。
  • よく話しかけて、よく話しかけられやすい人になろう。
  • 相手のメリットを考えよう。
  • win-winの関係を最後まで模索しよう。win-lose、lose-winは簡単にできる。
  • 目の前の仕事だけでなく全体を俯瞰しよう。先を見据えた準備と仕込みが大切。
  • 質問や相談する時は相手の時間を大切にしよう。感謝の気持ちを忘れれずに。
  • 良い戦略はいつもシンプル。

最後に

間違いやアドバイスなどがあればコメントしていただけけると幸いです。
今後もこの記事はメンテしていきたいなと思っています。

Discussion