- Reactの基礎
- イベントリスナーと状態管理(state)
- 制御構文とフォームの制御
- Reactでのスタイル適用方法
- ReactでDOM操作を行う方法
- Immutability(不変性)
- 関数型プログラミング
- useContextでstate管理
- useEffectの実行タイミング
- カスタムフック
- useEffectと副作用
- Redux
- クラスコンポーネント
- レンダリングの最適化
- パフォーマンスの最適化
- Rest APIを使ったサーバーとの通信
- Next.js
- Next.jsにおけるレンダリング
- テスト
- TypeScript
- JSX
- JSXがオブジェクトに変換される過程
- React要素のレンダー
- コンポーネントの定義
- 関数コンポーネント
- コンポーネントの親子関係
- コンポーネント間のデータのやりとり
- React要素とコンポーネント
- コンポーネントツリー
React による JavaScript の構文を拡張したもの。
JSX は JavaScript のオブジェクトに変換される。
ReactDOM が仮想 DOM を元に DOM を更新する。
コンポーネントは JavaScript の関数として定義する。
関数で定義されるコンポーネントは関数コンポーネントと呼ばれる。
関数コンポーネントはprops を受け取り、JSX を返す。
↓
・再利用性の向上(コードが使いまわせる)
・可読性の向上(コードが整理される)
・疎結合になる(バグを減らせる)
コンポーネントは出力する JSX の中に他のコンポーネントを含めることができる。
コンポーネントは props を親から子に渡すことでデータを受け渡す。
props を通して JavaScript のあらゆる値を渡すことができる。
props の流れは一方通行
props を子から親に渡すことはできない。
props は読み取り専用
コンポーネント内で props の値を変更してはいけない。
- 画面が変更されるために必要な処理
- useStateの役割と使い方
- useStateを使う際の注意点
- state更新用関数とレンダリング
- stateはコンポーネント毎に状態(値)を保持
- React要素のツリー構造が変わらない場合
- 同じ位置に独立してコンポーネントを表示
・React にコンポーネントの再実行(再レンダリング)を依頼し、新しい React 要素を作成してもらう必要がある。
・変更した値をどこかに保持しておく必要がある。(stateに保存)
↓
これらを可能にする仕組みを提供するのがuseState関数
① 接続(Hook into)
React 内部と接続。状態が管理されるようになる。
② "現在の値"と"更新関数"を返却する。
③ 更新関数で新しい値を React に渡す。また、React に自身のコンポーネントを再実行するように依頼する。
React 内部に保持されたコンポーネントに紐づく値をstateと呼ぶ。
- コンポーネントの中で呼び出す。
- if文やfor文の中で呼び出さない。
- 値の更新と再レンダリングは予約(非同期)される。
- 前回のstate値を使用する場合は、更新用関数に関数を渡す。
- オブジェクト型のstateを更新する再は新しいオブジェクトを作成する。
- stateの値はコンポーネントごとに独立して管理される。
- 一度消滅したコンポーネントのstateの値はリセットされる。
- stateをpropsとして渡すことで子コンポーネントで利用できる
- コンポーネントの位置によってstateが維持される。
コンポーネントに紐づく値はそれぞれ独立している。
↓
React 要素のツリー内の位置によっってどのコンポーネントの state か識別している。
コンポーネントのReact 要素ツリーにおける位置が変わらない場合は state は保持される
key を付けることによって同じ位置の同じコンポーネントでも別物と認識させることができる。
前提知識
React は React 要素ツリー(厳密には Fiber ツリー)の差分検出処理をして DOM を更新している
最後に要素を挿入した場合
差分検出は子の React 要素に対して先頭から順に比較し、差分処理を行う
先頭に要素を挿入した場合(key なし)
React は全ての子の React 要素を変更してしまい、全ての Real DOM を洗替える(子要素を全て削除して、新しい子要素を追加する)。
先頭に要素を挿入した場合(key あり)
子要素に key を持たせると、React はどの要素が変更、追加、削除されたかを識別できるようになるため、差分のみ更新することが可能になる。
- キーには必ず一意の値を設定する。
- キーに設定した値は変更しない。
- 配列のインデックスはなるべく使わない。
import "./Example.css";
CSS ファイルに class を定義して、JSX の className に適用する
- グローバルスコープとなるため、クラス名の衝突が起きやすい
- ルートファイル(`App.js`等)でグローバルなスタイルを当てたいときに使用する
style={{ color: 'red' }}
JSX の style 属性にオブジェクトを渡す
- 再利用性が低い
- 擬似要素やメディアクエリが使用できない
- レンダリングの度に計算されるので、パフォーマンスが劣る
- 動的で頻繁に計算されるスタイルの適用
import styles from "./Example.module.css";
CSS ファイルをモジュールとして JS ファイルに読み込んで、コンポーネントごとにローカルスコープを作ってスタイルを適用する
- クラス名の衝突が起きない
- `create-react-app`で設定済みのため、すぐ使える
- 将来、非推奨になる可能性がある
- CSSとJSが2つのファイルに分かれる
const StyledButton = styled.div``;
CSS を JS ファイル内に記載して、CSS を適用したコンポーネントを作成する
- クラス名の衝突が起きない
- ライブラリを導入する必要がある
- CSSとJSが1つのファイルにまとまる
- propsを参照して動的にスタイルできる
- 擬似要素やメディアクエリが使用できる
ポータルの子要素を、直接の親要素ではなく別の DOM 要素にマウントすることができる。
再レンダリングを発生させず値を保持する方法
const ref = useRef(initialValue);
- useRefは"refオブジェクト"を返す。
- currentプロパティに値が設定される。
-
ref.currentで値にアクセスできる。
値は読み書き可能
-
再レンダリングされても情報が保存される。
※通常の変数はレンダリングの度に初期化される。 -
refの値を変更しても再レンダリングがトリガーされない。
※同じく値を保持できるstateは変更されると再レンダリングされる。 - refオブジェクトをJSXのref属性に渡すとそのDOMにアクセスできるようになる。
↓
最も一般的な利用法
① ref オブジェクトの作成
const inputRef = useRef(null);
② 操作したい DOM に対応する JSX の ref 属性に渡す
<input ref={inputRef}>
③ React は DOM への参照をinputRef.current
に格納する
④ イベントハンドラなどで DOM にアクセスする
inputRef.current.focus();
⑤ イベントハンドラを<button>
のクリックイベントなどで発火させる
React のデフォルトでは、コンポーネントが他のコンポーネントの DOM にアクセスすることはできない
↓
アクセスされる側のコンポーネントがそれを許すかどうかを決めることができる(forwardRef
)
- refはレンダリングに使用しない値を保持するための逃げ道であり、頻繁に必要とするものではない。(ReactではDOMは直接操作するものではなく、あくまでReactの機能として変更が加えられるもの
- レンダリング中はref.currentを参照・変更してはならない(初回レンダリングは除く)。通常はイベントハンドラからアクセスする。
- DOMを手動で追加・削除する場合は、ReactのDOMの操作と干渉しないように注意する。
forwardRef
と共に使用する。親から受け取った ref オブジェクトをカスタマイズすることができる。
子コンポーネント内でuseImperativeHandle
に渡したメソッドが、親の ref に登録され、ref.current."メソッド名"
で実行できるようになる。
イミュータブル(immutable)
書き換えが不可(元の値は変わらない)
文字列、数値、BigInt、真偽値、undefined、シンボル
ミュータブル(mutable)
イミュータブルな値以外。オブジェクト(Object、Array など)
例えば数値のようなイミュータブルな値を変更すると、値自体が変更されるのではなく、新しい別の値に参照が向けられるようになる。
変数の参照先が変わらないため、配列の中身が変わっていることになる(オブジェクトも同様)。
コピーをすることで変数の参照先を変え、mutable な配列やオブジェクトを immutable のように扱う。
mutable なオブジェクトを immutable として取り扱う。
関数内で使用する場合は必ずオブジェクトをコピーして使用する。
- 前提
- 関数型とオブジェクト指向型
- 手続き(命令)型プログラミングとは?
- 関数型プログラミングとは?
- 関数型と手続き型は混在する
- 関数型プログラミングの重要なキーワード
- 関数型プログラミングのメリット(目標)
React は 16.8.0 の React Hooks 導入により、様々な Hooks が使用できるようになった。
それと同じく関数コンポーネントと呼ばれる関数でコンポーネントを定義するようになった。その影響で関数型プログラミングに大きくシフトした。
現実ではオブジェクト指向型と関数型が混合して書かれることがよくある。
ブラウザなどへの命令を手順通り(手続き通り)記述していく手法。
↓
コード量が大きくなってくると可読性が悪くなる。
// 制御フロー
let nums = [1, 2, 3];
let doubleNums = [];
for (let i = 0; i < nums.length; i++) {
let double = nums[i] * 2;
doubleNums.push(double);
}
手続き型の制御を(なるべく)関数に分離(隠蔽) し、やりたいことに集中できるようにするプログラミング手法。
let nums = [1, 2, 3];
let doubleNums = nums.map((num) => num * 2);
ループ制御:map メソッドが担当
やりたいこと:関数で定義(開発者が担当)
関数の利用者(関数型プログラミング)
let nums = [1, 2, 3];
let doubleNums = nums.map((num) => num * 2);
map メソッドはブラウザのビルトインメソッドだが、実際に 0 から記述すると手続き型の書き方になる。
↑
ループ処理は分離(中身は手続き型プログラミング)
for (let i = 0; i < nums.length; i++) {
let double = nums[i] * 2;
doubleNums.push(double);
}
-
(値の)状態管理と処理を分岐
- 状態と処理は切り離す
-
純粋関数(副作用を排除する)
- 特定の入力には特定の出力を返す
-
不変性(Immutability)
- 一度設定した値は書き換えない
- コードの可読性の向上
- 拡張性・再利用性の向上
- テスト性の向上
- モジュール化の向上
- Tree Shakingの向上
コンポーネントは親から子へ props を渡す。コンポーネントを跨いだり、兄弟コンポーネントに渡すことはできない。
propsでstateと更新関数をを間にある全てのコンポーネントでリレーして共有する。
↑ この書き方だと冗長になってしまうため、アプリケーション全体で共有して使用する state に関しては、useContextでラップする。
コンポーネントの状態
- Mounted:コンポーネントが生成されたとき
- Updated:何らかのstateが更新されたとき
- Unmounted:コンポーネントが消滅したとき
state 更新のタイミングで、依存値が更新されたかどうかを判断し、更新されていればcleanUp()
、callback()
の処理が実行される。
依存値が設定されていないので、再レンダリングが実行される度にcleanUp()
とcallback()
が呼ばれる。
useState などの React Hooks を内部で使用した関数(フック)のこと。
※ 関数名はuse◯◯とする。
↓
React Hooks を関数に切り出すことで再利用できる。
純粋関数
- 関数の出力(戻り値)が、提供される入力値(引数)のみに依存する。
- 外部スコープの状態(データ)は参照・変更しない。
- 引数で渡された値を変更しない。
- 関数外に影響を及ぼさない。
→ 上記の要件を満たさない操作は副作用と呼ばれる。
コンポーネントは JSX を構築する場所。JSX の構築に"直接"関係のない処理は全て副作用として扱われる。
副作用の例
- コンソールへのログ出力
- DOM操作
- サーバーとの通信
- タイマー処理
- ランダムな値の生成
↓
useEffect or イベントハンドラ内に記述
- Reduxとは
- Redux Toolkit(RTK)
- ステート(状態管理)
- ContextとuseContext
- Reduxによる状態管理
- Reduxのデータフロー
- ReduxのReducerには副作用は書かない
- Redux Thunkとミドルウェア
React とは別の状態管理のライブラリ
↓
React 以外のライブラリとも組み合わせて使用可能(React で使用する場合はreact-redux
というライブラリが必要)
素の Redux は他のライブラリが必要なケースが多い。
↓
公式が推奨する設定や書き方をまとめたもの。
様々な便利なライブラリが同梱されている。
Redux Toolkit(RTK)
- Redux
- Immer
- redux-thunk
- createSlice...
グローバルステート
アプリ全体で共有されるステート
例)useContext
、Redux
ローカルステート
特定のコンポーネント内でのみ使用されるステート
Redux を使用する場合でもルートコンポーネントは Provider で囲う必要がある。
Reducer は純粋関数として定義する。
副作用が発生する操作は Reducer には書かない。
副作用の例
- コンソールへのログ出力
- DOM操作
- サーバーとの通信
- タイマー処理
- ランダムな値の生成
↓
ミドルウェア(middleware)に記載
- Reactのコンポーネント
- コンポーネントの定義方法の変化
- Hooksの登場"前"のReact
- なぜクラスコンポーネントを学ぶのか?
- クラスコンポーネントの問題点
- クラスコンポーネントの問題点まとめ
- クラスコンポーネントのライフサイクルとは?
- 主なライフサイクルメソッド
- ライフサイクルメソッドとuseEffectの比較
コンポーネントの定義方法は大きく分けて 2 つある。
- 関数コンポーネント
- JSの関数として定義。ここまで登場したものは全て関数コンポーネント
- クラスコンポーネント
- JS(ES6)のクラスとして定義するコンポーネント
React16.8 より追加された Hooks という新機能(2019 年)の登場によってコンポーネントの定義方法が大きく変わった。
登場前
クラスコンポーネントがメイン
↓
登場後
関数コンポーネント + Hooks(公式に推奨されている)
- 状態の管理やライフサイクルを利用するにはクラスコンポーネントを使用する必要があった。
- 関数コンポーネントは存在したが、stateを管理できず、データを受け取って表示するだけなどの単純なコンポーネントにしか使えなかった。
↓
Hooks 登場前はクラスコンポーネントがメイン
- クラスコンポーネントが多用されている過去のプロジェクトに参加している、将来する可能性がある
- 既存のシステムの改修に携わる可能性がある
- クラスコンポーネントにしか対応していないライブラリを使用したい
- Reactの理解を深めたい
問題点1
共通のステートフルなロジックの再利用が難しい
高階コンポーネントやレンダープロップという設計パターンで、共通のロジックを分離して、ビュー(見た目)を担当するコンポーネントと合成することで関心を分離する。
↓
分離のたびにコンポーネントの再構成が必要であり、面倒な上にコードが追いにくくなる。
問題点2
ステートの管理が複雑なコンポーネントは保守性が低い
コンポーネントが複雑になると、ステートフルなロジックや副作用に関するロジックがコンポーネント内のいたる場所に存在してしまい、分散してしまう。
↓
コンポーネントが複雑化し、分割も困難になる。
無理に分割しようとすると、問題点 1 が発生する。
問題点3
JS の Class 構文は混乱を招き、関数に比べて可読性も低い。
JavaScript の Class 構文のthis
やbind
などの独特な構文を理解してい意識する必要がある。
↓
コードの可読性が下がり、冗長になる。
- ステートフルな共通のロジックの再利用が難しい
- ステートの管理が複雑になると保守性が下がる
- そもそもJSのクラスが混乱を招きやすく可読性が低い
↓
これらの問題を解決するため、Hooks が導入された。
コンポーネントの一生には 3 つの段階がある。
- Mounting
- Updating
- Unmounting
それぞれの段階で特別なメソッドが用意されており、特定のタイミングで実行させることができる。
Mounting
componentDidMount()
: 1 回目のrender()
が呼ばれ、DOM がレンダーされた後に 1 度だけ実行される。
Updating
componentDidUpdate()
: state が更新された直後に実行される。
Unmounting
componentWillUnmount()
: コンポーネントが破棄される直前に実行される。
クラスコンポーネント | 関数コンポーネント |
---|---|
componentDidMount() |
useEffect(..., []) |
componentDidUpdate() |
useEffect(..., [val]) |
componentWillUnmount() |
useEffect(() => { return () => {...}}, []) |
トリガー: 何らかの契機にレンダリングを予約すること
↓
レンダリング: コンポーネントを実行すること
↓
コミット: DOM への更新を行うこと
- 初回レンダリング
ルートコンポーネントを HTML(DOM)にマウントしたとき
<div id="app"></div>
← root.render(<App />);
- "state"の値が更新されたとき
コンポーネントの state の値が変更されたとき
※ 基本的には state の前後の値に差が生じた際にレンダリングがスケジュールされる
"更新用関数で渡された値"と"保持している値"を比較
値が異なることをもう少し具体的にすると、Object.is(①, ②)
の結果が同じかどうかになる。
現在値と同じ値で更新を行った場合、React は子のレンダーや副作用の事項を回避して処理を終了します
更新の回避が起きる前に React により該当のコンポーネント自体はレンダーされるかもしれない、ということに注意してください。 ツリーのそれ以上「深く」にまで処理は及ばないためこれは問題ではないはずです。もしレンダー中にコストの高い計算を行っている場合は useMemo を使った最適化が可能です。
React が(関数)コンポーネントを実行すること
↓
state の変更によってコンポーネントが再実行されることを再レンダリングと呼ぶ。
再レンダリングの結果、React が React 要素の差分のみを DOM に反映する
受け取った props の値が同じであれば再レンダリングをスキップ
コンポーネント内で定義した関数は再レンダリングのたびに再生成される。
コンポーネント内で定義した**"関数"**をメモして再利用し、レンダリングの度に生成されることを防ぐ
↓
子コンポーネントに関数を渡している場合に、不要な再レンダリングを防ぐことができる。
コンポーネントだけでなく値をメモすることが可能。コストの高い処理などをメモ化する。
↓
useMemo 自体の実行にもコストがかかるため、思い処理にのみ使用すること。
以下の特徴を持つ、サーバーへのリクエスト方式のこと
- リソースごとにURLを定義
-
メソッドでリソースに対する処理を定義
- メソッドの例)POST、GET、DELETE、PUTなど
- JSONでデータをやりとりする
処理ごとに URL のパスを設ける
リソースごとに URL のパスを設ける
React 開発のためのフレームワーク
高速な Web アプリケーションを作成するための様々な機能を提供
-
React
- UIを構築するための機能を提供するライブラリ
-
Next.js
- React開発のための機能を提供するフレームワーク
ゼロコンフィグで高度な機能を使用可能。
手動で複雑な設定をする必要なく、効率的に開発を進めることができる。
- 複数のレンダリング方法(SSR、SG、ISG)
- ファイルベースルーティング(ダイナミックルート)
- APIの作成(API Routes)
- デベロッパーに優しい開発環境(ゼロコンフィグ)
-
/pages
- ファイルまでのパスがそのままページになる
-
/styles
- グローバルに適用されるスタイルを配置
-
/pages/_app.js
- ページ遷移時に必ず呼ばれる処理を記述
-
next.config.js
- Next.jsの設定を記載
- pages配下からexportされたコンポーネントを1ページとしてルーティングする。
- [id]は動的なパスとして認識される。
- getServerSideProps、queryでダイナミックルートを取得する
・useRouter
ページ遷移を行うための値やメソッドを取得する際に利用
・<Link href>
href に遷移先の URL を設定する
・<Head>
<head>
タグ内に挿入したいタグを記載する
・<Script>
外部スクリプトを読み込む際に使用
- 複数のレンダリング方法の選択
- CSR - クライアントサイドレンダリング
- SSR - サーバーサイドレンダリング
- SG - 静的サイト生成
- Next.jsの基本構成
- ISR - インクリメンタル静的再作成
- CSR - クライアントサイドレンダリング
- SSR - サーバーサイドレンダリング
- SG - 静的サイト生成
- ISR - インクリメンタル静的再生成
・データフェッチやルーティングの全てがクライアント上で行われる
→ これまで行ってきた React 開発は CSR に分類される
※ Next.js の開発ではクライアント側でのみ行いたい処理はuseEffect
で囲む
- メリット
- 静的なファイルの配置のみで動く
- Node.jsの実行は必要ないため、サーバーの負荷が軽い
- デメリット
- 初期描画までに時間がかかる
- クローラーによってはSEO的な問題あり
- Node.js(サーバー)にリクエストが来たタイミングで動的にHTMLを生成
- 外部APIへのデータの取得やコンポーネントのpropsの値を決定する処理を行い、HTMLを作成してクライアント側に返却する
- メリット
- 生成済みのHTMLを取得するのでSEOに強い
- デメリット
- 生成処理を全てサーバー側でするので負担大
- HTMLをクライアントに渡すまで時間がかかる
- ビルド時にデータフェッチやpropsの値の決定を行い、HTMLを構築する
- クライアントからリクエストされると、サーバー側で構築することなく、生成済みのHTMLを渡す
- メリット
- 構築済みページのため表示速度が早い
- SEOも問題なし
- デメリット
- 更新頻度が高い動的コンテンツとの相性が悪い
基本的なページは
SG - 静的サイト生成
動的に作成する必要があるページは
SSR - サーバーサイドレンダリング
を用いる
- ビルド時にHTMLを構築
- 一定時間後にアクセスがあった場合、生成済みのHTMLを返しつつ、サーバー側でHTMLを更新
- 次のアクセス時に最新のHTMLを返す
- メリット
- SGを利用しながら動的なコンテンツも更新できる
- デメリット
- サーバーの設定が少し手間
- 基本はNext.jsの開発元のVercelを使う
関数、コンポーネント単位で行うテスト(Jest、@testing-library、mocha、chai など)
機能を結合し、アプリ全体に対して行うテスト(Selenium、Cypress、Puppeteer など)
関連システムを含め、エコシステム全体に対して行うテスト
様々なテストライブラリを含むパッケージ
パッケージ名 | 用途 |
---|---|
@testing-library/react | React のコンポーネントのレンダリングなどに使用 |
@testing-library/dom | DOM のテストに使用 |
@testing-library/jest-dom | Jest の DOM テストで使用 |
@testing-library/user-event | UI イベントの実行に使用 |
複雑な設定が必要ないテストフレームワーク
- 初期設定不要
- テスト用関数(test、expect、toBeなど)が利用可能
- 非同期処理のテスト
- 外部のAPIのmock化
などが簡単に行える。
JavaScript を拡張したスーパーセットプログラミング言語。
React、Vue、Angular、Svelte などと合わせて使用することができる。
- JavaScriptに変換してから実行。
- 型の定義が可能。
- JavaScriptにない記述が使用可能。
メリット
- 型定義によるチーム開発の円滑化
- 公開用ライブラリへの型定義
- バグの事前検知
- VSCodeの自動補完
デメリット
- 型の記述が面倒
- JavaScript特有の柔軟で簡易な記述の喪失