
- https://fumiononaka.com/samples/jsfes_2022.html
- はなし家、もの書き、コード書き(webフロントエンド)。
- ■Twitter: @FumioNonaka / Qiita: FumioNonaka
JavaScript Fes July 2022
React + TypeScript: Apollo Clientのリアクティブな変数で複数コンポーネント間の状態を管理する
お品書き
00 Apollo Clientとは
Apollo ClientはReactで使える状態管理ライブラリ。
- ローカルとリモートのデータをGraphQLで扱える。
- 今回はGraphQLのクエリは使わない。
- ローカルの状態を複数コンポーネントの間でどう共有するか。
- リアクティブな変数(「Reactive variables」)を使う。
データを扱う技術としてGraphQLと並んで注目されている。
- グラフ「データ層」
- JavaScriptに興味がある世界のIT技術者約2万4000人に対して2020年に集めたアンケート結果。
- 満足度や興味はGraphQLにつぐ2位。
- 利用度と認知度は定番Reduxが上。
- GraphQLと合わせて使うなら最強のライブラリ。
01 useStateで状態をもつ簡単なカウンター作例
まずは、Apollo Clientは用いることなく、useState
で状態をもつ簡単なカウンターの作例(サンプル001)。
React構文メモ
useState
フック
関数コンポーネントで状態を保持・管理する。
- 引数: 状態変数の初期値。
- 戻り値: [状態変数, 設定関数]の配列。
useCallback
フック
メモ化(キャッシュ)されたコールバックを返す。
- 引数: コールバック関数。
- 戻り値: メモ化されたコールバック関数。
サンプル001■React + TypeScript: Using reactive variables of Apollo Client 01
ノート01■React 18から関数コンポーネントはReact.FunctionComponent
(React.FC
)で型づける
関数コンポーネントのTypeScrptによる型づけは、React 18からはReact.FunctionComponent
(React.FC
)を用いることになった(「関数コンポーネント」参照)。
React.VoidFunctionComponent
(React.VFC
)は推奨されない。
02 ボタンと数値表示をモジュール分けする
今回のお題は複数コンポーネント間で、ひとつの状態をどう扱うか。コンポーネントは、つぎのように分ける。
CounterNumber
: カウンターの数値を表示するコンポーネントCounterButton
: カウンター増減のボタン- 加算(+)と減算(-)のふたつで使い回す。
親コンポーネント(CounterDisplay
)がカスタムフックから受け取った状態変数(count
)と数値増減の関数(setCounter
)は、子コンポーネントにプロパティとして渡す。
- Reactではお約束のかたち。
- まだ、Apollo Clientは使わない。
import { CounterNumber } from './CounterNumber';
import { CounterButton } from './CounterButton';
export const CounterDisplay: FC = () => {
return (
<div>
{/* <button onClick={() => setCounter(-1)}>-</button> */}
<CounterButton setCounter={setCounter} step={-1} text="-" />
{/* <span>{count}</span> */}
<CounterNumber count={count} />
{/* <button onClick={() => setCounter(1)}>+</button> */}
<CounterButton setCounter={setCounter} step={1} text="+" />
</div>
);
};
子コンポーネントは、親からプロパティとして受け取った変数や関数をJSXに組み込む。
src/CounterNumber.tsximport { FC } from 'react';
type Props = {
count: number;
};
export const CounterNumber: FC<Props> = ({ count }) => {
return <span>{count}</span>;
};
import { FC } from 'react';
type Props = {
setCounter: (step: number) => void;
step: number;
text: string;
};
export const CounterButton: FC<Props> = ({ setCounter, step, text }) => {
return <button onClick={() => setCounter(step)}>{text}</button>;
};
標準Reactの作法にしたがって、コンポーネント間で状態を受け渡した。
サンプル002■React + TypeScript: Using reactive variables of Apollo Client 02
03 各コンポーネントからカスタムフックを呼び出そうとすると
親モジュール(src/CounterDisplay.tsx
)からカスタムフック(useCounter
)の呼び出しを外す。
- 状態をもたないので、子コンポーネントにも渡せない。
// import { useCounter } from './useCounter';
import { CounterNumber } from './CounterNumber';
import { CounterButton } from './CounterButton';
export const CounterDisplay: FC = () => {
// const { count, setCounter } = useCounter();
return (
<div>
{/* <button onClick={() => setCounter(-1)}>-</button> */}
<CounterButton step={-1} text="-" />
{/* <CounterNumber count={count} /> */}
<CounterNumber />
{/* <CounterButton setCounter={setCounter} step={1} text="+" /> */}
<CounterButton step={+1} text="+" />
</div>
);
};
カスタムフック(useCounter
)は、子コンポーネントがそれぞれ呼び出して、状態を取得・設定する。
import { useCounter } from './useCounter';
/* type Props = {
count: number;
}; */
// export const CounterNumber: FC<Props> = ({ count }) => {
export const CounterNumber: FC = () => {
const { count } = useCounter();
};
import { useCounter } from './useCounter';
type Props = {
// setCounter: (step: number) => void;
};
// export const CounterButton: FC<Props> = ({ setCounter, step, text }) => {
export const CounterButton: FC<Props> = ({ step, text }) => {
const { setCounter } = useCounter();
};
カスタムフックから得た状態は、呼び出したコンポーネントごとに閉じている。
- 加算・減算のボタンをクリックしても、それぞれのコンポーネントの状態変数(
count
)が増減するだけ。 - カウンターの数値表示コンポーネントの状態は変わらない。
サンプル003■React + TypeScript: Using reactive variables of Apollo Client 02 (not worked)
04 Apollo Clientのリアクティブな変数で状態を扱う
カスタムフックのモジュール(src/useCounter.ts
)のみ書き改める。
- 前掲の3つのモジュールの記述はそのまま変えない。
src/CounterDisplay.tsx
src/CounterNumber.tsx
src/CounterButton.tsx
Apollo Clientでは、アプリケーションで共有する状態を、ひとつひとつリアクティブな変数として定める(「Reactive variable」)。
- 状態を一括管理するReduxと異なる。
構文001■Apollo Clientのメソッドとフック
makeVar
メソッド
リアクティブな変数をつくって初期化する。
- 引数: 変数の初期値。
- 戻り値: 変数値を設定する関数。
const 設定関数 = makeVar(初期値);
useReactiveVar
フック
リアクティブな状態変数の参照を得る。
- 引数:
makeVar
から返された設定関数。 - 戻り値: 変数の参照。
const 状態変数 = useReactiveVar(設定関数);
カスタムフック(useCounter
)の外でmakeVar
によるリアクティブな変数の初期化は行う。
- フックがどこから呼び出されようと、モジュールが参照する変数はひとつ。
- コンポーネント間で変数の参照が共有化される。
フックが返すのは、状態変数(count
)とカウンターの設定関数(setCounter
)のまま、変わらない。
// import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import { makeVar, useReactiveVar } from '@apollo/client';
const countVar = makeVar(0);
export const useCounter = () => {
// const [count, setCount] = useState(0);
const count = useReactiveVar(countVar);
// const setCounter = useCallback((step: number) => setCount(count + step), [
const setCounter = useCallback((step: number) => countVar(count + step), [
count
]);
};
ノート02■graphql
を依存に含める
graphql
は、クエリを使わなくても依存に加える。graphql
のバージョンを16以上にするとTypeErrorが出てしまう(CodeSandbox)。
サンプル004■React + TypeScript: Using reactive variables of Apollo Client 03
05 リアクティブな変数を汎用的なカスタムフックでつくる
汎用的なカスタムフックを定めると、リアクティブな変数が統一的に扱える。
Apollo Clientを使うと、状態が変数ごとに切り分けられ、しかもモジュール間で共有できる。
- Reduxのように大掛かりにならない。
- GraphQLも使う場合にはイチオシ!
作成者: 野中文雄
作成日: 2022年07月14日
Copyright © 2001-2020 Fumio Nonaka. All rights reserved.