サイトトップ

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

HTML5テクニカルノート

Create React App フックによる状態管理 02: useContextでコンテクストを使う


Create React App フックによる状態管理 01: 基本のuseStateを使う」では、基本のフックuseStateで状態をコンポーネントにもたせました。親コンポーネントに状態をまとめれば、子コンポーネントから参照することはできます。けれど、できれば状態やロジックは、要素の表示やインタフェースとは分けたいところです。useContextフックを用いれば、この切り分けができます。作例は引き続きカウンターです(図001)。前回書いたコード(サンプル002)に手を加えていきましょう。

図001■動きは前回と変わらないカウンター

図001

01 createContextとuseContextを使う

コンテクストは、まずcreateContext()関数でつくらなければなりません。戻り値のコンテクストオブジェクトに状態が収められます。


import { createContext } from 'react';
const 変数 = createContext(状態の初期値);

そして、コンテクストオブジェクトに備わるのが、プロバイダ(Provider)コンポーネントです。JSXコードでこのプロバイダコンポーネントに含めた子コンポーネントが、コンテキストの状態を参照できます。子コンポーネントに参照させる値は、プロバイダのvalueプロパティに加えてください。


<コンテキスト.Provider value={/* 子コンポーネントに与える参照 */}>
	<!-- 子コンポーネント -->
</コンテキスト.Provider>

ロジック分けの前に、createContext()の使い方を理解しましょう。コンテキストは、アプリケーションモジュール(src/App.js)からつぎのように関数を呼び出してつくります。状態の参照はプロバイダコンポーネントからvalueプロパティで渡しますので、カウンターコンポーネント(CounterDisplay)のプロパティはもう要りません。

src/App.js

// import React, { useState } from 'react';
import React, { createContext, useState } from 'react';

export const AppContext = createContext({ count: initialCount });
function App() {

	return (
		<AppContext.Provider value={{ count, reset, decrement, increment }}>
			<div className="App">
				{/* <CounterDisplay
					count={count}
					reset={reset}
					decrement={decrement}
					increment={increment}
				/> */}
				<CounterDisplay />
			</div>
		</AppContext.Provider>
	);
}

これでようやく、useContextフックが使えるようになりました。アプリケーション(App)からコンテキスト(AppContext)をカウンターのコンポーネント(src/CounterDisplay.js)にimportして呼び出すと、返されるのがプロバイダから与え得られたvalueプロパティの現在値を収めたオブジェクトです。

src/CounterDisplay.js

// import React from 'react';
import React, { useContext } from 'react';
import { AppContext } from './App';

	// const CounterDisplay = ({ count, reset, decrement, increment }) => {
	const CounterDisplay = () => {
		const { count, reset, decrement, increment } = useContext(AppContext);

};

コンテキストはアプリケーションモジュール(src/App.js)につくったので、まだロジックは分離されていません。でも、これで切り分ける準備は整ったということです。カウンターのモジュールの記述とともに、つぎのコード001にまとめます。併せて、CodeSandboxにサンプル001を公開しました。

コード001■createContextとuseContextでコンポーネントツリー内の状態を共有する

src/App.js

import React, { createContext, useState } from 'react';
import CounterDisplay from './CounterDisplay';
import './App.css';

const initialCount = 0;
export const AppContext = createContext({ count: initialCount });
function App() {
	const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const decrement = () => setCount((prevCount) => prevCount - 1);
	const increment = () => setCount((prevCount) => prevCount + 1);
	return (
		<AppContext.Provider value={{ count, reset, decrement, increment }}>
			<div className="App">
				<CounterDisplay />
			</div>
		</AppContext.Provider>
	);
}

export default App;

src/CounterDisplay.js

import React, { useContext } from 'react';
import { AppContext } from './App';

const CounterDisplay = () => {
	const { count, reset, decrement, increment } = useContext(AppContext);
	return (
		<>
			Count: {count}
			<button onClick={reset}>Reset</button>
			<button onClick={decrement}>-</button>
			<button onClick={increment}>+</button>
		</>
	);
};

export default CounterDisplay;

サンプル001■React state management 02-01: createContext and useContext

02 カスタムのコンテキストコンポーネントにロジックを移す

いよいよ、ロジックをアプリケーションモジュール(src/App.js)から、新たなコンテキストのコンポーネントに移します。ロジックとコンテキスト生成のコードは、コンポーネントから除いてください。

src/App.js

// import React, { createContext, useState } from 'react';
import React from 'react';

// const initialCount = 0;
// export const AppContext = createContext({ count: initialCount });
function App() {
	/* const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const decrement = () => setCount((prevCount) => prevCount - 1);
	const increment = () => setCount((prevCount) => prevCount + 1); */

}

コンテキストの新たなコンポーネント(src/AppContext.js)の記述はつぎのコード002のとおりです。ロジックは、アプリケーション(App)からほぼそのまま移せば構いません。ひとつ大事なのは、子コンポーネントが含められるようにプロバイダを返すことです。そのために、プロパティchildrenを受け取り、子要素として加えました。

コード002■ロジックを分けたカスタムコンテキストコンポーネント

src/AppContext.js

import React, { createContext, useState } from 'react';

const initialCount = 0;
export const AppContext = createContext({ count: initialCount });
export const AppProvider = ({ children }) => {
	const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const decrement = () => setCount((prevCount) => prevCount - 1);
	const increment = () => setCount((prevCount) => prevCount + 1);
	return (
		<AppContext.Provider value={{ count, reset, decrement, increment }}>
			{children}
		</AppContext.Provider>
	);
};

改めて、アプリケーションモジュール(src/App.js)は、新たなコンテキストコンポーネント(AppContext')のプロバイダ(AppProvider)で、子コンポーネントを包みます。カウンターのコンポーネント(src/CounterDisplay.js)も、useContextに渡すコンテキスト(名前は同じAppContextにしました)を差し替えてください。

src/App.js

import { AppProvider } from './AppContext';

function App() {

	return (
		// <AppContext.Provider value={{ count, reset, decrement, increment }}>
		<AppProvider>
			<div className="App">

			</div>
		{/* </AppContext.Provider> */}
		</AppProvider>
	);
}

src/CounterDisplay.js

// import { AppContext } from './App';
import { AppContext } from './AppContext';

	const CounterDisplay = () => {
		const { count, reset, decrement, increment } = useContext(AppContext);

}

これで、ロジックはコンテキストのコンポーネント(AppContext)に切り分けられました(前掲コード002)。つぎのコード003のとおり、アプリケーションモジュール(src/App.js)はプロバイダコンポーネント(AppProvider)をただ差し込み、カウンターのコンポーネント(src/CounterDisplay.js)は前掲コード001と変わらずコンテキストから与えられた参照を使うだけです。それぞれのモジュールの記述は、CodeSandboxに掲げた以下のサンプル002でお確かめください。

コード003■ロジックをカスタムコンテキストコンポーネントに分けたアプリケーションとカウンターコンポーネント

src/App.js

import React from 'react';
import { AppProvider } from './AppContext';
import CounterDisplay from './CounterDisplay';
import './App.css';

function App() {
	return (
		<AppProvider>
			<div className="App">
				<CounterDisplay />
			</div>
		</AppProvider>
	);
}

export default App;

src/CounterDisplay.js

import React, { useContext } from 'react';
import { AppContext } from './AppContext';

	const CounterDisplay = () => {
		const { count, reset, decrement, increment } = useContext(AppContext);
		return (
			<>
				Count: {count}
				<button onClick={reset}>Reset</button>
				<button onClick={decrement}>-</button>
				<button onClick={increment}>+</button>
			</>
		);
};

export default CounterDisplay;

サンプル002■React state management 02-02: Creating provider component


作成者: 野中文雄
作成日: 2020年11月04日


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