概要
Reactハンズオンラーニング 第2版を読んでメモってた残骸を発見した。
アウトプットして脳のメモリーを開放させてください。
ざっくりまとめると、これからReactを体型的に学ぶにはおすすめしたい一冊。
Reactを学習するために必要なJavaScriptの知識から教えてくれる優しい本です。
🔖Reactの基本 [1章〜5章]
宣言型と命令型
- Reactは宣言型
- 宣言型のアプローチはコードを見れば「何をしたいのかが分かる」
- 読みやすいコードになる
- スケールがしやすい
イミュータブル
- 変更を加えることが不可
- イミュータブルに保てる
非破壊的
なメソッドを使うべき - 関数型プログラミングは全てのデータがイミュータブル
- データに変更を加える時はコピーを作成してから変更
純粋関数
- 関数型プログラミングの中心的な概念
- 直接値を変更しない
- 引数を受け取って戻り値を返す
高階関数
- 他の関数を引数として受け取る
- 戻り値で関数を返す
-
Array.map
、Array.filter
、Array.reduce
は全て高階関数
JSXで使うmapに関して
props.recipes.map((recipe)=> <Recipe key={i} {...recipe} />)
- スプレッド構文で渡すと簡潔だが全てのプロパティが渡るので注意
- ある配列データを利用してDOMを組み立てるよくあるパターン
-
map
は新しい配列を返すので、React要素の入った配列が返ってくる - React要素はDOM要素を構築するためのレシピが記述されたJavaScriptのオブジェクト
-
console.log
を見るとReact要素のオブジェクト
が入っている
JSX
-
{}
でJSXに含めることができる
// スプレッド構文
<List {...list} />
// 上と同じ意味になる
<List a={list.a} b={list.b} />
Tips
- reduceは配列をオブジェクトに変更することもできる
- オブジェクトをreturnするときは()をつける(returnを省略する時だけ)
const returnObject = () => ({
id: 1,
name: 'yukiji',
})
console.log(returnObject())
🔖ステート管理 [6章]
アプリケーション全体のステート管理
- アプリの最上位で全てのステートを管理する
- ルートコンポーネントでのみステートを保持しアプリケーションのステートを一元管理するやり方は初期のReactで流行した。
- アプリケーション内の全てのコンポーネントがステートを持つのは望ましくない
- 機能追加やデバッグが困難になる
-
state
をprops
で上から下へ伝える- 逆にユーザー操作は関数プロパティ経由で下から上に伝える
- しかしアプリケーションの規模が大きくなるとこのパターンは非現実的になった。
🔖フック [7章]
開発に欠かせないフック
- useState
- useRef
- useContext
- useEffect
- useLayoutEffect
- useReducer
- useCallBack
- パフォーマンス最適化
- useMemo
- パフォーマンス最適化
useState
-
useState
フックはステートの初期値を受け取って配列を返す関数 - [
ステートの値,
ステートを変更する関数
] -
useState
でsetする関数はステート値だけでなく関数も渡せる - 引数でステート値を受け取り、returnで返す必要がある
-
useState
は値が変更されない限り同一のインスタンスを返す
useRef
-
useRef
を使うとDOMに直接アクセスできる - パラダイムに反する。イミュータブルでも宣言型でもない。
- React以外のライブラリとデータのやり取りをする時などに用意されているもの
- なるべく
useState
を使う
Reactコンテキスト
- コンテキストプロバイダーコンポーネントにデータを渡す
- コンポーネントツリー全体 or 一部を囲う(出発点の空港のようなイメージ)
- コンテキストコンシューマー(目的地の空港のようなイメージ)
- コンテキストからデータを読み出す
- Stateの1箇所管理(中継しなくて良い)
- コードが読みやすくなる
-
useContext
フックからデータを取得 - フックが登場する前はレンダープロップという方法でアクセスしていた
コンテキストとステートを併用する
- 親コンポーネントでステートを保持しその値を
Provider
コンポーネントに渡す - カスタムプロバイダー
- 値をsetする関数を全体公開するのは避ける
- 必要な箇所のみ公開したい
useEffect
-
useEffect
はコールバック関数を引数にとる - 副作用として実行したい処理
- 副作用 = 描画の一部ではないもの
- コンポーネントの描画後に実行したい処理を記述できる
- ステート管理のフックと協調して働くように設計されている
Reactアプリケーションのサイクル
- データの更新 → コンポーネント再描画 → 副作用が実行
依存配列
- 副作用が実行される条件を指定できる
useEffect(()=> {
console.log(`Hello${val}GooBye${val2}`)
}, [val, val2])
- val,val2が更新されたら実行される。[]空の配列を渡すと初回レンダリングのみ
useEffect(()=> {
console.log(`Hello${val}GooBye${val2}`)
return () => goodBye()
}, [val, val2])
- 戻り値として関数を返した場合、コンポーネントツリーからアンマウントされた時にその関数が呼び出される。
- コンポーネントが削除されたタイミングで実行できる。
useMemo
- メモ化された値を取得するフック
- メモ化とは情報工学でパフォーマンス改善のために計算結果をキャッシュすること
- コンポーネントの描画関数内で使うことで描画パフォーマンスが上がる
const words = useMemo(() => {
const words = children.split('')
return words
}, [])
- childrenプロパティに依存。変化しない限り呼び出されない。
useCallBack
- メモ化された関数を返す
const fn = useCallBack(()=> {
console.log(...)
}, [])
- fnは初回描画時に初期化されてから常に同一のインスタンスが代入される(不変になる)
useLayoutEffect
-
useEffect
よりも前に実行される - 描画が画面に反映される前に何か処理を実行したいときに使う
useReducer
- リデューサー関数とステートの初期値を引数にとる
const [checked, toggle] = useReducer(cheked => !checked, false)
- リデューサーは同じ引数で呼び出された場合、必ず同じ戻り値を返さなければいけない
- より複雑なステート更新をする際に効力を発揮する
const [user, setUser] = useReducer(
(user, newDetails) => ({...user, ...newDetails}),
firstUser
)
memo関数
- Reactの関数コンポーネントをメモ化するトップレベルの関数
const PureCat = memo(Cat)
- プロパティに変化がなければ再描画しない
- 追加されたコンポーネントのみ再描画する
- プロパティが関数の時も対処できる
- アロー関数は描画のたびにインスタンスが生成される
カスタムフック
- カスタムフックを使うと重複したコードを避けられる
const Custom = () => {
useState()...
useEffect()...
return...
}
カスタムフック → useFetchフック
export function useFetch(uri) {
const[data, setData] = useState()
const[error, setError] = useState()
const[loading, setLoading] = useState(true)
useEffect(()=> {
if(!uri) return
fetch(uri)
.then(data => data.json()
.then(setData)
.then(()=> setLoading(false))
.catch(setError)
}, [uri])
return {
loading,
data,
error
}
フックのルール
-
コンポーネントのスコープかカスタムフックに中から呼び出す
-
ひとつのフックで多くのことをせず複数のフックに分割する
-
フックは常に描画関数のトップレベルから呼び出さなければいけない
-
if
文やfor
文の中はNG - 副作用関数内で
if
文やfor
を利用するのはOK
-
-
フックは非同期呼び出しはNG
- 副作用関数内でならOK
useEffect(()=> { (async () => { await somePromise() }) () })
-
Reactはフックの戻り値及び依存配列を配列で管理できる
コンポーネントのパフォーマンス改善
- Reactアプリケーションではコンポーネントは頻繁に描画される
- Reactにおけるパフォーマンスチューニングは不要な描画を抑えて、描画回数を減らすこと
-
memo
,useMemo
,useCallBack
を使う
いつパフォーマンスチューニングを行うか
-
memo
,useCallBack
,useMemo
フックは乱用される傾向にある - 基本的にReactはパフォーマンスを念頭に設計されている
- まずは機能を作り込んでから、パフォーマンスに問題があるときにのみチューニングを施す
- チューニングはゴール設定をする
- 闇雲にやってもコストに見合った成果は得られない
- もたつくところを計測する
-
ReactDevTool
→React Profiler
-
🔖Reactのデータ [8章]
- データはアプリケーションにとって血液
- 各コンポーネントに養分を送っているイメージ
データの送信
fetch('/create/user', {
method: 'POST',
body: JSON.stringify({...})
})
- JSでのファイルのアップロードは
FormData
オブジェクトを使う
リクエストの認証
- HTTPリクエスト送信する際に認証を求められる場合がある
- 特に個人情報や機密情報など
- リクエストごとにユニークなトークンを送信することでサーバーに対して認証する
- トークンはHTTPリクエストの
Authorization
ヘッダーに付与される
fetch('/create/user', {
method: 'GET',
headers: {
Autorization: `${token}`
}
})
Reactでのfetch
-
useEffect
→fetch
の呼び出し -
useState
→ 取得したデータを入れる
データの保存
window.locationStorage
- localStorageはブラウザにデータを保存する。sessionStorageはブラウザを閉じるまでデータを保存
const saveJson = (key, data) =>
localStorage.setItem(key, JSON.stringify(data))
- HTTPキャッシュを利用するのも手
- HTTPレスポンスヘッダに
Cache-Control: max-age=
を指定すると、指定された期間ブラウザにデータをキャッシュする
- HTTPレスポンスヘッダに
非同期リクエストの状態管理
- HTTPリクエストもPromise同様3つの状態(成功、失敗、保留)を持つ
- 3つの状態を正しくハンドリングするにはそれなりの量のコードを記述する必要がある
- HTTPリクエストは実際に時間がかかったり、エラーになる可能性があるので3つの状態を正しくコーディングする
レンダープロップ
- リストコンポーネントのArray.mapコードを抽象化できる
function List ({data = [], renderItem, renderEmpty}) {
return !data.length ? (
renderEmpty
) : (
<ul>
{data.map((item, i) => (
<li key={i}>{renderItem(item)}</li>
))}
</ul>
)
}
仮想リスト
- またはウィンドウリストという
- 巨大なリストを効率よく表示するテクニック
-
react-iwndow
,react-virtualized
などのライブラリがある
Profiler
-
Profiler
で描画にかかった時間が見れる - コンポーネントの抽象化は逆にアプリケーションが複雑になる場合がある
- 常にシンプルに保つ責務がある
複数のリクエスト
- 複数のリクエストから取得したデータを合成してユーザーに提示する手法は覚えておく
- 一つのリクエストが成功してから次のリクエストが実行される様をウォーターフォールリクエストと呼ぶ
- ウォーターフォールでの実装は並列処理できないか検討する
GraphQL
💡 facebook社によって考案された技術- WebAPIを宣言的に記述するための手法
- 1回のAPI呼び出しで必要なデータを全て取得できる
- リクエストのたびに特別なクエリを発行する
- GraphQLをサポートする多くのサービスはクエリを書いてその場で実行と結果の確認ができるREPL環境を提供する
- HTTPリクエストのボディにクエリ文字列が格納されている
- fetchで実現できる
Tips
- fetchも含むAPIリクエストに絞れる
Devツール → NetWorkタブ → XHR
- アンマウントされたコンポーネントのステートを更新しようとするとエラーになる
🔖サスペンス [9章]
Reactサスペンス
-
suspence
→ Facebook社が自社サイトで特定の問題を解決しようとして開発した -
suspence
コンポーネントはPromise
がthrow
されるとfallback
に設定されたコンポーネントを描画する -
JavaScript
はどんな型でもthrow
できる -
throw
されたものは何でもcatch
できる
エラーバウンダリー
💡 エラーを1箇所でハンドリングする- あるコンポーネントで例外が発生した場合でもアプリケーション全体に影響を及ばさないようにする機構
- 自分が実装していないエラーを追うのは困難
- クラスコンポーネントで使用できる
サスペンスデータソース
- Suspenceコンポーネントと協調するために設計された関数
- Promiseさえあれば作成できる
ReactのDOM更新
- コンポーネントツリーのコピーを作成
- 変更を反映し現行のツリーと比較
- 変更箇所のみをDOMに反映させる(既存のDOMノードを再利用する)
- DOMの更新は非同期処理
- メインスレッドがブロックされ、UIの応答性が損なわれる
Fiber
💡 Reactコアの提供する共通の変更箇所検出アルゴリズムが利用できるように- React v16でDOM更新のアルゴリズムを刷新
- より非同期的なアプローチ
- DOMの更新処理を
reconciler
とrenderer
の2つのモジュールに分ける -
reconciler
は変更箇所の検出(Reactコア) -
renderer
は描画のみ(ReactDOM) - Fiberの実行はメインスレッドがアイドル状態の時にのみ行われる
🔖テスト [10章]
ES lint
-
eslintignore
- ビルド済みのJSはチェック対象から外す
TDD Test-Driven Development
💡 まずは小さい単位でTDDのサイクルを回す。Reactにおいてはフックの仕様が明確になる- まず初めにテストを書く
- テストを実行して失敗を確認する(Red)
- コードを書いてテストの成功を確認する(Green)
- リファクタリングをしてコードの改善をする(Gold)
Jest
- React推奨のJavaScriptテストフレームワーク
- Node.js上でDOMブラウザのシュミレーションができる
戻るボタン対策
- 戻るボタンで前ページを表示する場合はサーバーにリクエストは発行されず、ブラウザのキャッシュに保存されたデータを利用する
- ECサイトなどではワンタイムトークンを利用して再誤注文を防ぐ
🔖その他メモ
Tips
- Production環境ではWebアプリケーションのエラー画面は表示させない
JS Tips
- オブジェクト、配列、関数の参照は内容ではなく同一のインスタンス下で決まる
const a = [1,2,3]
const b = [1,2,3]
a === b // false
const a = b = [1,2,3]
a === b // true