コンテクスト – React
コンテクストは React の簡易なグローバル管理機能です。 props を経由せずに値を操作、反映できることによって多様な props のリレーを行うことなく各所で同一の値を扱えます。これを利用することで props リレーをすることなく関数一つで通知なりなんなりを呼び出せます。
デモは次です。
各ボタンコンポーネントでは props を用いていませんが、ボタンを押すとグローバル領域の通知用スナックバーに通知が追加されます。呼び出し側のソースコードは次です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 通知追加ボタンコンポーネント クラスコンポーネント版 import React from "react" ; import { NotifyMessagesContext } from "./NotifyMessagesContext.jsx" ; export default class ClassComponent extends React.Component { constructor(props, context) { // context を元クラスである React.Component に渡すとよしなにやってくれます super (props, context); this .pushMessage = () => { // 実行はこの context に引っ付いた関数を呼び出すのみ this .context.pushMessage( "クラスコンポーネントからの通知" ); }; } render() { return ( <div> <button className= "push-notify-btn" onClick={ this .pushMessage}> 通知追加 in クラスコンポーネント </button> </div> ); } } // どの context を使うかの宣言。これがないとクラスコンポーネントはコンテクストを使えません ClassComponent.contextType = NotifyMessagesContext; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 通知追加ボタンコンポーネント 関数コンポーネント版 import React from "react" ; import { useNotifyMessages } from "./NotifyMessagesContext.jsx" ; export default function FunctionComponent() { // import した useNotifyMessages から pushMessage を呼び出し const { pushMessage } = useNotifyMessages(); // ボタンを押したら pushMessage を文字列を引数にして呼び出すだけ return ( <div> <button className= "push-notify-btn" onClick={() => pushMessage( "関数コンポーネントからの通知" )} > 通知追加 in 関数コンポーネント </button> </div> ); } |
呼び出し側はコンテクストから一つの関数を呼び出し、それを実行しているのみです。グローバルなところから呼び出すため props のリレーも不要です。これだけで立派な通知になるので再利用が容易です。
これを実現しているコンテクストのコードは次です。 Matterial UI のスナックバーを用いていますが、原理的には他のコンポーネントでも可能です。
Snackbar API – Material-UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import React, { useContext, useState } from "react" ; import Snackbar from "@material-ui/core/Snackbar" ; import IconButton from "@material-ui/core/IconButton" ; import CloseIcon from "@material-ui/icons/Close" ; // どの様なコンテクストかを定義. // キーを持つメッセージ群 と メッセージを追加する関数 export const NotifyMessagesContext = React.createContext({ messages: {}, pushMessage: msg => {} }); // 各関数コンポーネントで使う export const useNotifyMessages = () => useContext(NotifyMessagesContext); // ルートコンポーネントで使う export const NotifyMessagesProvider = NotifyMessagesComponent; // メッセージのインデックス。関数コンポーネントでは保持できないので外部に定義 let msgIndex = 0; /** * スナックバーに送られてきたメッセージを表示する * @constructor */ function NotifyMessagesComponent(props) { // メッセージを state で持つ const [messages, setMessages] = useState({}); // メッセージの追加関数 const pushMessage = newMessage => { const newMsgIndex = msgIndex + 1; // 送られてきたメッセージに時刻の接頭辞をつける const formattedNewMessages = `[${ new Date().toISOString()}]:${newMessage}`; // state にメッセージを追加 setMessages({ ...messages, [`${newMsgIndex}`]: formattedNewMessages }); msgIndex++; }; // メッセージを削除する関数 const deleteMessage = (deleteKey, reason = "" ) => { if (reason === "clickaway" ) { // スナックバーの外をクリックした時は閉じない return ; } const newMessages = {}; // 削除対象以外のキーを持つメッセージを残した部分を newMessages に入れて state にセット Object.keys(messages) .filter(msgKey => msgKey !== deleteKey) .forEach(msgKey => (newMessages[msgKey] = messages[msgKey])); setMessages(newMessages); }; // スナックバーを連続して表示する部分. 5000ms で自動で消える // NotifyMessagesContext.Provider と props.children が重要。他は Material UI関連の定義 return ( <NotifyMessagesContext.Provider value={{ messages, pushMessage }}> {Object.keys(messages).map((msgKey, index) => ( <Snackbar key={msgKey} anchorOrigin={{ vertical: "bottom" , horizontal: "right" }} style={{ bottom: `${24 + 72 * index}px` }} autoHideDuration={5000} open={!!messages[msgKey]} onClose={(event, reason) => deleteMessage(msgKey, reason)} message={messages[msgKey]} action={ <IconButton size= "small" aria-label= "close" color= "inherit" onClick={() => deleteMessage(msgKey)} > <CloseIcon fontSize= "small" /> </IconButton> } /> ))} {props.children} </NotifyMessagesContext.Provider> ); } |
コンテクストのプロバイダで囲った中で props.children を呼びます。こうするとこのコンポーネントで囲った中ではどこでもコンテクストが使えます。このコンポーネントを呼び出すのは大体、ルートコンポーネントでこれは次のコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import React from "react" ; import ReactDOM from "react-dom" ; import App from "./App" ; import { NotifyMessagesProvider } from "./NotifyMessagesContext" ; const rootElement = document.getElementById( "root" ); // import した NotifyMessagesProvider を使うだけ ReactDOM.render( <React.StrictMode> <NotifyMessagesProvider> <App /> </NotifyMessagesProvider> </React.StrictMode>, rootElement ); |
このコンテクストの使い方の応用で全画面ローディング、エラーハンドリング、ページ遷移時のメッセージなど様々なグローバルなものを定義できます。