React では、コンテキストは、コンポーネントの各レイヤーにプロップを手動で追加することなく、コンポーネント ツリー間でデータを渡す方法です。コンテキストは、コンポーネントをレイヤーごとに明示的に渡すことなく、コンポーネント間で指定された値を共有する方法を提供します。木の中で。
このチュートリアルの動作環境: Windows 10 システム、React バージョン 17.0.1、Dell G3 コンピューター。
コンテキストは、コンポーネントの各レイヤーにプロップを手動で追加することなく、コンポーネント ツリー間でデータを受け渡す方法を提供します。典型的な React アプリケーションでは、データは props を介して上から下 (親から子) に渡されますが、このアプローチは特定の種類のプロパティ (例: ロケール設定、UI テーマ) にとっては非常に面倒です。これらのプロパティは、多くのコンポーネントで必要となります。アプリケーション。 Context は、コンポーネント ツリーの各レベルで明示的に props を渡すことなく、コンポーネント間でそのような値を共有する方法を提供します。
コンテキストは、現在認証されているユーザー、テーマ、優先言語など、コンポーネント ツリーに対して「グローバル」なデータを共有するように設計されています。たとえば、次のコードでは、「テーマ」属性を通じてボタン コンポーネントのスタイルを手動で調整します。
class App extends React.Component { render() { return <Toolbar theme="dark" /> }}function Toolbar(props) { // Toolbar コンポーネントは追加の「テーマ」プロパティを受け取り、それが ThemedButton に渡されます。成分。 // アプリケーション内のすべてのボタンがテーマの値を知る必要がある場合、 // この値はすべてのコンポーネントに渡される必要があるため、面倒になります。 return ( <p> <ThemedButton テーマ={props.theme} /> </p> );}class ThemedButton extends React.Component { render() { return <Button テーマ={this.props.theme} /> }; }// 小道具を渡す: App -> Toolbar -> ThemedButton// ネストが非常に深い場合は、小道具が途中で必要ない場合でも、小道具をレイヤーごとに渡す必要があり、非常に面倒に思えます。コンテキストを使用すると、中間要素を介して props を渡すことを回避できます。
// コンテキストを使用すると、各コンポーネントを介して値を明示的に渡すことなく、コンポーネント ツリーの奥深くに値を渡すことができます。 // 現在のテーマのコンテキストを作成します (「ライト」がデフォルト値です)。 const ThemeContext = React.createContext('light');class App extends React.Component { render() { // プロバイダーを使用して、現在のテーマを次のコンポーネント ツリーに渡します。 // 深さに関係なく、どのコンポーネントもこの値を読み取ることができます。 // この例では、現在の値として「dark」を渡します。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); }}// 中間コンポーネントは、引き継がれるテーマを指定する必要がなくなりました。 function Toolbar() { return ( <p> <ThemedButton /> </p> );}class ThemedButton extends React.Component { // 現在のテーマのコンテキストを読み取るために contextType を指定します。 // React は最も近いテーマ プロバイダーを検索し、その値を使用します。 // この例では、現在のテーマの値は「dark」です。 static contextType = ThemeContext; render() { return <Button theme={this.context} /> }}// ThemedButto.contextType = ThemeContext; を使用することもできます。Context オブジェクトを作成します。 React がこの Context オブジェクトにサブスクライブされたコンポーネントをレンダリングするとき、コンポーネントは、コンポーネント ツリー内でそれ自体に最も近い一致するプロバイダーから現在のコンテキスト値を読み取ります。
コンポーネントが配置されているツリーに一致するプロバイダーがない場合にのみ、defaultValue パラメーターが有効になります。これは、コンポーネントをラップするプロバイダーを使用せずにコンポーネントをテストするのに役立ちます。注: unknown がプロバイダーの値に渡されると、使用側コンポーネントの defaultValue は有効になりません。
各 Context オブジェクトは Provider React コンポーネントを返します。これにより、使用するコンポーネントはコンテキストの変更をサブスクライブできます。
プロバイダーは値属性を受け取り、それを使用側コンポーネントに渡します。プロバイダーは、複数のコンシューマー コンポーネントと対応する関係を持つことができます。複数のプロバイダーをネストして使用することもでき、内側のレイヤーが外側のレイヤーのデータを上書きします。
プロバイダーの値が変更されると、プロバイダー内のすべての使用コンポーネントが再レンダリングされます。プロバイダーもその内部コンシューマー コンポーネントも shouldComponentUpdate 関数の対象ではないため、コンシューマー コンポーネントは、その祖先コンポーネントが更新を終了した場合でも更新できます。
クラスにマウントされた contextType 属性は、React.createContext() によって作成された Context オブジェクトに再割り当てされます。これにより、this.context を使用して最新の Context の値を使用できるようになります。レンダリング関数を含め、あらゆるライフサイクルでアクセスできます。
import MyContext from './MyContext';class MyClass extends React.Component {componentDidMount() { let value = this.context; /* コンポーネントがマウントされた後、MyContext コンポーネントの値を使用していくつかの副作用操作を実行します* / }componentDidUpdate() { let value = this.context; /* ... */ }componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context ; /* MyContext コンポーネントの値に基づいてレンダリングします*/ } // または、上記の例のように static contextType = MyContext;}MyClass.contextType = MyContext; を使用します。ここで、React コンポーネントはコンテキストの変更をサブスクライブすることもできます。これにより、機能コンポーネントでコンテキストをサブスクライブできるようになります。
これには子供としての機能が必要です。この関数は現在のコンテキスト値を受け取り、React ノードを返します。関数に渡される値は、コンポーネント ツリーの上位でこのコンテキストに最も近いプロバイダーによって提供される値と同等です。対応するプロバイダーがない場合、value パラメーターは createContext() に渡されるdefaultValue と同等になります。
コンテキスト オブジェクトは、文字列型の displayName という名前のプロパティを受け入れます。 React DevTools はこの文字列を使用して、表示するコンテキストを決定します。
次のコンポーネントは、DevTools で MyDisplayName として表示されます。
const MyContext = React.createContext(/* 何らかの値 */);MyContext.displayName = 'MyDisplayName';<MyContext.Provider> // DevTools の "MyDisplayName.Provider" <MyContext.Consumer> // "MyDisplayName.Consumer" In開発ツール上記のテーマの例では、動的な値を使用してより複雑な使用法を実現できます。
テーマコンテキスト.js
import const テーマ = { ライト: { 前景: '#000000', 背景: '#eeeeee', }, dark: { 前景: '#ffffff', 背景: '#222222', },};export const ThemeContext = React .createContext(主題.dark); // これはデフォルト値ですテーマボタン.js
import { ThemeContext } from './theme-context'; class ThemedButton extends React.Component { render() { // ThemeContext のデフォルト値を取得 let theme = this.context; {...props} style={{backgroundColor:theme.background}} /> ); // 静的 contextType = ThemeContext;}ThemedButton.contextType = ThemeContext;デフォルトの ThemedButton をエクスポートします。アプリ.js
import { ThemeContext, Strategies } from './theme-context';import ThemedButton from './themed-button';// ThemedButton を使用した中間コンポーネント関数 Toolbar(props) { return ( <ThemedButton onClick={props.changeTheme } > テーマの変更 </ThemedButton> );} class App extends React.Component {constructor(props) { super(props); this.state = { テーマ:主題.ライト, }; this.toggleTheme = () => { this .setState(state => ({ テーマ: state.theme === テーマ.ダーク ? テーマ.ライト: テーマ.ダーク, })); } render() { // ThemeProvider 内の ThemedButton ボタン コンポーネントによって使用されます。状態の値、 // および外部コンポーネントはデフォルトのテーマ値を使用します return ( <Page> <ThemeContext.Provider value={this.state.theme}> <Toolbar changeTheme={this.toggleTheme} /> </ThemeContext .Provider> <Section> <ThemedButton /> </Section> </Page> ); }}ReactDOM.render(<App />, document.root);// ThemeContext.Provider によってラップされたコンポーネントは、ツールバーの ThemeContext の値を使用できます。 ThemedButton は this.context を使用して値を取得できます。// 状態を更新する方法は props を介して渡され、更新は子孫コンポーネントによってトリガーされることに注意してください。Context を介した方法については、以下で説明します。上の例では、props を介して update 関数を渡し、アプリ内のテーマの値を変更します。コンポーネント ツリーに深くネストされているコンポーネントからコンテキストを更新する必要があることがわかっています。このシナリオでは、コンテキストを介して関数を渡して、コンシューマー コンポーネントにコンテキストを更新させることができます。
テーマコンテキスト.js
// createContext に渡されるデフォルト値のデータ構造が呼び出し側コンポーネント (コンシューマー) と一致することを確認してください。 import const ThemeContext = React.createContext({ テーマ: テーマ.ダーク, toggleTheme: () => {}, // テーマを更新するメソッドを定義し、それを渡します});テーマトグラーボタン.js
import { ThemeContext } from './theme-context';function ThemeTogglerButton() { // テーマトグラーボタンはテーマ値を取得するだけでなく、コンテキスト (以下の app.js 部分) から toggleTheme 関数も取得します return ( < ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> テーマの切り替え </button> )} </ThemeContext.Consumer> );デフォルトの ThemeTogglerButton をエクスポートします。アプリ.js
import { ThemeContext, テーマ } from './theme-context';import ThemeTogglerButton from './theme-toggler-button';class App extends React.Component {constructor(props) { super(props); ) => { this.setState(state => ({ theme: state.theme === sinners.dark ? sinners.light : Strategies.dark, })); // State には更新関数も含まれています。コンテキストプロバイダーに渡されます。 this.state = { theme: Strategies.light, toggleTheme: this.toggleTheme, // 更新関数を定義し、コンテキストを通じてそれを渡します } } render() { // 状態全体がプロバイダーに渡されます return ( < ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); }}function Content() { return ( <p> <ThemeTogglerButton /> </p> );}ReactDOM.render( <アプリ />、document.root);コンテキストが迅速に再レンダリングされるようにするために、React は各コンシューマ コンポーネントのコンテキストをコンポーネント ツリー内の個別のノードにする必要があります。
// テーマコンテキスト、デフォルトのテーマは「ライト」 value const ThemeContext = React.createContext('light') // ユーザーログインコンテキスト const UserContext = React.createContext({ name: 'Guest',}); React .Component { render() { const { signedInUser, theme } = this.props; // 初期コンテキスト値を提供するアプリ コンポーネント return ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser} > < Layout /> </UserContext.Provider> </ThemeContext.Provider> ); }}function Layout() { return ( <p> <Sidebar /> <Content /> </p> );}// コンポーネント複数のコンテキスト関数を使用する可能性があります Content() { return ( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext .Consumer> )} </ThemeContext.Consumer> );}2 つ以上のコンテキスト値が頻繁に一緒に使用される場合は、これらの値を提供する独自のレンダリング コンポーネントを作成することを検討することをお勧めします。
コンテキストは参照 ID を使用していつレンダリングするかを決定するため、プロバイダーの親コンポーネントが再レンダリングされるときに、コンシューマー コンポーネントで予期しないレンダリングを引き起こす可能性のあるいくつかの落とし穴が存在する可能性があります。たとえば、プロバイダーが再レンダリングされるたびに、次のコードは次のすべてのコンシューマ コンポーネントを再レンダリングします。これは、value 属性が常に新しいオブジェクトに割り当てられるためです。
class App extends React.Component { render() { return ( <MyContext.Provider value={{something: 'something'}}> <Toolbar /> </MyContext.Provider> }};この状況を防ぐために、値の状態が親ノードの状態に昇格されます。
class App extends React.Component {constructor(props) { super(props); // 複数のレンダリングの後、値が変更されない場合、次のコンシューマー コンポーネントは再レンダリングされません。値: { 何か: '何か'}, } } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> );