サイトトップ

Director Flash 書籍 業務内容 プロフィール
FumioNonaka

JavaScript Fes July 2022

React + TypeScript: Apollo Clientのリアクティブな変数で複数コンポーネント間の状態を管理する

お品書き

00 Apollo Clientとは

Apollo ClientはReactで使える状態管理ライブラリ。

データを扱う技術として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)を用いることになった(「関数コンポーネント」参照)。

02 ボタンと数値表示をモジュール分けする

今回のお題は複数コンポーネント間で、ひとつの状態をどう扱うか。コンポーネントは、つぎのように分ける。

親コンポーネント(CounterDisplay)がカスタムフックから受け取った状態変数(count)と数値増減の関数(setCounter)は、子コンポーネントにプロパティとして渡す。

src/CounterDisplay.tsx
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.tsx
import { FC } from 'react';

type Props = {
	count: number;
};
export const CounterNumber: FC<Props> = ({ count }) => {
	return <span>{count}</span>;
};
src/CounterButton.tsx
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)の呼び出しを外す。

src/CounterDisplay.tsx
// 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)は、子コンポーネントがそれぞれ呼び出して、状態を取得・設定する。

src/CounterNumber.tsx
import { useCounter } from './useCounter';

/* type Props = {
	count: number;
}; */
// export const CounterNumber: FC<Props> = ({ count }) => {
export const CounterNumber: FC = () => {
	const { count } = useCounter();

};
src/CounterButton.tsx
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();

};

カスタムフックから得た状態は、呼び出したコンポーネントごとに閉じている。

サンプル003■React + TypeScript: Using reactive variables of Apollo Client 02 (not worked)

04 Apollo Clientのリアクティブな変数で状態を扱う

カスタムフックのモジュール(src/useCounter.ts)のみ書き改める。

Apollo Clientでは、アプリケーションで共有する状態を、ひとつひとつリアクティブな変数として定める(「Reactive variable」)。

構文001■Apollo Clientのメソッドとフック

makeVarメソッド

リアクティブな変数をつくって初期化する。

const 設定関数 = makeVar(初期値);
useReactiveVarフック

リアクティブな状態変数の参照を得る。

const 状態変数 = useReactiveVar(設定関数);

カスタムフック(useCounter)の外でmakeVarによるリアクティブな変数の初期化は行う。

フックが返すのは、状態変数(count)とカウンターの設定関数(setCounter)のまま、変わらない。

src/useCounter.ts
// 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
	]);

};

ノート02graphqlを依存に含める

サンプル004■React + TypeScript: Using reactive variables of Apollo Client 03

05 リアクティブな変数を汎用的なカスタムフックでつくる

汎用的なカスタムフックを定めると、リアクティブな変数が統一的に扱える。

Qiita「React + TypeScript: Apollo Clientのリアクティブな変数(reactive variables)で複数コンポーネント間の状態を管理する

Apollo Clientを使うと、状態が変数ごとに切り分けられ、しかもモジュール間で共有できる。


作成者: 野中文雄
作成日: 2022年07月14日


Copyright © 2001-2020 Fumio Nonaka.  All rights reserved.