「ReactQueryが流行っているらしいけど、いつ使えば良いのかわからず触ったことがない」
「useStateやuseEffectなど基本のフックを使うだけじゃダメなの?」
「ドキュメントやYoutubeを見たけど細かい設定項目が多すぎて諦めた」
本日はそんな方に向けてReactQueryの基本を解説していきます。
どれかの書き方が一番良いというものではなく、それぞれの足りない点を補う意味で新しいライブラリやフレームワークは練習しておきたいですよね。
useStateやuseEffectなどReactに最初から使用できるフックの使い方がわかってきたかもしれません。
本記事ではReactQueryの基本であるuseQueryとuseMutationを中心に、「ReactQueryを使うと何が楽になるのか」を知ってもらう内容になっています。
あまり細かい設定は飛ばして基本の書き方に絞って解説しますので駆け出しエンジニアの方でも読んでいただけるはずです。
また動画もあるので必要に応じて活用してください。
ReactQueryを導入する方法
npmやyarnなどパッケージツールでReactの環境を作るだけではReactQueryを使うことはできません。
Reactの環境を作成した状態で追加で以下のコマンドを実行します。
npm i @tanstack/react-query
また今回は紹介しませんが便利な開発ツールも用意されていて、そちらは以下のコマンドを追加で実行します。
npm i @tanstack/react-query-devtools
正常にインストールできているかはpackage.jsonを確認すると良いでしょう。
{
"name": "react-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-query-devtools": "^5.0.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.4",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
~~ 以下省略 ~~
}
続いてindex.jsにて以下のように記載します。
関数とコンポーネントはインポート文が必要なので忘れないように書いてください。
import React from "react";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// QueryClientとQueryClientProviderをインポート
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
reportWebVitals();
QueryClientProviderと言う専用コンポーネントのなかでしかReactQueryを使うことができません。
そのため基本的にはApp.jsを囲うことが多いです。
ちなみにReactQueryからインポートするときの文章ですが
昔は「import 名前 from “@react-query”;」でしたが今は違います。
今は「import 名前 from “@tanstack/react-query”;」になります。
古い教材や記事、エディタの補完機能で「@react-query」と出ているかもしれませんが、
「@tanstack/react-query」の方を使わないと今はエラーになるので注意してください。
ReactQueryのuseQureyの使い方
それではReactQueryのなかで一番基本となるuseQueryフックを使ってみます。
専用のコンポーネントファイルを作成しても、App.jsに直接書いてもらっても大丈夫です。
useQueryはデータ取得をするためのフックになります。
APIやDBなどから保存されたデータを取得する場合に使うのですが、今回は簡易的にstudentsDataと言う配列を手書きで作ってstudentsDataを取得する形で試してみます。
import { useQuery } from "@tanstack/react-query";
import React from "react";
// 仮のデータベースを配列でstudentsDataと言う名前で作った
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
return (
<div>
</div>
);
};
export default UseQuery;
useQueryフックはuseStateなど他のフックと同様にインポートして初期値を設定する使い方です。
以下のように書いてみます。
// useQueryをインポート
import { useQuery } from "@tanstack/react-query";
import React from "react";
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
const studentsQuery = useQuery({
queryKey: ["students"],
queryFn: () => {
return [...studentsData];
},
});
return (
<div>
</div>
);
};
export default UseQuery;
useQueryはオブジェクト形式にしてqueryKeyとqueryFnと言うプロパティを必ず設定することになっています。
他にもプロパティがありますが最初は必須のプロパティの2点だけをマスターしましょう。
queryKeyとは「クエリキー」とも呼ばれて、データを扱うページのスラッグだと思ってください。
上記コードではstudentsとしているので、「https://xxxxx.com/students/」と言うページで取得したデータを表示させるような感じです。
ブログサイトなど詳細ページを作る場合はIDや日付などで区切って「https://xxxxx.com/students/0001」のようなURLで個別のページを表示するイメージです。
WordPressなど何かしらのWebサイトを作ったことがある方であれば理解しやすいかと思います。
続いてqueryFnですがデータを取得して実行する処理の内容を書きます。
そのためプロパティに対する値は上記コードのようにアロー関数もしくは関数名を書くことになります。
今回は配列studentsDataをそのまま表示するだけにしようと思うので、returnしている関数になっています。
ここまできたらuseQueryを代入した定数studentsQueryをコンソールで見てみましょう。
import { useQuery } from "@tanstack/react-query";
import React from "react";
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
const studentsQuery = useQuery({
queryKey: ["students"],
queryFn: () => {
return [...studentsData];
},
});
// ここを追加
console.log(studentsQuery);
return (
<div>
</div>
);
};
export default UseQuery;
オブジェクトになっていて中を開けるといろんなプロパティが格納されているのがわかります。
これらはuseQueryで使用できる便利な機能です。
例えばdataと言うプロパティには取得したデータが保存されています。
自分たちで最初に手書きで作った配列studentsDataの内容ですね。
そのためデータを使うときはstudentsQuery.dataとすれば良さそうです。
import { useQuery } from "@tanstack/react-query";
import React from "react";
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
const studentsQuery = useQuery({
queryKey: ["students"],
queryFn: () => {
return [...studentsData];
},
});
// ここを変更
console.log(studentsQuery.data);
return (
<div>
</div>
);
};
export default UseQuery;
データがコンソールに表示されていますね。
dataの次によく使うプロパティはstatusです、基本的にデータ取得は非同期処理で実行するためPromiseオブジェクトのように「成功か失敗か」を返す仕組みになっています。
useQueryではstatusに値が入っています。
import { useQuery } from "@tanstack/react-query";
import React from "react";
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
const studentsQuery = useQuery({
queryKey: ["students"],
queryFn: () => {
return [...studentsData];
},
});
// ここを追加
console.log(studentsQuery.status);
console.log(studentsQuery.data);
return (
<div>
</div>
);
};
export default UseQuery;
「pending」「success」のようなキーワードがコンソールに表示されるようになりましたね。
注目して欲しいのが「success」になった後にdataの中身が表示されている点です。
ちなみにネット回線の不具合などのトラブル時には「error」と言う値になります。
・ページを開いた直後
・特定のボタンをクリックしたとき
・5分以上の時間が経過したとき
など自分がどのタイミングでデータを取得したいかを考えてstatusを使ってデータ取得前と後で画面に表示する内容を考えることになるでしょう。
例えば以下のような感じです。
import { useQuery } from "@tanstack/react-query";
import React from "react";
const studentsData = [
{
id: "0001",
name: "Yamada",
age: 20,
},
{
id: "0002",
name: "Tanaka",
age: 21,
},
{
id: "0003",
name: "Yoshida",
age: 22,
},
];
const UseQuery = () => {
const studentsQuery = useQuery({
queryKey: ["students"],
queryFn: () => {
return [...studentsData];
},
});
// ここを追加
if (studentsQuery.status === "pending") return <p>ローディング中・・・</p>;
if (studentsQuery.status === "error") return <p>エラー!</p>;
return (
// ここを追加
<div>
{studentsQuery.data.map((student) => (
<p key={student.id}>{student.name}</p>
))}
</div>
);
};
export default UseQuery;
ネット回線が遅いときは一瞬「ローディング中。。。」と言うテキストが表示されました。
このようにstatusと組み合わせてデータ取得を行うのはuseQueryに限らず他の文法でも使う考え方なので初心者の方も「データ取得にはタイムラグがある」と覚えておきましょう。
ReactQueryのuseMutationの使い方
先ほどのuseQueryはデータ取得のためのフックであったのに対して、データ作成や更新にはuseMutationと言うフックがあります。
こちらもフックなのでインポートして初期値を設定するところから始まります。
useMutationについてもいろんなプロパティがありますがmutationFnというプロパティのみ必須項目になります。
// ここを追加
import { useMutation } from "@tanstack/react-query";
// ここを追加
import React from "react";
const UseMutation = () => {
const taskMutation = useMutation({
mutationFn: () => {
}
});
return (
<div>
</div>
);
};
export default UseMutation;
今回は入力フォームを用意してフォームに入力されたものを取得してみたいと思います。
なお入力フォームの値取得にはReactの基本フックであるuseRefを使いましょう。
先に定数taskMutationをコンソールログで表示させてみます。
import { useMutation } from "@tanstack/react-query";
// ここを追加
import React, { useRef } from "react";
const UseMutation = () => {
// ここを追加
const taskRef = useRef();
const taskMutation = useMutation({
mutationFn: () => {
return taskRef.current.value;
}
});
console.log(taskMutation);
// ここを追加
return (
<div>
<p>Todo</p>
<input type="text" ref={taskRef} />
<button onClick={() => taskMutation.mutate(taskRef)}>作成</button>
<p>{taskMutation.data}</p>
</div>
);
};
export default UseMutation;
useMutationについてもオブジェクト型になっていてプロパティが用意されているのがわかります。
useQueryとは少し違う中身ですがオブジェクトのプロパティを指定することで処理の結果を取得できそうな感じですね。
mutationFnの処理後の結果はonSuccessと言うプロパティで設定することができます。
useRefで検知しているフォームの中身をコンソールに表示させてみます。
mutationFnで実行したreturnの中身(taskRef.current.value)はdataと言うプロパティでonSuccessの引数に渡します。
import { useMutation } from "@tanstack/react-query";
// ここを追加
import React, { useRef } from "react";
const UseMutation = () => {
const taskRef = useRef();
const taskMutation = useMutation({
mutationFn: () => {
return taskRef.current.value;
},
// ここを追加
onSuccess: (data) => {
console.log(data);
},
});
console.log(taskMutation);
return (
<div>
<p>Todo</p>
<input type="text" ref={taskRef} />
<button onClick={() => taskMutation.mutate(taskRef)}>作成</button>
<p>{taskMutation.data}</p>
</div>
);
};
c
export default UseMutation;
入力フォームに文字を入力してボタンをクリックするとコンソールに文字が表示されました。
またtaskMutationのオブジェクトのなかにあるdataプロパティの値にも代入されていることがわかります。
useQueryの時のdataとは少し意味が違いますが、dataとすれば入力内容が取得できるようになっています。
useQueryとuseMutationともに「別にわざわざ使わなくても良くない」と思われた方がいるかもしれません。
今回やってみた処理内容であればuseStateなどの基本のフックだけで実装できちゃいます。
最近ReactQueryが流行っている理由としては「より簡潔に書くことができる」というものが多いです。
statusやdataなど事前に必要な情報はプロパティを指定すれば取得できるのは初期のReactプロジェクトに比べると便利になっているわけです。
例えばstatusと指定するだけで進行状況を取得できるわけですが、最初のころは自分で作っていた時代がありました。
またデータについても自分で空の配列を用意して、そこに格納するように関数を作る必要があったのです。
以下はTodoアプリを作っているコードです。
import React, { useSate, useEffect } from "react";
const WrongUseEffect = () => {
const [todos, setTodos] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const fetchTodos = async () => {
try {
setIsLoading(true);
console.log("取得中");
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
if (res.ok) {
const results = await res.json();
setTodos(results);
} else {
setIsError(new Error(res.statusText));
}
} catch (error) {
setIsError(error);
}
setIsLoading(false);
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
{isLoading ? (
<p>ローディング中。。。</p>
) : (
<div>
{todos.map((todo) => (
<p key={todo.id}>{todo.title}</p>
))}
</div>
)}
</>
);
};
export default WrongUseEffect;
データはtodoと言う定数にして初期値を空の配列として宣言します。
またローディング状況もisLoadingという定数を自分で作って、必要な場所にsetLoading(true)と書いていました。
さらに自分で定数の名前を決めるのでチーム内で命名規則のようなルールを周知しておかないと読みにくいコードが出来上がるリスクがあります。
ReactQueryを使用することで、それらの作業を簡易的な方法に置き換えることができます。
また小規模なプロジェクトであればReduxの代わりとして状態管理の役割として使用することもできます。
そのあたりはまた別の機会に共有しようと思いますが、まずはReactQueryで書くことで自分なりに使い勝手を体験しておくことをオススメします。
ReactQueryでキャッシュを指定する
ReactQueryを使う大きな特徴にキャッシュの制御が挙げられます。
通常キャッシュは自作すると大変なのですがReactQueryを使用すれば簡単な文法でコントロールできるので大変便利です。
index.jsに戻りましょう。
import React from "react";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
// ここを変更する
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
reportWebVitals();
new QueryClientしたときにdefaultOptionsという初期設定のようなものを書くことができて、staleTimeというプロパティで「更新かけるまでの時間」を指定することができます。
通常は0秒になっていてレンダリングが行われるとバックグラウンドで新しいデータを取り込んで反映してくれます。
それだけでも便利ですが、あえて5秒待たせるとすればリアルタイムにキャッシュ更新が行われないようになります。
キャッシュを常にリアルタイムで更新しないメリットとしては、古い状態を使い回すことになるのでデータの通信回数が抑えられることです。
動画データなど重たいデータを扱うサイトで常にキャッシュを更新しているとユーザーのネット回線によってはモッサリした動作になることがあります。
そもそもキャッシュの常に更新すべきか?という問いは運営しているサイトによりますので状況に応じて対応していきましょう。
useQueryフックのplaceholderDataを使ってキャッシュ処理
ReactQueryは大変便利なライブラリでuseQueryフックの機能にもキャッシュ処理に使用できるものがあります。
useQueryの中のプロパティでplaceholderDataというものがあり、こちらに指定した値やテキストがローディング時の最初に優先的に表示されるようになります。
つまり処理の重たいコンポーネント内で真っ白な画面を数秒も表示させずに済む訳ですね。
import { useQuery } from '@tanstack/react-query';
import React from 'react';
const studentsData = [
{
id: '0001',
name: 'Yamada',
age: 20,
},
{
id: '0002',
name: 'Tanaka',
age: 21,
},
{
id: '0003',
name: 'Yoshida',
age: 22,
},
];
const PlaceHolderData = () => {
const studentsQuery = useQuery({
queryKey: ['students'],
queryFn: () => {
return [...studentsData];
},
placeholderData: [
{
id: '0000',
name: 'Sample',
age: 99,
},
],
});
console.log(studentsQuery.data);
if (studentsQuery.status === 'pending') return <p>ローディング中・・・</p>;
if (studentsQuery.status === 'error') return <p>エラー!</p>;
return (
<>
<div>
{studentsQuery.data.map((student) => (
<p key={student.id}>{student.name}</p>
))}
</div>
</>
);
};
export default PlaceHolderData;
studentsDataという学生情報をオブジェクト形式にしてあるデータを一覧で表示するものです。
本来はAPIやデータベースから取得しますが擬似的に再現しています。
上記のコードだと3件の登録ですが1000件などになってくると数秒間の遅延が発生する訳です。
こちらに対してplaceholderDataでサンプルのデータを表示させるものとします。
コンソールでデータの取得状況を見てみます。
上図のように最初はサンプルのデータのみが取得されていて、2回目に正式にデータの一覧が取得されているのがわかります。
例えば回線の遅い環境で重たい画面を表示させる際には、ユーザーから見ると時間の経過によって表示される内容や件数が変わることになります。
placeholderDataはそのようなケースに効果的で仕組みとしてはキャッシュを利用しています。
結局のところキャッシュはどこで管理されているのか?
キャッシュと聞くとブラウザの機能を思い浮かべる方が多いかと思いますが、ReactQueryでは何がキャッシュをコントロールしているのでしょうか?
結論としてはindex.jsでインスタンス化したQueryClientになります。
import React from "react";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
reportWebVitals();
そしてインスタンス化したqueryClientという定数をQueryClientProviderコンポーネントにclientという名前のpropsとして渡しています。
QueryClientProviderコンポーネントはApp.jsをラップしているので、これでプロジェクト全体でpropsであるclient(queryClient)を使用できるようになっています。
また各コンポーネントファイルでキャッシュの情報を取るにはuseQueryClientというフックを使用します。
import { useQuery, useQueryClient } from '@tanstack/react-query';
import React from 'react';
const studentsData = [
{
id: '0001',
name: 'Yamada',
age: 20,
},
{
id: '0002',
name: 'Tanaka',
age: 21,
},
{
id: '0003',
name: 'Yoshida',
age: 22,
},
];
const PlaceHolderData = () => {
// useQueryClientを使ってキャッシュ情報を取得
const queryClient = useQueryClient();
console.log(queryClient);
const studentsQuery = useQuery({
queryKey: ['students'],
queryFn: () => {
return [...studentsData];
},
placeholderData: [
{
id: '0000',
name: 'Sample',
age: 99,
},
],
});
if (studentsQuery.status === 'pending') return <p>ローディング中・・・</p>;
if (studentsQuery.status === 'error') return <p>エラー!</p>;
return (
<>
<div>
{studentsQuery.data.map((student) => (
<p key={student.id}>{student.name}</p>
))}
</div>
</>
);
};
export default PlaceHolderData;
上図がコンソールの表示結果になっていてオブジェクトがあるはずです。
useQueryClientフックは現在表示しているコンポーネントにおけるQueryClientのインスタンスを取得するための特別なフックです。
index.jsでインスタンス化したQueryClientはコンソールにあるようなオブジェクトとして存在しています。
またキャッシュを指定した場合にはdefaultOptionsというプロパティにstaleTimeが表示されます。
ReactQueryはいわゆるReduxやRecoilのような状態管理ライブラリの機能を持っているわけです。
useStateのように各コンポーネントで状態を持つのだと同じようなステートをいろんなファイルで持ったりpropsで何回も渡すことになります。
そこで一つの場所にまとめて管理して使用したいコンポーネントで適宜呼び出していくスタイルはReduxという有名なライブラリを使うことが多いですが、ReactQueryもその一端を担うライブラリになっています。
そのためReduxを使わずにReactQueryを使って状態管理するプロジェクトも増えています。
ReduxやRecoilではキャッシュまでは管理できなかったのでReactQueryが注目されています。
1点だけ注意なのがuseQueryClientフックを使ってQueryClientのインスタンスを取得するときの定数名はindex.jsでインスタンス化したときの名前と揃える必要があります。
index.jsで定数名をqueryClientとしていて、コンポーネントでは例えばuseQueryClientのように違う定数名を使ってみます。
import { useQuery, useQueryClient } from '@tanstack/react-query';
import React from 'react';
const studentsData = [
{
id: '0001',
name: 'Yamada',
age: 20,
},
{
id: '0002',
name: 'Tanaka',
age: 21,
},
{
id: '0003',
name: 'Yoshida',
age: 22,
},
];
const PlaceHolderData = () => {
// useQueryClientという定数名に変更
const useQueryClient = useQueryClient();
console.log(useQueryClient);
const studentsQuery = useQuery({
queryKey: ['students'],
queryFn: () => {
return [...studentsData];
},
placeholderData: [
{
id: '0000',
name: 'Sample',
age: 99,
},
],
});
if (studentsQuery.status === 'pending') return <p>ローディング中・・・</p>;
if (studentsQuery.status === 'error') return <p>エラー!</p>;
return (
<>
<div>
{studentsQuery.data.map((student) => (
<p key={student.id}>{student.name}</p>
))}
</div>
</>
);
};
export default PlaceHolderData;
定数名をコンポーネントで使いやすい名前で変更すると上図のようなエラーになります。
これはindex.jsでインスタンス化したものとは違うQueryClientを探してしまっているからです。
実際にやる人はいませんがクラスとインスタンスは複数実行できますよね。
そこでそれぞれは定数名で判別することを人間がするようにReactQueryも定数名でインスタンスを探しているのです。
index.jsでインスタンス化したときの定数名はコンポーネントでも同じ定数名でuseQueryClientを実行するように注意してください。
ReactQueryとuseEffectの違い
ここまでReactQueryの基本的な使い方を紹介しましたが最後に基本的なReactフックとの書き方の違いを比較しておこうと思います。
ReactQueryをわざわざ使わなくても本記事で書いたコードくらいであればuseEffectなど基本のフックだけでも対応できるからです。
例えばAPIからデータを取得する例です。
ReactQueryを使わない場合の書き方
import React, { useEffect, useState } from "react";
const WrongUseEffect = () => {
const [todos, setTodos] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const fetchTodos = async () => {
try {
setIsLoading(true);
console.log("取得中");
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
if (res.ok) {
const results = await res.json();
console.log("完了");
setTodos(results);
console.log(results);
} else {
setIsError(new Error(res.statusText));
}
} catch (error) {
setIsError(error);
}
setIsLoading(false);
};
useEffect(() => {
fetchTodos();
}, []);
return (
<>
{isLoading ? (
<p>ローディング中。。。</p>
) : (
<div>
{todos.map((todo) => (
<p key={todo.id}>{todo.title}</p>
))}
</div>
)}
</>
);
};
export default WrongUseEffect;
ReactQueryを使う場合の書き方
import { useQuery } from "@tanstack/react-query";
import React, { useEffect } from "react";
const UseReactQuery = () => {
console.log("render");
const { data, error, isLoading } = useQuery({
queryKey: ["todos"],
queryFn: () =>
fetch("https://jsonplaceholder.typicode.com/todos").then((res) =>
res.json()
),
});
useEffect(() => {
console.log("mount");
}, []);
if (error) {
console.log(error);
}
return (
<>
{isLoading ? (
<p>ローディング中。。。</p>
) : (
<div>
{data.map((todo) => (
<p key={todo.id}>{todo.title}</p>
))}
</div>
)}
</>
);
};
export default UseReactQuery;
書き方は個人の好みもありますがReactQueryを使った方がコンパクトに書けるケースが多いです。
また状態変数もReactQuery側で「data」「isLoading」など最初からキーワードが決まっているのでチーム内での命名規則の統一をする必要がありません。
加えて先ほど紹介したキャッシュの制御も含めて、総合的にReactQueryの方が便利に感じて使っているエンジニアが増えているのが昨今のトレンドです。
書き方やライブラリは所属するチームによって変わるわけですが、いろんなパターンを知っておくことはエンジニアである以上は大切ですのでぜひ練習してみてください。
またkindleをお持ちの方は以下よりダウンロードしていただくと復習に便利です。
Kindleで保存する