React 的第八個 hook:useContext 與 createContext

感覺很實用的東西!

先來認識 Props Drilling

在介紹這兩個 hook 前可以先認識什麼是「Props Drilling」,這是一個在專案規模比較大的時候時一定會碰到的經典問題。

以前在寫 Vue 的時後好像就碰過類似的問題,就是當父元件要傳遞的 props 非常深的時候,code 會極其難看,來看個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useState } from "react";

function DemoInnerButton({ setTitle }) {
// 用 props 拿到的 function 改變 title
return <button onClick={() => setTitle("New title!")}>change title</button>;
}

function DemoInnerContent({ setTitle }) {
return <DemoInnerButton setTitle={setTitle} />;
}

function DemoInner({ setTitle }) {
return <DemoInnerContent setTitle={setTitle} />;
}

export default function Demo() {
const [title, setTitle] = useState("This is title");
return (
<div>
Title value is: {title} <br />
<DemoInner setTitle={setTitle} />
</div>
);
}

簡單來說,Demo 有一個 title 的 state,現在想要在最底下的子元件 DemoInnerButton 按下按鈕時更新 state。

為了讓 DemoInnerButton 拿到 setTitle 的 setter,就得像這樣子一直往下傳:

  • Demo 傳給 DemoInner
  • DemoInner 傳給 DemoInnerContent
  • DemoInnerContentDemoInnerButton

這個操作就叫做「Props Drilling」,因為我們不停的往下「鑽」。

所以 React 就提供了兩個 hook useContextcreateContext ,用來解決這個問題。

useContext 與 createContext

來改寫剛剛的例子,先簡單說明用法:

  1. 建立一個 <Context.provider />,可以想成是專門傳遞 Context 的一種 Component
  2. 在要子元件上從 Context 身上把需要的 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
27
28
29
30
31
32
33
import { useState, createContext, useContext } from "react";

// 建立 Context Component
// 查網路的資料是說可以傳入 props 的預設值,當使用 Provider 沒傳 value 時就會套用
// 不過我實測沒效,不確定是版本問題還是有改規則,總之還是乖乖傳 value 吧!
const TitleContext = createContext();

function DemoInnerButton() {
// 直接指定 Context 拿出 props
const setTitle = useContext(TitleContext);
return <button onClick={() => setTitle("New title!")}>change title</button>;
}

function DemoInnerContent() {
return <DemoInnerButton />;
}

function DemoInner() {
return <DemoInnerContent />;
}

function Demo() {
const [title, setTitle] = useState("This is title");
return (
{/* 在最外層包一層 Provider */}
<TitleContext.Provider value={setTitle}>
<div>
Title value is: {title} <br />
<DemoInner />
</div>
</TitleContext.Provider>
);
}

這樣子寫之後就不用再每一層都寫 props 了,非常方便!

context-01

OK,這樣確實解決了。不過有一個問題,「當父子元件沒有寫在同一支檔案裡的時候,子元件要怎麼存取 TitleContext?」

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
import { useState, createContext, useContext } from "react";
// 拆出去的子元件
import DemoInnerButton from './DemoChild'

// 寫在這裡的 Context
const TitleContext = createContext();


function DemoInnerContent() {
return <DemoInnerButton />;
}

function DemoInner() {
return <DemoInnerContent />;
}

export default function Demo() {
const [title, setTitle] = useState("This is title");
return (
<TitleContext.Provider value={setTitle}>
<div>
Title value is: {title} <br />
<DemoInner />
</div>
</TitleContext.Provider>
);
}

問得好!我一開始也想過。不過解法還蠻直覺的,就是先把 Context 抽出去變成一支檔案,在 import 進來就好啦。(可以開一個 store 資料夾來存,資料很多的話)

Context:

1
2
3
import { createContext } from "react";
// export 出去
export const TitleContext = createContext();

子元件:

1
2
3
4
5
6
7
8
9
10
import { useContext } from "react";
// 引入 Context
import { TitleContext } from "./TitleContext";

export default function DemoInnerButton() {
// 這邊就能拿到 Context 了
const setTitle = useContext(TitleContext);
return <button onClick={() => setTitle("New title!")}>change title</button>;
}

父元件:

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
import { useState } from "react";
// 引入子元件
import DemoInnerButton from './DemoChild'
// 引入 Context
import { TitleContext } from './TitleContext'

function DemoInnerContent() {
return <DemoInnerButton />;
}

function DemoInner() {
return <DemoInnerContent />;
}

export default function Demo() {
const [title, setTitle] = useState("This is title");
return (
<TitleContext.Provider value={setTitle}>
<div>
Title value is: {title} <br />
<DemoInner />
</div>
</TitleContext.Provider>
);
}

大功告成!可喜可賀!

context-02

其他補充

  • Provider 裡面可以再包一層 Provider
  • Provider 可以在某一層被覆寫(在某層又包一層同樣的 Provider
  • value 可以傳任何東西,Array 或 Object 都行,然後接收者就會拿到對應的值。
  • 可以透過 state 來動態更新 Providervalue
mentor-program-day115 React class component 的生命週期
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×