React×React-Query×TSの公開日報
- Reactのバージョンは18系です。
- 使っている技術は、React(18x), TypeScript
環境構築でコケる
環境構築にてこづってしまった。
原因はかなり単純で、react cli
を入れた後にtypescriptをインストールして、インストール後に必要なセットアップを一切していなかったからだ。
(セットアップ = tsc --init
的なコマンドで、tsconfig.jsonを作る)
→ そもそもreactと一緒にtsの環境を構築してくれる以下のコマンド
npx create-react-app {プロジェクト名} --template typescript
があるらしいので、みんなはぜひこっちのコマンドを使ってみてくれ。
18xだと、コンポーネントの型にVFCは非推奨らしい
なぜなら、18系のFCの挙動がVFCと同じみたい。
17系までのVFCとFCの違い
- VFC: 子コンポーネントの型が与えられていないと、エラーが発生する
- FC: 子コンポーネントの型が指定されていなくても、FCの中で定義されているので、エラーは発生しない = 意図しない子コンポーネントが存在する場合にエラーが出ない、。
propsを使うときは、propsの型を書く必要がある
親から渡されたprops
を子コンポーネント内で使う際は、props
の型を以下のように読み込まなければならない。
// Parent.tsx 念のため親コンポーネントも貼っておく
export const TodoList: React.FC = () => {
const todosData = [
{
title: 'Eat'
},
{
title: 'Sleep'
},
{
title: 'Repeat'
},
];
return (
<ul>
{
todosData.map((todoData) =>
<TodoItem title={todoData.title} />
)
}
</ul>
);
};
// Child.tsx
// ★ React.FC<ここにpropsの型をいれる>
export const TodoItem: React.FC<Todo> = (props) => {
return (
<li>
<label>
<input type="checkbox" />
{props.title}
</label>
<div className="editTodoButtonWrapper">
<button></button>
<button></button>
</div>
</li>
);
}
ファイル構成は難しい
現在のファイル構成
src
|-common
| |-type
| |-Todo.ts
|-components
|-TodoList
|-TodoItem
|- index.tsx
|-index.tsx
|-index.css
|-CreateTodo
課題点
- 共通cssをどこに置くか。。
- 書いてて気づいたが、共通のcssにすべきなのではなく、共通のコンポーネントにすべきだ!
- 共通cssは、src配下をさらに
js
,css
に分けてcss
側に入れておけば良さそう?(未検証)
6/22
React 16系から組み込まれた「Suspense, Lazy」とは何か?メリデメは何か?
Lazy
コンポーネントを動的に読み込む機能
メリット
- jsファイルの初回読み込みサイズが少なくなり、画面の読み込み速度・パフォーマンス向上を見込める
デメリット
- lazyloadするコンポーネントを個別のjsファイルに分割してビルドする際、ファイル名がランダムに生成される
- そのため、jsファイルをデプロイした際、ユーザーがデプロイ前の画面にアクセスしているとすると、
ChunkLoadError
が発生してしまう
- そのため、jsファイルをデプロイした際、ユーザーがデプロイ前の画面にアクセスしているとすると、
Suspense
コンポーネントのレンダリングを待機できる機能
メリット
- 非同期でデータやコンポーネントを取得する際、画面表示の準備ができるまで別の要素を表示させることができる
デメリット
- ?
6/24
学び
-
セレクトボックスのselectタグには、background-colorが効かない(今更、、)
-
react でselectboxを使う際、optionタグにselected属性を使うのは非推奨
→ 代わりに、selectタグのvalue属性に対し、selected
にしたい値を設定する必要あり -
コンポーネントスタイルの上書き方法(子コンポーネントにスタイルを受け取るpropsを設定する必要がある -> Vueはそんなことしなくてよかったなー🤔)
// 子コンポーネント export const Child = (props: any) => { return ( <div className={props.className}></div> ) };
// 親コンポーネント export const Parent = () => { return ( <Child className="childStyle"> ) };
-
ある子コンポーネントをループ処理する場合、親コンポーネント側で一意な
key
を設定する必要がある -
has
というcss擬似要素が存在する- MDNを見たところ、iOS safariでしか対応していないため、プロダクトでは使えなさそう、、
- 以下は使用例
.parent {
color: blue;
}
.child {
color: red;
}
.parent:has( > child:hover) {
color: white;
}
6/27
学んだこと
textareaの属性の当て方
<textarea id="" cols={30} rows={3}></textarea>
画像の読み込み方
結論: import
を使う
import img from '../../../../images/img.jpg';
return (
<img src={img} />
)
hooksとは
- 関数コンポーネント内で、プロパティやライフサイクルの機能を使えるようにするメソッド
- Reactのhooksで作成したオブジェクト型の変数は、直接触ってはいけない
- 参照先が変わってしまい、イミュータブルな特性が失われるため
React suspenseとは
- データをロード中のコンポーネントがレンダリングを放棄し(= サスペンドし)、この状態をReact側が検知してサスペンド状態のUI描画を行う機能のこと
- 従来は、サスペンド時のUIの描画はコンポーネント1つ1つの責務であったが、suspenseが登場してからは、複数コンポーネントのサスペンド時のUIを取得することができるようになった
開発環境でコンポーネントが2回呼び出している、?
- おかしいなと思っていたが、原因は
StrictMode
でした(公式リファレンスにも書かれていましたが、Reactの意図しない動作を防ぐために開発環境では2回呼び出しているみたいです)
非同期処理の中でHooksを使う際の注意
- todoのデータを非同期で取得した時のコールバック関数内で、
useStatte
を使うと無限に非同期処理が実行されてしまいました。- 原因は、
useState
を使うと(正確には、内部状態やプロパティが更新されると)、再描画されるため、その度に非同期処理が実行され、、、というループに陥っていた - 解決策として
useEffect
を使うらしい(これから学ぶ💪)
- 原因は、
6/28
学び
useEffectの第二引数
- 必ず配列
- 値を監視したい場合は、配列の中に値を記載する
useEffect(() => { ...etc }, [value])
react-router-dom
のSwitch
はRoutes
に変わっている
<Link />
と<a></a>
の違い
-
Link
は、クライアントサイドでレンダリングを行うため、画面間でデータを持ち回すのが容易である -
a
は、サーバーサイドでレンダリングを行うため、アプリ全体にリロードが走ってしまう
7/3
学び
useContext
の使い方
- 親コンポーネント側で
createContext
を使う - データを渡したい子コンポーネントが存在するコンポーネントを囲む
- データを使いたい子コンポーネント側で
useContext
を使う
コンポーネントの型付け
- 以下の
ComponentType
にprops
の型を付与する
export const Componet: React.FC<ComponentType> = (props) => {
配列の型の付け方
-
Array<T>
のように、配列の要素の型まで指定する必要あり
7/9(土)
react-query
- データの取得、取得したデータのキャッシュ、保存先のデータと同期するためのライブラリ
-
useQuery
は、第一引数にkey
、第二引数にクエリ関数(= promiseを返すメソッド)- クエリ関数で
fetch
を使う際、fetch
の仕様上エラーを返さないので、自分でthrow Error
する必要がある
- クエリ関数で
- 一意な
key
には、単純な文字列型だけでなく、配列型・オブジェクト型も指定することができるuseQuery('todos', ...) // queryKey === ['todos'] 文字列をkeyに指定しても、内部的には配列として保持される
- クエリキーは単にキーとして使うだけでなく、クエリ関数の引数として使うこともできる
7/12(火)
userId
- 一意のユニークIDを生成するHooks
- 配列処理の
key
に使うと、パフォーマンスが落ちる
useQueryとuseMutations
-
useQuery
はデータ取得に使える -
useMutations
はデータの保存、更新、削除時に使える- 引数にクエリ関数を設定することができる
- 定義済みの関数を設定しようとすると、「この呼び出しに一致するオーバーロードは存在しません」と怒られてしまう
- 返り値に
mutate
というメソッドが存在し、そのメソッドがクエリ関数と同じ働きをする
- 引数にクエリ関数を設定することができる
オーバーロード
- 同じ名前で複数の実装を持つこと。「関数のオーバーロード」といった使い方をする。(この概念はJavaScriptでは使えない)
- メリット:入力する値によって、出力が変わること
hooks
- 必ず関数コンポーネント or hooks内で呼び出す必要がある
7/13(水) -> この日までで、約40h
react-queryは何が嬉しいのか
- キャッシュ機能があることで、サーバーへアクセスする回数を減らせること
- キャッシュを使って状態管理をすることで、クライアントの状態管理ができる
- 今までクライアントの状態管理をする際は、
useContext
やredux
を使っていた
- 今までクライアントの状態管理をする際は、
- コード量がかなり減る
- 例えば、従来の
store
管理では、dispatch
してデータを取得し、stateを更新する処理も自分で書いて実行する必要があったが、react-query
ではuseQuery
を使ってデータを取得するだけで、全て完了する。
- 例えば、従来の
stale while revalidation
- 初回のマウント時はキャッシュがないため、サーバーから取得する
- 2回目以降は、キャッシュを表示させつつ、裏側でサーバーへアクセスし、最新のデータを表示させることができる
useContextの問題
- 再レンダリングが多く実行されてしまう
getQueryData or useQuery
- getQueryDataでキャッシュからデータを取得した後、キャッシュの値を更新しても取得したデータは古いまま
- useQueryで取得したデータは、キャッシュのデータを更新すると、更新後のデータが即座に反映されるため、画面にすぐ反映させたい場合は
useQuery
でデータを取得する
stale while revalidate
- アプリケーションへのデータの反映を早くするために、最初はキャッシュから使うが、裏側で最新のデータをフェッチするという概念
suspenseをネストさせた場合
以下のようなコードがあるとする。(FetchView
、AxiosView
は、内部に非同期処理を持つコンポーネントとする)
const sample () => {
return (
<suspense fallback={<Spinner />}>
<FetchView />
<suspense fallback={<Spinner />}>
<AxiosView />
</suspense>
</suspense>
)
}
この時、ネストしているAxiosView
は、親であるFetchView
が表示された後から実行されるため、AxiosView
単体での非同期処理より表示が遅くなる
改めて、Reactのライフサイクルについてまとめる
-
componentWillMount
=> コンポーネントがマウントされる直前に呼び出される -
componentDidMount
=> コンポーネントがマウントした直後に呼び出される -
componentWillReceiveProps
=> コンポーネントのprops
の値が変更された時に呼び出される -
shouldComponentUpdate
=> コンポーネントのprops
やstate
が変更され、再描画される前に呼び出される。このメソッドでtrue
を返した場合は再描画され、false
を返した際は再描画しない。 -
componentWillUpdate
=> コンポーネントが再描画される直前に呼び出される。shouldComponentUpdate
がfalseの場合は、呼び出されない -
componentDidUpdate
=> コンポーネントが再描画された後に呼び出される -
componentWillUnmount
=> コンポーネントが棄却される前に呼び出される
useMutate
-
useQuery
と違い、自動でキャッシュとサーバーのデータを同期してくれることはない - そのため、こちらで処理を細かく記載する必要がある
- データを同期する方法は2つある
-
invalidation
:Query
に、サーバーのデータを更新したことを知らせる。 -
Direct Updates
: `直接キャッシュの値を変更する -
ここを見る限り、キャッシュの更新には
invalidation
を使うことを優先した方がいいとのこと。- 理由は、バックエンドとフロントエンドのデータを同じ状態に同期する必要があり、直接更新すると、本来サーバーで行うべき処理を、フロント側でも行う必要があるためである。
- 例えば、リストからデータを削除する際、サーバーにリストのidを送れば済む話だが、直接更新する場合は、フロント側で削除するデータを探し、削除する機能を実装する必要が出てきたりする。
-
useMutationはsuspenseをサポートしていない?
- https://github.com/TanStack/query/discussions/967
- 自分の見解:
useMutation
はuseQuery
と違い、キャッシュとサーバーのデータの整合性を担保できる処理を記載する必要があるため、宣言的に使えるのではなく、こちら側で処理を記載できるようにし、自由度を上げた仕様にしている。- よって、
useMutation
にはsuspenseもサポートされておらず、こちら側でローディング中かどうかを記載する必要がある - ただし、
ErrorBoudary
に関してはサポートしているよう
- よって、
7/14(木) -> 計50h
useState
に渡す型
- ステートの型を渡す
useEffect
の第二引数に入れるもの
useEffect(() => {
// 処理
}, ?) // ${?}に何を入れるか
- 結論:
useEffect
を実行したいタイミングで変更される値- 第二引数の値の変化によって、
useEffect
は実行されるため、watchして欲しい値を入れる
- 第二引数の値の変化によって、
Reactのレンダリングパフォーマンスチューニングについて
基礎知識
- Reactコンポーネントは以下の3パターンで再レンダリングが行われる
- 自身のstateが変更された時
- propsのstateが更新された時
- 親コンポーネントが再レンダリングされた時に子コンポーネントも一緒にレンダリングされる
- コンポーネントが再レンダリングされた時、以前持っていたデータのメモリは空き、新しいメモリに再び保存される(ガベージコレクション)
useCallback
-
useCallback
はメモ化されたコールバックを返すhook
メモ化とは・・・メモ化またはメモライゼーションとは、コンピュータプログラムの最適化の手法で、高コストな関数呼び出し結果を保存し、同じ入力が再び発生した際にキャッシュから値を返すというもの(Wikipediaより)
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
メモ化されたコールバックを返します。
インラインのコールバックとそれが依存している値の配列を渡してください。useCallback はそのコールバックをメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。これは、不必要なレンダーを避けるために(例えば shouldComponentUpdate などを使って)参照の同一性を見るよう最適化されたコンポーネントにコールバックを渡す場合に便利です。
上記は公式より。
これを読むと、useCallback
の目的は「不必要なレンダーを避けるため」であることが分かる。
パフォーマンスが悪い例
const Child = ({ onClick, count }) => {
return <button onClick={onClick}>{count}</button>;
};
const Parent = () => {
const [count1, setCount1] = React.useState(0);
const increment1 = () => setCount1(c => c + 1);
const [count2, setCount2] = React.useState(0);
const increment2 = () => setCount2(c => c + 1);
return (
<>
<Child count={count1} onClick={increment1} />
<Child count={count2} onClick={increment2} />
</>
);
}
上記で、片方のChild
ボタンをクリックすると、親のParent
コンポーネントのステートを更新するため、親は再レンダリングされ、親が再レンダリングされると、Child
も再レンダリングされてしまう。
解決策
-
setCount1
,setCount2
をuseCallback
で囲む - 子コンポーネントを
React.memo
化する
React.memo
- React.memo は高階コンポーネントです。
高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数です。(公式より)
React.memo
を使う目的は、親コンポーネントの再レンダリングによって子コンポーネントが再レンダリングされてしまうのを防ぐためです。
React.memoは、props
のみチェックするため、props
が変更された場合に限り、子コンポーネントを再レンダリングします。
備忘録
- おバカな私は、以下のようにデータを更新する処理に対して、
useCallback
を使ってみました←
const updateData = useCallback(() => {
update(data); // dataは関数外で定義されているuseStateとします
}, []);
するとどうでしょう。データの更新が全く行われなくなりました。
useCallback
は、中の処理をキャッシュするため、キャッシュさせたくない更新処理には使っちゃだめなんですね。。。
7/15(9h)
列挙型(別名enum型)
- 関連する値の集合を扱うことができる
- TypeScriptの列挙型はnumberベースとなっている(値を入れない限り、デフォルトで数値が0~1ずつ増えていく)
enum = {
apple, // 0
banana, // 1
meron // 2
}
Tree-shaking
コンピューティングでは、ツリーシェイクは、コードを最適化するときに適用されるデッドコード除去手法です。minifierに共通する従来の単一ライブラリのデッドコード除去技術とは対照的に、ツリーシェイクは、エントリポイントから開始し、実行可能な関数のみを含めることで、バンドル全体から未使用の関数を除去します。簡潔に「ライブコードインクルージョン」と表現されます。(wikipediaより)
JavaScriptでは、import/exportが静的なため、Tree-shakingによって使われていない関数を除却しバンドルサイズを小さくすることが容易となっている。
Reactのcss適用手法について
Pure CSS
- create-react-appした際に用意されているやり方
- スコープが広く、クラス名が衝突しやすいため、オススメはされない(←今回やってしまった)
メリット
- 学習コストが低い
デメリット
- cssクラスのスコープが広く、命名が衝突しやすい
style props
<button style={
color: red;
font-weight: bold;
} />
CSS Modules
- CSS Modulesとは、cssの適用範囲をコンポーネントに閉じさせる手法です
- 記法がPure CSSに近いため、学習コストが低い
- 懸念点としては、現在ライブラリがメンテナンス状態であり、将来的に非推奨
メリット
- 学習コストが低い
- スコープがローカルなため、cssクラス名の競合が発生しづらい
デメリット
- 将来的にサポートが終了し、非推奨になるリスクがある
- css, jsの二つのファイルをメンテナンスする必要があるし、開発する際に行き来しないといけない
CSS in JS
- jsファイル内にcssを記載する
- ファイルをcss, jsで分ける必要がない
メリット
- jsファイル内にcssを記載でき、開発体験が上がる
デメリット
- 若干のパフォーマンス低下
選択肢
- styled-components:以前から存在し、スターがemotionに比べ2倍以上ある。パフォーマンス面の課題が大きい
- emotion:リリースされてから人気が上がってきている(npmより)
- 最近はvanilla-extractも人気が上がってきている(npmより)
- ビルド時に静的なcssを生成するため、runtimeがゼロでパフォーマンスが良い
- stiches/react:これはnear runtime zero。runtimeゼロ系で一番人気(npm-trend)。記法はstyled-componentsに似ており、型安全に書けて、パフォーマンスもいいことからstyled-componentsの強化版のようなもの。
- linaria:こちらはruntimeがゼロ(github, npm)。
- 以下の理由により、個人的にはこれが一番いいのでは?という感想
- 記法がstyled-componentsに似ている
- 特殊な書き方が少なくて学習コストが低そう
- React用のライブラリ(
linaria/react
)が用意されており、サポートされている - runtimeゼロ系のライブラリで2番目に人気
- 以下の理由により、個人的にはこれが一番いいのでは?という感想
👉linariaを使ってみます
👉linariaは、beta版しか上手く動かなかったためなしとする
postcss
- 特殊な構文でcssを読み取って処理し、通常のcssを返すJavaScript製のツール
- プラグインを使うことで、sassのような記法やlinter、その他cssを書く上で助けとなるツールを使うことができる
performance~runtime~
- プログラム実行時のパフォーマンス
- ex.) CSS in JS は CSS modulesに比べて、runtimeが劣る
performance~loadtime~
- ページ読み込み時のパフォーマンス
- ex.) CSS in JS は CSS modulesに比べて、loadtimeが劣る
冪等性
- ある処理を何回実行しても同じ結果になる性質のこと
HMR
- Hot Module Replacementの略:ビルドツールがtsの変更を検知した際、画面の再描画を行うことなく際レンダリングしてくれる機能
7/17
xxxProps
が一般的
propsの型変数の命名は、7/18
Reactを使う上での命名(絶対的な決まりではないが、一般的に使われやすいもの)
- propsの型変数の命名:
xxxProps
- useStateの関数:
setXxx
- イベントをハンドリングする関数:
onXxx
orhandleXxx
- 下位コンポーネントに処理を渡す際:
onXxx
ステートフル、ステートレス
- ステートフル:以前のリクエスト・レスポンスの文脈を汲み取り、その文脈を踏まえた上でレスポンスしてくれるもの
- ステートレス:以前のリクエスト・レスポンスの文脈とは関係なく、あるリクエストをしたら同じ結果が返ってくるもの
- ステートフルなプログラムでは、状態のパターンが複雑化しやすく、不具合が混入しやすくなる
ボタンのUXについて
- 内部で早期リターンしてしまう場合は、そもそも押下できない作りの方が、ユーザーにとって分かりやすい
contextの使い所
- 主に、複数のネストした様々なコンポーネントで、一部のデータへアクセスしたい場合に使われる
- デメリットとしては、コンポーネント同士が依存するため、再利用性が低くなること
TypeScript
- never型:値を持たない型のこと。プログラミング言語の設計にはbottom型の概念があり、TypeScriptではそれを表現するためにnever型として設定しているそう
Flow
- JavaScriptのtypeチェッカー
- 一昔前のOSS。フェイスブック社が開発
useRef
- 値の参照を保持するためのhook
- 主にDOMの参照に使われる
const element = useRef<HTMLInputElement>(null);
<input ref={element}>
こうすることで、element.current.focus()
でフォーカスさせられる。
内部の設計としては、Refオブジェクト({ current: null }
)を生成し、メモ化しているだけ。
他の使い方としては
const ref = useRef(initialValue);
// 設定した値を取り出す
const value = ref.current;
// 値を設定し直す
const newValue = 10;
ref.current = newValue;
useRefで作成した値を更新しても、参照先を更新するだけのため、再レンダリングが発生しない。
7/20
クエリパラメータとパスパラメータ
どちらで実装すべきか迷った時
- パスパラメータ:一意の画面を表示させたい場合(学校ごとのページとか、各todoの詳細ページとか)
- クエリパラメータ:フィルタ・ソート・検索などの特定の条件を追加したいとき
react-router
- <Route />コンポーネントをネストさせると、親の中に子を表示する、というレンダリングになる
<Route
path={`parent`}
element={<Parent />}
>
<Route
path={`child`}
element={<Child />}
/>
</Route>
↑の例では、Parentコンポーネントを描画した上で、Childコンポーネントを表示させる、という作りになる。
さらに、その特性を利用し、Parent側で<Outlet />というコンポーネントが使える
const Parent = () => {
return (
<>
<div>parent</div>
<Outlet />
</>
)
};
こうすることで、<Outlet />部分に、ネストした子コンポーネントが表示される。
Map
- 同じ配列やオブジェクト等の値からデータを取り出すケースが多い場合、Mapを使った方が効率がいい
- JSのMapは順序性を持っている(Rustは持ってなさそう)
7/21(75h)
Map
- 既存のkeyに対し、set()で新たな値をセットすると、値が更新される
- Array.from(new Map([...hoge]))で、エントリーの配列を取得できる
- new Map([...hoge]).valuesで、配列のうち値の部分を挿入順に持つ配列を取得できる
react-router
- パスを
:todoId
のように設定すると、コンポーネント内で、useParams().todoId
のように取得可能
7/22(77h)
React公式
- functional componentベースの公式レファレンスを出している(https://beta.reactjs.org/)
useEffect
- 外部のデータとReact内部の状態をつなげる処理を書くところ
- 外部データへアクセスする記述がなければ、冗長なコードになっている可能性が高い