感覺很實用的東西!
先來認識 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 }) { 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
DemoInnerContent
傳DemoInnerButton
這個操作就叫做「Props Drilling」,因為我們不停的往下「鑽」。
所以 React 就提供了兩個 hook useContext
和 createContext
,用來解決這個問題。
useContext 與 createContext
來改寫剛剛的例子,先簡單說明用法:
- 建立一個
<Context.provider />
,可以想成是專門傳遞 Context 的一種 Component
- 在要子元件上從 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";
const TitleContext = createContext();
function DemoInnerButton() { 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 ( {} <TitleContext.Provider value={setTitle}> <div> Title value is: {title} <br /> <DemoInner /> </div> </TitleContext.Provider> ); }
|
這樣子寫之後就不用再每一層都寫 props
了,非常方便!
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'
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 const TitleContext = createContext();
|
子元件:
1 2 3 4 5 6 7 8 9 10
| import { useContext } from "react";
import { TitleContext } from "./TitleContext";
export default function DemoInnerButton() { 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'
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> ); }
|
大功告成!可喜可賀!
其他補充
Provider
裡面可以再包一層 Provider
Provider
可以在某一層被覆寫(在某層又包一層同樣的 Provider
value
可以傳任何東西,Array 或 Object 都行,然後接收者就會拿到對應的值。
- 可以透過 state 來動態更新
Provider
的 value