Reactive variables【Apollo Client】による local state 管理

f:id:stmn_mmoto:20210630180557p:plain

はじめに

はじめまして、株式会社スタメンのミツモトです。 普段は、REST API をベースに React + Redux で開発しています。

最近になり開発者体験の向上、プロジェクトのスピードアップという観点から、社内でスキーマファーストな開発に関心が集まっています。そういった背景もあり弊社でも GraphQL の勉強会を始めました。今後のプロジェクトにおいて GraphQL を導入する可能性があるため皆で学んでいます。

React ✕ GraphQL という技術を採用する場合、client 側の状態管理ライブラリとして Apollo Client が挙げられます。

API のレスポンスを正規化かつキャッシュしてくれる Apollo Client の inMemoryCache の恩恵は大きく、開発者だけでなくユーザーにおいても画面の表示速度向上などのメリットがあります。

Apollo Client で API のレスポンスを状態管理する一方で、client 側だけで状態を持ち、どのコンポーネントからも参照・更新したいというニーズもあるかと思います。

Apollo Client 3から Reactive variables という機能が追加され、local state 管理が簡単にできるようになりました。 今回はその話をさせていただきます。

Reactive variablesとは

Reactive variables は Apollo Client のキャッシュとは別で local state を管理するのに役立つ仕組みです。cacheと分かれていることにより、どんな型・構造のデータも保持することができ、 アプリケーションのどこからでも参照・更新ができます。

参考:Reactive variables

コンポーネント内で閉じる local state であれば React 公式の useState を利用できますが、状態を複数のコンポーネントで利用したい場合に別の状態管理方法を考える必要があります。そんな時に Reactive variables が利用できます。

実装方法

アプリケーション全体にかかわるテーマ(theme)を設定・反映する実装方法を例にご紹介します。

1. Reactive variables の定義

まず始めに themeVar という Reactive variables を定義します。

import { makeVar } from "@apollo/client";

export const initialTheme = 'light';
export const themeVar = makeVar(initialTheme);

以下のように値を更新できます。

themeVar('dark');
console.log(themeVar()) // => 'dark'

themeVar() でその時の状態を呼び出せますが、コンポーネントの再描画を行うため useQuery または useReactiveVar を用います。(後述します)

2. typePolicy、typeDefs の設定

GraphQLのクエリとして theme を扱うため、 ApolloClient インスタンスのオプションに typePolicy(inMemoryCacheの引数)、typeDefs を設定します。

export const typePolicies = {
  typePolicies: {
    Query: {
      fields: {
        theme: { read() { return themeVar(); }} // Query の theme フィールドとして themeVar を返す
      }
    }
  }
};
import { gql } from "@apollo/client";

export const typeDefs = gql`
  extend type Query {
    theme: String
  }
`
import React from "react";
import ReactDOM from "react-dom";

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import { typePolicies } from "./typePolicy";
import { typeDefs } from "./typeDefs";

import App from "./App";

const client = new ApolloClient({
  cache: new InMemoryCache(typePolicies),
  connectToDevTools: true,
  typeDefs,
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

3. コンポーネントでの呼び出し

theme の表示確認用に ThemeView コンポーネントを定義します。 useQueryを用いて状態を参照するため、合わせてクエリを定義しています。

import React, { FC } from "react";
import styled from "styled-components";
import { gql,  useQuery }from "@apollo/client";

export const GET_THEME = gql`
  query getTheme {
    theme @client
  }
`;

// themeの表示確認用コンポーネント
const ThemeView: FC = () => {
  const { theme } = useQuery(GET_THEME).data;

  return (
    <Div theme={theme}>
      {theme}
    </Div>
  );
};

const Div = styled.div<{ theme: string }>`
  background-color: ${props => props.theme === 'light' ? 'white' : 'darkgray' };
`;

themeを切り替えるための ThemeSelectコンポーネントを定義します。

import React, { FC, ChangeEventHandler } from "react";
import { themeVar } from "../themeVar";

// themeを切り替えるコンポーネント
const ThemeSelect: FC = () => {
  
  const handleChangeTheme: ChangeEventHandler<HTMLSelectElement> = (e) => {
    themeVar(e.target.value);
  };
  
  return (
    <select onChange={handleChangeTheme}>
      <option value="light">light</option>
      <option value="dark">dark</option>
    </select>
  );
};

プルダウンで theme を選択することで、表示確認用のThemeViewコンポーネントの色を変更できます。 このように useQuery を用いて状態を取得できますが、useReactiveVar を利用すればよりシンプルに書くことができます。

4.【番外編】useReactiveVarを用いた実装

useReactiveVar は Apollo Client 3.2 から導入されました。こちらを利用すると GraphQLのクエリを書かずに状態を参照できます。

useQueryで呼び出していた部分が、useReactiveVar に置き換わります。

import React, { FC } from "react";
import styled from "styled-components";
import { useReactiveVar } from "@apollo/client";
import { themeVar } from "../themeVar";

// themeの表示確認用コンポーネント
const ThemeView: FC = () => {
  const theme = useReactiveVar(themeVar);

  return (
    <Div theme={theme}>
      {theme}
    </Div>
  );
};
...

useReactiveVar を利用すると、ApolloClientインスタンスの typePolicy、typeDefs の設定も不要になります。シンプルに書けるようになりますが、デメリットとして Apollo Client Devtools で状態を確認できなくなることが挙げられます。( 確認方法を知っている方、是非教えてほしいです!)

おわりに

今回の記事では Apollo Client で local state 管理をする方法として Reactive variables をご紹介させていただきました。 アプリケーション全体にかかわるテーマの例を挙げましたが、それ以外のUIに関わる状態であったり、ドラッグ&ドロップでコンポーネント間を跨ぐ処理を実装する際に Reactive variables は利用できます。気になった方はぜひお試しください!

スタメンでは一緒にプロダクト開発を進めてくれる仲間を募集しています! 興味を持っていただいた方は、是非下記の募集ページを御覧ください。