序文コンテキストは文脈と訳されます。プログラミングの分野ではよく見かける概念ですが、Reactにも存在します。 React の公式ドキュメントでは、Context は Advanced に分類されており、React の高度な API に属していますが、公式はアプリの安定バージョンで Context を使用することを推奨していません。
しかし、これはコンテキストに注意を払う必要がないという意味ではありません。実際、多くの優れた React コンポーネントは、Context を使用して機能を完成させています。たとえば、react-redux の <Provider /> は、Context を通じてグローバル ストアを提供します。ドラッグ コンポーネント react-dnd は、Context を通じてコンポーネント内の DOM ドラッグ アンド ドロップ イベントを配布します。ルーティング コンポーネント react-router は、Context を通じてルーティングの状態を管理します。 React コンポーネント開発では、Context をうまく使用すると、コンポーネントが強力かつ柔軟になります。 今日は、開発中に Context について学んだことと、それをコンポーネントの開発にどのように使用しているかについてお話ししたいと思います。 注: この記事で言及されているすべてのアプリは Web アプリを指します。 React Context の初見コンテキストの公式定義 React ドキュメントの Web サイトでは、「Context とは何か」という定義は提供されていませんが、Context が使用されるシナリオと Context の使用方法について説明しています。 公式ウェブサイトでは、Context を使用するシナリオが次のように説明されています。
簡単に言えば、コンポーネント ツリー内のレイヤーごとに props や state を渡してデータを渡したくない場合は、Context を使用してレベル間のコンポーネント データ転送を実装できます。 データを渡すには props または state を使用し、データは上から下に流れます。 Context を使用すると、コンポーネント間でデータを渡すことができます。 コンテキストの使い方コンテキストが機能するには、通常は親ノードであるコンテキスト プロデューサー (プロバイダー) と、通常は 1 つ以上の子ノードであるコンテキスト コンシューマー (コンシューマー) の 2 つのコンポーネントが必要です。したがって、コンテキストの使用はプロデューサー-コンシューマー モデルに基づいています。 親コンポーネント、つまり Context プロデューサーでは、静的プロパティ childContextTypes を通じて子コンポーネントに提供される Context オブジェクトのプロパティを宣言し、Context を表すプレーン オブジェクトを返すインスタンス getChildContext メソッドを実装する必要があります。 'react' から React をインポートします 'prop-types' から PropTypes をインポートします クラス MiddleComponent は React.Component を拡張します { 与える () { <ChildComponent /> を返します。 } } ParentComponentクラスはReact.Componentを拡張します。 //コンテキストオブジェクトのプロパティを宣言する static childContextTypes = { propA: PropTypes.文字列、 メソッドA: PropTypes.func } // Contextオブジェクトを返します。メソッド名は合意されたgetChildContext() { 戻る { propA: 'propA', メソッドA: () => 'メソッドA' } } 与える () { <MiddleComponent /> を返します。 } } Context コンシューマーの場合、親コンポーネントによって提供される Context に次の方法でアクセスします。 'react' から React をインポートします 'prop-types' から PropTypes をインポートします クラス ChildComponent は React.Component を拡張します { //使用する必要があるコンテキストプロパティを宣言します。static contextTypes = { propA: PropTypes.文字列 } 与える () { 定数{ プロップA、 方法A } = this.context console.log(`context.propA = ${propA}`) // context.propA = propA console.log(`context.methodA = ${methodA}`) // context.methodA = 未定義 戻る ... } } 子コンポーネントは、親コンポーネントの Context オブジェクトのプロパティにアクセスする前に、静的プロパティ contextTypes を通じて宣言する必要があります。そうしないと、プロパティ名が間違って記述されていなくても、取得されるオブジェクトは未定義になります。 ステートレス サブコンポーネントの場合、次の方法で親コンポーネントのコンテキストにアクセスできます。 'react' から React をインポートします 'prop-types' から PropTypes をインポートします const ChildComponent = (props, context) => { 定数{ プロップA } = コンテキスト console.log(`context.propA = ${propA}`) // context.propA = propA 戻る ... } 子コンポーネント.contextProps = { propA: PropTypes.文字列 } 次のリリースでは、React は Context API を調整し、プロデューサー-コンシューマー モデルの使用をより明確にしました。 'react' から React をインポートします。 'react-dom' から ReactDOM をインポートします。 定数ThemeContext = React.createContext({ 背景: '赤'、 色: 「白」 }); 静的メソッド React.createContext() を使用して Context オブジェクトを作成します。この Context オブジェクトには、<Provider /> と <Consumer /> の 2 つのコンポーネントが含まれます。 クラスAppはReact.Componentを拡張します。 与える () { 戻る ( <ThemeContext.Provider 値 = {{背景: '緑'、色: '白'}}> <ヘッダー /> </テーマコンテキスト.プロバイダー> ); } } <Provider /> の値は、現在の getChildContext() と同等です。 クラス Header は React.Component を拡張します { 与える () { 戻る ( <Title>こんにちは、React コンテキスト API</Title> ); } } クラス Title は React.Component を拡張します { 与える () { 戻る ( <テーマコンテキスト.コンシューマー> {コンテキスト => ( <h1 スタイル = {{背景: context.background、色: context.color}}> {this.props.children} </h1> )} </ThemeContext.Consumer> ); } } <Consumer /> の子要素は関数である必要があり、<Provider /> によって提供されるコンテキストは関数パラメータを通じて取得されます。 Context の新しい API は React のスタイルに近いことがわかります。 コンテキストを直接取得できるいくつかの場所実際、インスタンスのコンテキスト プロパティ (this.context) に加えて、React コンポーネントには、親コンポーネントによって提供されるコンテキストに直接アクセスできる場所が他にも多数あります。たとえば、構築方法:
たとえば、ライフサイクルは次のようになります。
関数指向のステートレス コンポーネントの場合、関数のパラメータを通じてコンポーネントのコンテキストに直接アクセスできます。 const StatelessComponent = (props, コンテキスト) => ( ...... ) 以上がコンテキストの基本です。より詳しい説明については、こちらを参照してください。 コンテキストについての私の理解さて、基本的なことを話した後で、React のコンテキストについての私の理解についてお話ししたいと思います。 コンテキストをコンポーネントスコープとして扱うReact を使用する開発者は、React アプリが本質的に React コンポーネント ツリーであることを知っています。各 React コンポーネントは、このツリーのノードに相当します。アプリのルート ノードを除き、他の各ノードには親コンポーネント チェーンがあります。 たとえば、上の図では、<Child /> の親コンポーネント チェーンは <SubNode /> -- <Node /> -- <App /> であり、<SubNode /> の親コンポーネント チェーンは <Node /> -- <App /> であり、<Node /> の親コンポーネント チェーンには <App /> という 1 つのコンポーネント ノードのみがあります。 これらのツリー接続されたコンポーネント ノードは、実際には Context ツリーを形成します。各ノードの Context は、親コンポーネント チェーン上のすべてのコンポーネント ノードによって getChildContext() を通じて提供される Context オブジェクトで構成されるオブジェクトです。 JS スコープ チェーンの概念に精通している開発者は、JS コード ブロックの実行中に対応するスコープ チェーンが作成されることを知っているはずです。このスコープ チェーンは、変数や関数など、実行時の JS コード ブロックの実行中にアクセスできるアクティブなオブジェクトを記録します。JS プログラムは、スコープ チェーンを介してコード ブロックの内外の変数や関数にアクセスします。 JS スコープ チェーンを例えとして使用すると、React コンポーネントによって提供される Context オブジェクトは、実際にはアクセス用に子コンポーネントに提供されるスコープのようなもので、Context オブジェクトのプロパティはスコープ内のアクティブ オブジェクトとして見ることができます。コンポーネントのコンテキストは、getChildContext() を通じて親ノード チェーン上のすべてのコンポーネントによって返される Context オブジェクトで構成されるため、コンポーネントは Context を通じて親コンポーネント チェーン上のすべてのノード コンポーネントによって提供される Context のプロパティにアクセスできます。 そこで、JS スコープ チェーンの考え方を借用し、コンポーネントのスコープとして Context を使用しました。 コンテキストの制御性と影響に焦点を当てるしかし、コンポーネントスコープとしてのコンテキストは、一般的なスコープの概念とは異なります(私がこれまで触れてきたプログラミング言語に関する限り)。コンテキストの制御可能性と影響に焦点を当てる必要があります。 日常の開発では、スコープやコンテキストの使用は非常に一般的で、自然であり、無意識にさえ行われます。ただし、React でコンテキストを使用するのはそれほど簡単ではありません。親コンポーネントは、childContextTypes を通じて、親コンポーネントが提供する Context を「宣言」する必要があり、子コンポーネントは contextTypes を通じて親コンポーネントの Context プロパティを「適用」する必要があります。したがって、React の Context は「許可された」コンポーネント スコープであると私は考えています。 この「承認された」アプローチの利点は何でしょうか?私の理解する限りでは、まず第一に、フレームワーク API の一貫性を維持し、propTypes と同様に宣言型のコーディング スタイルを使用することです。さらに、コンポーネントが提供するコンテキストの制御可能性と影響範囲をある程度確保することができます。 React App のコンポーネントはツリーのような構造で、レイヤーごとに拡張され、親子コンポーネントは 1 対多の線形依存関係です。コンテキストをランダムに使用すると、この依存関係が実際に破壊され、コンポーネント間に不要な依存関係が追加され、コンポーネントの再利用性が低下し、アプリの保守性に影響を与える可能性があります。 上の図から、元の線形依存コンポーネント ツリーでは、子コンポーネントが親コンポーネントの Context を使用するため、<Child /> コンポーネントが <Node /> と <App /> の両方に依存することがわかります。これら 2 つのコンポーネントから分離されると、<Child /> の可用性は保証されなくなり、<Child /> の再利用性が低下します。 私の意見では、react-redux がこのように行っているとしても、コンテキストを介してデータや API を公開することはエレガントな方法ではありません。したがって、不必要な影響を減らすためのメカニズム、つまり制約が必要です。 childContextTypes と contextTypes の 2 つの静的プロパティを制約することにより、データであろうと関数であろうと、コンポーネント自体、またはコンポーネントに関連する他の子コンポーネントだけがコンテキストのプロパティに自由にアクセスできることが、ある程度保証されます。コンポーネント自体または関連する子コンポーネントのみが、どの Context プロパティにアクセスできるかを明確に知ることができ、内部か外部かを問わず、コンポーネントに関連しない他のコンポーネントについては、親コンポーネント チェーン内の各親コンポーネントの childContextTypes によってどの Context プロパティが「宣言」されているかが不明であるため、contextTypes を通じて関連するプロパティに「適用」することはできません。したがって、コンポーネントのスコープ コンテキストに「権限」を与えることで、コンテキストの制御可能性と影響の範囲をある程度確保できると理解しています。 コンポーネントを開発する過程では、常にこれに注意し、Context を軽々しく使用しないようにする必要があります。 最初にコンテキストを使用する必要はありませんReact は高レベル API であるため、Context の使用を優先することは推奨されていません。私の理解はこうです:
つまり、Context が制御可能であることを保証できる限り、Context を使用しても問題はありません。Context を合理的に適用できれば、実際に Context は React コンポーネント開発に非常に強力なエクスペリエンスをもたらすことができます。 コンテキストをデータ共有の媒体として使用する公式コンテキストは、コンポーネント間のデータ通信に使用できます。私はそれをデータを共有するための橋、媒体として理解しています。データ共有は、アプリ レベルとコンポーネント レベルの 2 つのカテゴリに分けられます。
アプリ ルート ノード コンポーネントによって提供される Context オブジェクトは、アプリ レベルのグローバル スコープと見なすことができるため、アプリ ルート ノード コンポーネントによって提供される Context オブジェクトを使用して、アプリ レベルのグローバル データを作成します。既成の例については、 react-redux を参照してください。以下は、 <Provider /> コンポーネントのソース コードのコア実装です。 エクスポート関数createProvider(storeKey = 'store', subKey) { const subscriptionKey = サブキー || `${storeKey}サブスクリプション` クラス Provider は Component を拡張します { 子コンテキストを取得する() { 戻り値: [storeKey]: this[storeKey], [subscriptionKey]: null } } コンストラクタ(props, context) { super(プロパティ、コンテキスト) ストアキーをprops.storeに設定します。 } 与える() { Children.only(this.props.children) を返します } } // ...... プロバイダー.propTypes = { ストア: storeShape.isRequired、 子: PropTypes.element.isRequired、 } プロバイダー.childContextTypes = { [storeKey]: storeShape.isRequired、 [サブスクリプションキー]: サブスクリプションシェイプ、 } プロバイダを返す } デフォルトのcreateProvider()をエクスポートする アプリのルート コンポーネントが <Provider /> コンポーネントでラップされると、基本的にアプリのグローバル プロパティ ストアが提供され、これはアプリ全体でストア プロパティを共有することと同じです。もちろん、<Provider /> コンポーネントを他のコンポーネントにラップして、コンポーネント レベルでストアをグローバルに共有することもできます。
コンポーネントの機能がコンポーネント自体では完了できず、追加のサブコンポーネントに依存する必要がある場合は、コンテキストを使用して複数のサブコンポーネントで構成されるコンポーネントを構築できます。たとえば、 react-router 。 react-router の <Router /> は、ナビゲーション リンクとリダイレクトされたコンテンツが通常分離されているため、ルーティング操作と管理を単独で完了することはできません。そのため、ルーティング関連の作業を一緒に完了するには、<Link /> や <Route /> などのサブコンポーネントに依存する必要もあります。関連するサブコンポーネントを連携させるために、 react-router の実装では、 Context を使用して <Router />、 <Link />、 <Route /> などの関連するコンポーネント間でルーターを共有し、ルーティングの統一された操作と管理を完了します。 以下は、上記をよりよく理解するための関連コンポーネント <Router />、<Link />、<Route /> の部分的なソース コードです。 // ルータ.js /** * 履歴をコンテキストに配置するためのパブリック API。 */ クラス Router は React.Component を拡張します { 静的プロパティタイプ = { 履歴: PropTypes.object.isRequired、 子: PropTypes.node }; 静的コンテキストタイプ = { ルーター: PropTypes.object }; 静的な子コンテキストタイプ = { ルーター: PropTypes.object.isRequired }; 子コンテキストを取得する() { 戻る { ルーター: { ...このコンテキストルーター、 履歴: this.props.history、 ルート: { 場所: this.props.history.location、 一致: this.state.match } } }; } // ...... コンポーネントマウント() { const { children, history } = this.props; // ...... this.unlisten = history.listen(() => { this.setState({ 一致: this.computeMatch(history.location.pathname) }); }); } // ...... } ソース コードには他のロジックもありますが、<Router /> の中核は、子コンポーネントのルーター属性を持つ Context を提供し、同時に履歴を監視し、履歴が変更されると setState() を通じてコンポーネントの再レンダリングをトリガーすることです。 // リンク.js /** * 履歴対応の <a> をレンダリングするためのパブリック API。 */ クラスLinkはReact.Componentを拡張します。 // ...... 静的コンテキストタイプ = { ルーター: PropTypes.shape({ 履歴: PropTypes.shape({ プッシュ: PropTypes.func.isRequired、 置き換え: PropTypes.func.isRequired、 createHref: PropTypes.func.isRequired })。が必要です })。が必要です }; handleClick = イベント => { this.props.onClick の場合、 this.props.onClick(イベント); もし ( !event.defaultPrevented && イベント.ボタン === 0 && !this.props.target && !isModifiedEvent(イベント) ){ イベントをデフォルトにしない(); // <Router /> コンポーネントによって提供されるルーター インスタンスを使用します。const { history } = this.context.router; const { replace, to } = this.props; (置換)の場合{ history.replace(to); } それ以外 { history.push(to); } } }; 与える() { const { replace, to, innerRef, ...props } = this.props; // ... const { history } = this.context.router; 定数場所 = typeof を === "string" にする ? createLocation(to, null, null, history.location) : に; 場所のhrefを履歴に追加します。 戻る ( <a {...props} onClick={this.handleClick} href={href} ref={innerRef} /> ); } } <Link /> の核となるのは、<a> タグをレンダリングし、<a> タグのクリック イベントをインターセプトし、<Router /> が共有するルーターを介して履歴に対してルーティング操作を実行し、<Router /> に再レンダリングを通知することです。 // ルート.js /** * 単一のパスを一致させてレンダリングするためのパブリック API。 */ クラス Route は React.Component を拡張します { // ...... 状態 = { 一致: this.computeMatch(this.props, this.context.router) }; // 一致するパスを計算します。一致する場合は一致するオブジェクトを返し、そうでない場合は null を返します。 マッチを計算する( { computedMatch、場所、パス、厳密、正確、敏感 }, ルーター ){ if (computedMatch) は computedMatch を返します。 // ...... const { ルート } = ルーター; const パス名 = (場所 || route.location).pathname; matchPath(pathname, { path, strict, exact, sensitive }, route.match) を返します。 } // ...... 与える() { const { マッチ } = this.state; const { children, component, render } = this.props; const { history, route, staticContext } = this.context.router; 定数 location = this.props.location || route.location; const props = { 一致、場所、履歴、静的コンテキスト }; if (component) return match? React.createElement(component, props): null; if (render) return match? render(props): null; if (typeof children === "function") return children(props); if (children && !isEmptyChildren(children)) React.Children.only(children) を返します。 null を返します。 } } <Route /> には <Router /> に似たソースコードがあり、ネストされたルートを実現できますが、その核となるのは、現在のルートのパスが Context によって共有されるルーターを介して一致するかどうかを判断し、コンポーネントをレンダリングすることです。 上記の分析から、 react-router 全体が実際には <Router /> のコンテキストを中心に構築されていることがわかります。 コンテキストを使用したコンポーネントの開発以前は、スロット配布コンポーネントという単純なコンポーネントが Context を通じて開発されていました。この章では、このスロット配布コンポーネントの開発経験を活用して、コンテキストを使用してコンポーネントを開発する方法について説明します。 スロット分配コンポーネントまず、スロット分散コンポーネントとは何かについて説明します。この概念は、Vuejs で初めて導入されました。スロット配布とは、コンポーネントの組み合わせによって親コンポーネントの内容を子コンポーネントのテンプレートに挿入する技術です。Vuejs ではスロットと呼ばれています。 この概念をより直感的に理解できるように、スロット分散に関するデモを Vuejs から移行しました。 提供されたスロットを持つコンポーネント <my-component /> の場合、テンプレートは次のようになります。 <div> <h2>私はサブコンポーネントのタイトルです</h2> <スロット> 配信するコンテンツがない場合にのみ表示されます</slot> </div> 親コンポーネントの場合、テンプレートは次のようになります。 <div> <h1>私は親コンポーネントのタイトルです</h1> <私のコンポーネント> <p>これは初期コンテンツです</p> <p>これは初期のコンテンツです</p> </my-component> </div> 最終的なレンダリング結果: <div> <h1>私は親コンポーネントのタイトルです</h1> <div> <h2>私はサブコンポーネントのタイトルです</h2> <p>これは初期コンテンツです</p> <p>これは初期のコンテンツです</p> </div> </div> コンポーネント <my-component /> の <slot /> ノードが、最終的に親コンポーネントの <my-component /> ノードの下のコンテンツに置き換えられることがわかります。 Vuejs は名前付きスロットもサポートしています。 たとえば、レイアウト コンポーネント <app-layout />: <div class="コンテナ"> <ヘッダー> <スロット名="ヘッダー"></スロット> </ヘッダー> <メイン> <スロット></スロット> </メイン> <フッター> <スロット名="フッター"></スロット> </フッター> </div> 親コンポーネント テンプレートでは次のようになります。 <アプリレイアウト> <h1 slot="header">これはページタイトルである可能性があります</h1> <p>メインコンテンツの段落。 </p> <p>別の段落。 </p> <p slot="footer">連絡先情報はこちら</p> </アプリレイアウト> 最終的なレンダリング結果: <div class="コンテナ"> <ヘッダー> <h1>これはページタイトルの可能性があります</h1> </ヘッダー> <メイン> <p>メインコンテンツの段落。 </p> <p>別の段落。 </p> </メイン> <フッター> <p>連絡先情報はこちらです</p> </フッター> </div> スロット分散の利点は、コンポーネントをテンプレートに抽象化できることです。コンポーネント自体はテンプレート構造のみを考慮し、具体的な内容は親コンポーネントに任せます。同時に、DOM 構造を記述する HTML の文法表現を崩しません。これは非常に有意義な技術だと思いますが、残念ながら、React のこの技術に対するサポートはあまり親切ではありません。そこで、Vuejs のスロット配布コンポーネントを参考に、React ベースのスロット配布コンポーネントのセットを開発しました。これにより、React コンポーネントにもテンプレート機能が備わります。 <AppLayout /> コンポーネントの場合は、次のように記述します。 クラスAppLayoutはReact.Componentを拡張します。 静的 displayName = 'AppLayout' 与える () { 戻る ( <div class="コンテナ"> <ヘッダー> <スロット名="ヘッダー"></スロット> </ヘッダー> <メイン> <スロット></スロット> </メイン> <フッター> <スロット名="フッター"></スロット> </フッター> </div> ) } } 外側のレイヤーで使用する場合は、次のように記述できます。 <アプリレイアウト> <アドオンスロット="ヘッダー"> <h1>これはページタイトルの可能性があります</h1> </アドオン> <アドオン> <p>メインコンテンツの段落。 </p> <p>別の段落。 </p> </アドオン> <アドオンスロット="フッター"> <p>連絡先情報はこちらです</p> </アドオン> </アプリレイアウト> コンポーネント実装のアイデアこれまで考えてきたことを踏まえて、まずは実装案を整理してみましょう。 スロット配布コンポーネントが、スロット コンポーネント <Slot /> と配布コンポーネント <AddOn /> という 2 つのサブコンポーネントに依存する必要があることは容易にわかります。スロット コンポーネントは、コンテンツを配布するためのスロットを積み重ねて提供する役割を担います。配信コンポーネントは、配信コンテンツを収集し、それをスロット コンポーネントに提供して配信コンテンツをレンダリングする役割を担います。これは、スロットのコンシューマーに相当します。 明らかに、ここには問題があります。<Slot /> コンポーネントは <AddOn /> コンポーネントから独立しています。<AddOn /> のコンテンツを <Slot /> に入力するにはどうすればよいでしょうか?この問題を解決するのは難しくありません。 2 つの独立したモジュールを接続する必要がある場合は、それらの間にブリッジを構築するだけです。それで、この橋をどうやって作るのでしょうか?先ほど想像したコードを振り返ってみましょう。 <AppLayout /> コンポーネントの場合は、次のように記述します。 クラスAppLayoutはReact.Componentを拡張します。 静的 displayName = 'AppLayout' 与える () { 戻る ( <div class="コンテナ"> <ヘッダー> <スロット名="ヘッダー"></スロット> </ヘッダー> <メイン> <スロット></スロット> </メイン> <フッター> <スロット名="フッター"></スロット> </フッター> </div> ) } } 外側の層で使用する場合は、次のように記述します。 <アプリレイアウト> <アドオンスロット="ヘッダー"> <h1>これはページタイトルの可能性があります</h1> </アドオン> <アドオン> <p>メインコンテンツの段落。 </p> <p>別の段落。 </p> </アドオン> <アドオンスロット="フッター"> <p>連絡先情報はこちらです</p> </アドオン> </アプリレイアウト> <Slot /> であっても <AddOn /> であっても、それらは実際には <AppLayout /> の範囲内にあります。 <Slot /> は <AppLayout /> コンポーネントの render() メソッドによって返されるコンポーネント ノードであり、 <AddOn /> は <AppLayout /> の子ノードです。したがって、 <AppLayout /> は <Slot /> と <AddOn /> の間のブリッジと見なすことができます。では、<AppLayout /> はどのようにして <Slot /> と <AddOn /> 間の接続を確立するのでしょうか?ここでは、この記事の主人公である「コンテキスト」を使用します。次の質問は、Context を使用して <Slot /> と <AddOn /> 間の接続を確立するにはどうすればよいかということです。 ブリッジ <AppLayout /> については先ほど説明しました。外側のコンポーネントでは、<AppLayout /> が <AddOn /> を通じてスロットを埋めるためのコンテンツを収集する役割を担います。 <AppLayout /> は、コンテキストを利用して入力コンテンツを取得するためのインターフェースを定義します。レンダリングの際、<Slot /> は <AppLayout /> によってレンダリングされるノードであるため、<Slot /> は Context を通じて <AppLayout /> で定義された充填コンテンツを取得するためのインターフェースを取得し、このインターフェースを通じて充填コンテンツを取得してレンダリングすることができます。 アイデアに従ってスロット配分コンポーネントを実装する<AddOn /> は <AppLayout /> の子ノードであり、<AddOn /> は特定のコンポーネントであるため、名前または displayName で識別できます。したがって、レンダリングの前、つまり render() が返される前に、<AppLayout /> は子ノードを走査し、 slot の値をキーとして使用して <AddOn /> の各子ノードをキャッシュします。 <AddOn /> がスロットを設定しない場合は、名前のない <Slot /> を埋めているものとみなされます。これらの名前のないスロットには、$$default などのキーを指定できます。 <AppLayout /> の場合、コードは次のようになります。 クラスAppLayoutはReact.Componentを拡張します。 静的な子コンテキストタイプ = { リクエストAddOnRenderer: PropTypes.func } // 各 <AddOn /> のコンテンツをキャッシュするために使用されます。addOnRenderers = {} // Context { を通じて子ノードに getChildContext () インターフェイスを提供します const requestAddOnRenderer = (名前) => { if (!this.addOnRenderers[名前]) { 未定義を返す } 戻り値 () => ( this.addOnRenderers[名前] ) } 戻る { リクエストAddOnRenderer } } 与える () { 定数{ 子供たち、 ...レストプロップ } = this.props (子供)の場合{ // kv モードで <AddOn /> の内容をキャッシュする const arr = React.Children.toArray(children) 定数名Checked = [] this.addOnRenderers = {} arr.forEach(アイテム => { 定数 itemType = アイテム.type if (item.type.displayName === 'アドオン') { const slotName = item.props.slot || '$$default' // コンテンツの一意性を保証する if (nameChecked.findIndex(item => item === stubName) !== -1) { 新しいエラーをスローします(`スロット(${slotName}) は使用されています`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(スタブ名) } }) } 戻る ( <div class="コンテナ"> <ヘッダー> <スロット名="ヘッダー"></スロット> </ヘッダー> <メイン> <スロット></スロット> </メイン> <フッター> <スロット名="フッター"></スロット> </フッター> </div> ) } } <AppLayout /> は、Context インターフェイス requestAddOnRenderer() を定義します。requestAddOnRenderer() インターフェイスは、名前に基づいて関数を返します。返された関数は、名前に基づいて addOnRenderers のプロパティにアクセスします。addOnRenderers は、<AddOn /> のコンテンツ キャッシュ オブジェクトです。 <Slot /> の実装は非常にシンプルで、コードは次のようになります。 // プロパティ、コンテキスト const スロット = ({ name, children }, { requestAddOnRenderer }) => { const addOnRenderer = requestAddOnRenderer(名前) 戻り値 (addOnRenderer && addOnRenderer()) || 子供 ヌル } Slot.displayName = 'スロット' Slot.contextTypes = { requestAddOnRenderer: PropTypes.func } Slot.propTypes = { 名前: PropTypes.文字列 } Slot.defaultProps = { 名前: '$$default' } <Slot /> はコンテキストを通じて <AppLayout /> が提供する requestAddOnRenderer() インターフェースを取得し、最終的なレンダリングの主なオブジェクトは <AppLayout /> にキャッシュされた <AddOn /> のコンテンツであることがわかります。指定された <AddOn /> のコンテンツが取得されない場合、 <Slot /> 自体の子要素がレンダリングされます。 <AddOn /> はさらにシンプルです: 定数AddOn = () => null AddOn.propTypes = { スロット: PropTypes.string } AddOn.defaultTypes = { スロット: '$$default' } AddOn.displayName = 'アドオン' <AddOn /> は何もせず、null を返します。その機能は、スロットに配布されたコンテンツを <AppLayout /> にキャッシュさせることです。 <AppLayout /> をより多用途にすることができます上記のコードにより、<AppLayout /> は基本的にスロット分散機能を持つコンポーネントに変換されますが、<AppLayout /> がユニバーサルではないことは明らかです。これを独立したユニバーサル コンポーネントに昇格できます。 このコンポーネントにSlotProviderという名前を付けました 関数 getDisplayName (コンポーネント) { コンポーネント.displayName || コンポーネント.name || 'コンポーネント' を返します } const slotProviderHoC = (ラップされたコンポーネント) => { 戻りクラスはReact.Componentを拡張します{ 静的 displayName = `SlotProvider(${getDisplayName(WrappedComponent)})` 静的な子コンテキストタイプ = { リクエストAddOnRenderer: PropTypes.func } // 各 <AddOn /> のコンテンツをキャッシュするために使用されます。addOnRenderers = {} // Context { を通じて子ノードに getChildContext () インターフェイスを提供します const requestAddOnRenderer = (名前) => { if (!this.addOnRenderers[名前]) { 未定義を返す } 戻り値 () => ( this.addOnRenderers[名前] ) } 戻る { リクエストAddOnRenderer } } 与える () { 定数{ 子供たち、 ...レストプロップ } = this.props if (子) { // kv モードで <AddOn /> の内容をキャッシュする const arr = React.Children.toArray(children) 定数名Checked = [] this.addOnRenderers = {} arr.forEach(アイテム => { 定数 itemType = アイテム.type if (item.type.displayName === 'アドオン') { const slotName = item.props.slot || '$$default' // コンテンツの一意性を保証する if (nameChecked.findIndex(item => item === stubName) !== -1) { 新しいエラーをスローします(`スロット(${slotName}) は使用されています`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(スタブ名) } }) } 戻り値 (<WrappedComponent {...restProps} />) } } } エクスポート const SlotProvider = slotProviderHoC React の高階コンポーネントを使用して、元の <AppLayout /> を独立した一般的なコンポーネントに変換します。元の <AppLayout /> の場合、この SlotProvider 高階コンポーネントを使用して、スロット配布機能を持つコンポーネントに変換できます。 './SlotProvider.js' から {SlotProvider} をインポートします。 クラスAppLayoutはReact.Componentを拡張します。 静的 displayName = 'AppLayout' 与える () { 戻る ( <div class="コンテナ"> <ヘッダー> <スロット名="ヘッダー"></スロット> </ヘッダー> <メイン> <スロット></スロット> </メイン> <フッター> <スロット名="フッター"></スロット> </フッター> </div> ) } } デフォルトのSlotProvider(AppLayout)をエクスポートします。 上記の経験から、コンポーネントを設計・開発する際には、
このとき、データを共有するための媒体として仲介者を使用する必要があります。redux などのサードパーティ モジュールを導入するよりも、Context を直接使用する方がエレガントになります。 新しいコンテキストAPIを試す新しいバージョンの Context API を使用して、以前のスロット配布コンポーネントを変換します。 // スロットプロバイダー.js 関数 getDisplayName (コンポーネント) { コンポーネント.displayName || コンポーネント.name || 'コンポーネント' を返します } エクスポートconstSlotContext = React.createContext({ リクエストAddOnRenderer: () => {} }) const slotProviderHoC = (ラップされたコンポーネント) => { 戻りクラスはReact.Componentを拡張します{ 静的 displayName = `SlotProvider(${getDisplayName(WrappedComponent)})` // 各 <AddOn /> のコンテンツをキャッシュするために使用されます。addOnRenderers = {} requestAddOnRenderer = (名前) => { if (!this.addOnRenderers[名前]) { 未定義を返す } 戻り値 () => ( this.addOnRenderers[名前] ) } 与える () { 定数{ 子供たち、 ...レストプロップ } = this.props if (子) { // kv モードで <AddOn /> の内容をキャッシュする const arr = React.Children.toArray(children) 定数名Checked = [] this.addOnRenderers = {} arr.forEach(アイテム => { 定数 itemType = アイテム.type if (item.type.displayName === 'アドオン') { const slotName = item.props.slot || '$$default' // コンテンツの一意性を保証する if (nameChecked.findIndex(item => item === stubName) !== -1) { 新しいエラーをスローします(`スロット(${slotName}) は使用されています`) } this.addOnRenderers[stubName] = item.props.children nameChecked.push(スタブ名) } }) } 戻る ( <SlotContext.Provider 値={ リクエストAddOnRenderer: this.requestAddOnRenderer }> <ラップされたコンポーネント {...restProps} /> </slotcontext.provider> ) } } } const slotprovider = slotproviderhocをエクスポートします 以前のChildContextTypesとgetChildContext()は、いくつかのローカル調整を除いて削除されています。 // slot.js './slotprovider.js'から{slotContext}をインポート const slot =({name、children})=> { 戻る ( <slotcontext.consumer> {(context)=> { const addonrenderer = requestaddonrenderer(name) return(addonrenderer && addonrenderer())|| 子供|| ヌル }} </slotcontext.consumer> ) } slot.displayname = 'slot' slot.proptypes = {name:proptypes.string} slot.defaultProps = {name: '$$ default'} コンテキストは以前はプロデューサー - 消費者モードで使用されていたため、コンポーネント自体は比較的単純だったため、新しいAPIを使用した変換後に大きな違いはありませんでした。 要約する
上記は私の共有コンテンツです。 参考文献コンテキスト-https://reactjs.org/docs/context.html これは、Reactコンテキストの私の理解と応用に関する終わりです。 以下もご興味があるかもしれません:
|
<<: crontab スケジュールされたタスクが実行されない理由の分析と解決
>>: MySql インデックスの詳細な紹介と正しい使用方法
目次継承とプロトタイプチェーン継承されたプロパティ継承されたメソッドJavaScript でのプロト...
序文この記事はかなり詳細で、少し面倒です。他のチュートリアル ドキュメントでは多くの手順が省略されて...
mysqlは時間のかかるSQLを記録しますMySQL は、最適化と分析のために、時間のかかる SQL...
mysql のデフォルトのストレージ ディレクトリは/var/lib/mysql/です。以下は、デフ...
主にインストール後に my.ini ファイルを確認するために、msi 形式でインストールしました。フ...
目次MySQL 5.6以前MySQL 5.6以降要約する知らせMySQL 5.6以前更新手順元のテー...
HTML onfocus イベント属性定義と使用法onfocus 属性は、要素がフォーカスを受け取っ...
目次序文SQL文の最適化遅いクエリSQLを記録する設定を変更する方法スロークエリログを表示するSQL...
<br />オリジナルリンク: http://www.dudo.org/article....
公式サイトの説明: コンポーネントを定義する場合、コンポーネントは複数のインスタンスを作成するために...
グリッドシステムの形成1692年、新しく即位したフランス国王ルイ14世は、フランスの印刷技術のレベル...
Shtml と asp は似ています。shtml という名前のファイルでは、asp の命令と同様に、...
目次最初にコールバック関数を使用するes6 非同期処理モデルこの非同期モデルに合わせたAPI: pr...
私たちが毎日使っているブラウザや Word 文書のスクロール バーはなぜ右側にあるのでしょうか。多く...
現在のデータベースでサポートされているエンジンを表示します エンジンを表示 +-----------...