參考文章
使用 Context 深層傳遞參數(shù)
通常來說,會通過 props 將信息從父組件傳遞到子組件。但是,如果必須通過許多中間組件向下傳遞 props,或是在應(yīng)用中的許多組件需要相同的信息,傳遞 props 會變的十分冗長和不便。Context 允許父組件向其下層無論多深的任何組件提供信息,而無需通過 props 顯式傳遞。
傳遞 props 帶來的問題
傳遞 props 是將數(shù)據(jù)通過 UI 樹顯式傳遞到使用它的組件的好方法。
但是當(dāng)需要在組件樹中深層傳遞參數(shù)以及需要在組件間復(fù)用相同的參數(shù)時,傳遞 props 就會變得很麻煩。最近的根節(jié)點父組件可能離需要數(shù)據(jù)的組件很遠,狀態(tài)提升 到太高的層級會導(dǎo)致 “逐層傳遞 props” 的情況。
React 的 context 功能可以在組件樹中不需要 props 將數(shù)據(jù)“直達”到所需的組件中。
Context:傳遞 props 的另一種方法
Context 讓父組件可以為它下面的整個組件樹提供數(shù)據(jù)。Context 有很多種用途。這里就有一個示例。思考一下這個 Heading
組件接收一個 level
參數(shù)來決定它標(biāo)題尺寸的場景:
// App.js
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading level={1}>主標(biāo)題</Heading>
<Heading level={2}>副標(biāo)題</Heading>
<Heading level={3}>子標(biāo)題</Heading>
<Heading level={4}>子子標(biāo)題</Heading>
</Section>
);
}
// Section.js
export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}
// Heading.js
export default function Heading({ level, children }) {
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
default:
throw Error('未知的 level:' + level);
}
}
假設(shè)想讓相同 Section
中的多個 Heading 具有相同的尺寸:
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading level={1}>主標(biāo)題</Heading>
<Section>
<Heading level={2}>副標(biāo)題</Heading>
<Heading level={2}>副標(biāo)題</Heading>
<Section>
<Heading level={3}>子標(biāo)題</Heading>
<Heading level={3}>子標(biāo)題</Heading>
<Section>
<Heading level={4}>子子標(biāo)題</Heading>
<Heading level={4}>子子標(biāo)題</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
目前,將 level
參數(shù)分別傳遞給每個 <Heading>
:
<Section>
<Heading level={3}>關(guān)于</Heading>
<Heading level={3}>照片</Heading>
</Section>
將 level
參數(shù)傳遞給 <Section>
組件而不是傳給 <Heading>
組件看起來更好一些。這樣的話可以強制使同一個 section 中的所有標(biāo)題都有相同的尺寸:
<Section level={3}>
<Heading>關(guān)于</Heading>
<Heading>照片</Heading>
</Section>
但是 <Heading>
組件是如何知道離它最近的 <Section>
的 level 的呢?
這需要子組件可以通過某種方式“訪問”到組件樹中某處在其上層的數(shù)據(jù)。
不能只通過 props 來實現(xiàn)它。這就是 context 大顯身手的地方。可以通過以下三個步驟來實現(xiàn)它:
-
創(chuàng)建 一個 context。(可以將其命名為
LevelContext
, 因為它表示的是標(biāo)題級別。) - 在需要數(shù)據(jù)的組件內(nèi) 使用 剛剛創(chuàng)建的 context。(
Heading
將會使用LevelContext
。) - 在指定數(shù)據(jù)的組件中 提供 這個 context。 (
Section
將會提供LevelContext
。)
Context 可以讓父節(jié)點,甚至是很遠的父節(jié)點都可以為其內(nèi)部的整個組件樹提供數(shù)據(jù)。
Step 1:創(chuàng)建 context
首先,需要創(chuàng)建這個 context,并 將其從一個文件中導(dǎo)出,這樣組件才可以使用它:
// LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(1);
createContext
只需默認(rèn)值這么一個參數(shù)。在這里, 1
表示最大的標(biāo)題級別,但是可以傳遞任何類型的值(甚至可以傳入一個對象)。將在下一個步驟中見識到默認(rèn)值的意義。
Step 2:使用 Context
從 React 中引入 useContext
Hook 以及剛剛創(chuàng)建的 context:
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
目前,Heading
組件從 props 中讀取 level
:
export default function Heading({ level, children }) {
// ...
}
刪掉 level
參數(shù)并從剛剛引入的 LevelContext
中讀取值:
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
useContext
是一個 Hook。和 useState
以及 useReducer
一樣,只能在 React 組件中(不是循環(huán)或者條件里)立即調(diào)用 Hook。useContext
告訴 React Heading
組件想要讀取 LevelContext
。
現(xiàn)在 Heading
組件沒有 level
參數(shù),不需要再像這樣在 JSX 中將 level 參數(shù)傳遞給 Heading
:
<Section>
<Heading level={4}>子子標(biāo)題</Heading>
<Heading level={4}>子子標(biāo)題</Heading>
</Section>
修改一下 JSX,讓 Section
組件代替 Heading
組件接收 level 參數(shù):
<Section level={4}>
<Heading>子子標(biāo)題</Heading>
<Heading>子子標(biāo)題</Heading>
</Section>
將修改下邊的代碼直到它正常運行:
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section level={1}>
<Heading>主標(biāo)題</Heading>
<Section level={2}>
<Heading>副標(biāo)題</Heading>
<Heading>副標(biāo)題</Heading>
<Section level={3}>
<Heading>子標(biāo)題</Heading>
<Heading>子標(biāo)題</Heading>
<Section level={4}>
<Heading>子子標(biāo)題</Heading>
<Heading>子子標(biāo)題</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
注意:這個示例還不能運行。所有 headings 的尺寸都一樣,因為 即使正在使用 context,但是還沒有提供它。 React 不知道從哪里獲取這個 context!
如果不提供 context,React 會使用在上一步指定的默認(rèn)值。在這個例子中,為 createContext
傳入了 1
這個參數(shù),所以 useContext(LevelContext)
會返回 1
,把所有的標(biāo)題都設(shè)置為<h1>
。通過讓每個 Section
提供它自己的 context 來修復(fù)這個問題。
Step 3:提供 context
Section
組件目前渲染傳入它的子組件:
export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}
把它們用 context provider 包裹起來 以提供 LevelContext
給它們:
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
這告訴 React:“如果在 <Section>
組件中的任何子組件請求 LevelContext
,給他們這個 level
?!苯M件會使用 UI 樹中在它上層最近的那個 <LevelContext.Provider>
傳遞過來的值。
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section level={1}>
<Heading>主標(biāo)題</Heading>
<Section level={2}>
<Heading>副標(biāo)題</Heading>
<Heading>副標(biāo)題</Heading>
<Section level={3}>
<Heading>子標(biāo)題</Heading>
<Heading>子標(biāo)題</Heading>
<Section level={4}>
<Heading>子子標(biāo)題</Heading>
<Heading>子子標(biāo)題</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
這與原始代碼的運行結(jié)果相同,但是不需要向每個 Heading
組件傳遞 level
參數(shù)了!取而代之的是,它通過訪問上層最近的 Section
來“斷定”它的標(biāo)題級別:
- 將一個
level
參數(shù)傳遞給<Section>
。 -
Section
把它的子元素包在<LevelContext.Provider value={level}>
里面。 -
Heading
使用useContext(LevelContext)
訪問上層最近的LevelContext
提供的值。
在相同的組件中使用并提供 context
目前,仍需要手動指定每個 section 的 level
:
export default function Page() {
return (
<Section level={1}>
...
<Section level={2}>
...
<Section level={3}>
...
由于 context 可以從上層的組件讀取信息,每個 Section
都會從上層的 Section
讀取 level
,并自動向下層傳遞 level + 1
。 可以像下面這樣做:
// Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
這樣修改之后,不用將 level
參數(shù)傳給 <Section>
或者是 <Heading>
了:
// App.js
import Heading from './Heading.js';
import Section from './Section.js';
export default function Page() {
return (
<Section>
<Heading>主標(biāo)題</Heading>
<Section>
<Heading>副標(biāo)題</Heading>
<Heading>副標(biāo)題</Heading>
<Section>
<Heading>子標(biāo)題</Heading>
<Heading>子標(biāo)題</Heading>
<Section>
<Heading>子子標(biāo)題</Heading>
<Heading>子子標(biāo)題</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
// Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading 必須在 Section 內(nèi)部!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
default:
throw Error('未知的 level:' + level);
}
}
現(xiàn)在,Heading
和 Section
都通過讀取 LevelContext
來判斷它們的深度。而且 Section
把它的子組件都包在 LevelContext
中來指定其中的任何內(nèi)容都處于一個“更深”的級別。
注意:本示例使用標(biāo)題級別來展示,因為它們直觀地顯示了嵌套組件如何覆蓋 context。但是 context 對于許多其他的場景也很有用。可以用它來傳遞整個子樹需要的任何信息:當(dāng)前的顏色主題、當(dāng)前登錄的用戶等。
Context 會穿過中間層級的組件
可以在提供 context 的組件和使用它的組件之間的層級插入任意數(shù)量的組件。這包括像 <div>
這樣的內(nèi)置組件和自己創(chuàng)建的組件。
在這個示例中,相同的 Post
組件(帶有虛線邊框)在兩個不同的嵌套層級上渲染。注意,它內(nèi)部的 <Heading>
會自動從最近的 <Section>
獲取它的級別:
// App.js
import Heading from './Heading.js';
import Section from './Section.js';
export default function ProfilePage() {
return (
<Section>
<Heading>My Profile</Heading>
<Post
title="旅行者,你好!"
body="來看看我的冒險。"
/>
<AllPosts />
</Section>
);
}
function AllPosts() {
return (
<Section>
<Heading>帖子</Heading>
<RecentPosts />
</Section>
);
}
function RecentPosts() {
return (
<Section>
<Heading>最近的帖子</Heading>
<Post
title="里斯本的味道"
body="...那些蛋撻!"
/>
<Post
title="探戈節(jié)奏中的布宜諾斯艾利斯"
body="我愛它!"
/>
</Section>
);
}
function Post({ title, body }) {
return (
<Section isFancy={true}>
<Heading>
{title}
</Heading>
<p><i>{body}</i></p>
</Section>
);
}
// Section.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children, isFancy }) {
const level = useContext(LevelContext);
return (
<section className={
'section ' +
(isFancy ? 'fancy' : '')
}>
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
不需要做任何特殊的操作。Section
為它內(nèi)部的樹指定一個 context,所以可以在任何地方插入一個 <Heading>
,而且它會有正確的尺寸。
Context 讓你可以編寫“適應(yīng)周圍環(huán)境”的組件,并且根據(jù) 在哪 (或者說 在哪個 context 中)來渲染它們不同的樣子。
Context 的工作方式可能會讓你想起 CSS 屬性繼承。在 CSS 中,可以為一個 <div>
手動指定 color: blue
,并且其中的任何 DOM 節(jié)點,無論多深,都會繼承那個顏色,除非中間的其他 DOM 節(jié)點用 color: green
來覆蓋它。類似地,在 React 中,覆蓋來自上層的某些 context 的唯一方法是將子組件包裹到一個提供不同值的 context provider 中。
在 CSS 中,諸如 color
和 background-color
之類的不同屬性不會覆蓋彼此。可以設(shè)置所有 <div>
的 color
為紅色,而不會影響 background-color
。類似地,不同的 React context 不會覆蓋彼此。通過 createContext()
創(chuàng)建的每個 context 都和其他 context 完全分離,只有使用和提供 那個特定的 context 的組件才會聯(lián)系在一起。一個組件可以輕松地使用或者提供許多不同的 context。
寫在使用 context 之前
使用 Context 看起來非常誘人!然而,這也意味著它也太容易被過度使用了。如果只想把一些 props 傳遞到多個層級中,這并不意味著需要把這些信息放到 context 里。
在使用 context 之前,可以考慮以下幾種替代方案:
- 從 傳遞 props 開始。 如果組件看起來不起眼,那么通過十幾個組件向下傳遞一堆 props 并不罕見。這有點像是在埋頭苦干,但是這樣做可以讓哪些組件用了哪些數(shù)據(jù)變得十分清晰!維護你代碼的人會很高興你用 props 讓數(shù)據(jù)流變得更加清晰。
-
抽象組件并 將 JSX 作為
children
傳遞 給它們。 如果通過很多層不使用該數(shù)據(jù)的中間組件(并且只會向下傳遞)來傳遞數(shù)據(jù),這通常意味著在此過程中忘記了抽象組件。舉個例子,可能想傳遞一些像posts
的數(shù)據(jù) props 到不會直接使用這個參數(shù)的組件,類似<Layout posts={posts} />
。取而代之的是,讓Layout
把children
當(dāng)做一個參數(shù),然后渲染<Layout><Posts posts={posts} /></Layout>
。這樣就減少了定義數(shù)據(jù)的組件和使用數(shù)據(jù)的組件之間的層級。
如果這兩種方法都不適合,再考慮使用 context。
Context 的使用場景
- 主題: 如果應(yīng)用允許用戶更改其外觀(例如暗夜模式),可以在應(yīng)用頂層放一個 context provider,并在需要調(diào)整其外觀的組件中使用該 context。
- 當(dāng)前賬戶: 許多組件可能需要知道當(dāng)前登錄的用戶信息。將它放到 context 中可以方便地在樹中的任何位置讀取它。某些應(yīng)用還允許同時操作多個賬戶(例如,以不同用戶的身份發(fā)表評論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數(shù)據(jù)的 provider 中會很方便。
- 路由: 大多數(shù)路由解決方案在其內(nèi)部使用 context 來保存當(dāng)前路由。這就是每個鏈接“知道”它是否處于活動狀態(tài)的方式。如果你創(chuàng)建自己的路由庫,你可能也會這么做。
- 狀態(tài)管理: 隨著應(yīng)用的增長,最終在靠近應(yīng)用頂部的位置可能會有很多 state。許多遙遠的下層組件可能想要修改它們。通常 將 reducer 與 context 搭配使用來管理復(fù)雜的狀態(tài)并將其傳遞給深層的組件來避免過多的麻煩。
Context 不局限于靜態(tài)值。如果在下一次渲染時傳遞不同的值,React 將會更新讀取它的所有下層組件!這就是 context 經(jīng)常和 state 結(jié)合使用的原因。文章來源:http://www.zghlxwxcb.cn/news/detail-698356.html
一般而言,如果樹中不同部分的遠距離組件需要某些信息,context 將會對你大有幫助。文章來源地址http://www.zghlxwxcb.cn/news/detail-698356.html
摘要
- Context 使組件向其下方的整個樹提供信息。
- 傳遞 Context 的方法:
- 通過
export const MyContext = createContext(defaultValue)
創(chuàng)建并導(dǎo)出 context。 - 在無論層級多深的任何子組件中,把 context 傳遞給
useContext(MyContext)
Hook 來讀取它。 - 在父組件中把 children 包在
<MyContext.Provider value={...}>
中來提供 context。
- 通過
- Context 會穿過中間的任何組件。
- Context 可以讓你寫出 “較為通用” 的組件。
- 在使用 context 之前,先試試傳遞 props 或者將 JSX 作為
children
傳遞。
到了這里,關(guān)于React 18 使用 Context 深層傳遞參數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!