HTML5テクニカルノート
Create React App フックによる状態管理 02: useContextでコンテクストを使う
- ID: FN2011002
- Technique: ECMAScript 2015
- Library: React 17.0.1
「Create React App フックによる状態管理 01: 基本のuseStateを使う」では、基本のフックuseState
で状態をコンポーネントにもたせました。親コンポーネントに状態をまとめれば、子コンポーネントから参照することはできます。けれど、できれば状態やロジックは、要素の表示やインタフェースとは分けたいところです。useContext
フックを用いれば、この切り分けができます。作例は引き続きカウンターです(図001)。前回書いたコード(サンプル002)に手を加えていきましょう。
図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;
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.jssrc/CounterDisplay.jsimport { AppProvider } from './AppContext'; function App() { return ( // <AppContext.Provider value={{ count, reset, decrement, increment }}> <AppProvider> <div className="App"> </div> {/* </AppContext.Provider> */} </AppProvider> ); }
// 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;
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.