#React Context API — Props 傳送門
Context API 是從 v16.3 被加入的 API ,其幫助我們跨組件傳遞資料,它就像是在茫茫組件海裡開啟一道傳送門,把對應的 Props 送過去,好用程度堪比「Rick and Morty」中的那把 Portal Gun。
我就不複製貼上一堆文檔或是沒必要的文字來做文字小偷,直接廢話不多說開始:講講 Context ,到底解決了什麼問題。
常見的苦痛問題 — Props Drilling
來個思想實驗,假設今天你有一個組件得不停地把狀態往下傳你會怎麼做?
你肯定會把該傳下去的不管是 getter、setter ,總之就是傳到爆吧!
可是今天你要把一個Counter的 「加一」 功能做在孫子組件…
你組件.jsx
<div className="me">
<Child plusOne={plusOne}> //把方法傳給子組件
Child.jsx
export default({plusone})=> (
<div className="child">
<GrandChild plusone={plusone}>
</div>
)
兒子:「幹 」
這個感覺就像是你填了一個遺囑,決定把遺產分給孫子,但是卻把遺產交給你兒子,叫兒子記得傳下去 87% 像。
而這種問題我們稱之為 — Props Drilling。
試想:如果你的這個Prop,要傳給三四個不同層的組件,且有些是 setter 的部分(比如剛剛說的+1、-1的操作 setState 方法),有些是 getter 的部分(單純只要拿到 Counter 目前的值),你要怎麼做?
謎之音A:那就全局狀態啊! Redux 聽過嗎? MobX 也可以啊?通通拖出去,通通給我管起來。
(嗯,也行,如果 Project 越做越大,勢必會遇到狀態管理的問題,先做這步,以後 migration 才不會這麼痛苦。)
謎之音B:你不會全部寫成一個組件喔?
(幹,你可以滾了。)
Context API Solution
我們說了,Context 是一個如同傳送門一樣的存在,我們可以幻想一件事,如果 Context 很屌,那麼我們的組件應該會是這個樣子…
你組件.jsx
<你>
{/* 開傳送門 設定 Counter state 進去 */}
<Child>
{/* 開到這為止*/}
</你>
Child.jsx …傻也甭幹 就是把 GrandChild 給變出來
GrandChild.jsx
<>
/* 開傳送門 拿出Counter state*/
<p>{Counter.currentCount}</p>
/* 開到這裡為止*/
</>
實際上的 Context 還真的是這樣
讓我們一起把剛剛挖空的拼圖填上吧!
傳送門 Context.js
import {createContext} from 'react'
export default createContext(null);
你組件.jsx
import Context from './Context'
export default () => {
return (
<Context.Provider value={counter}>
<Child/>
</Context.Provider>
)
}
這裡解釋一下,所有罩在 Provider 底下的組件們,都收到 Context 大神的庇護,就跟在庇護之地的涅法雷姆一樣。
子組件.jsx (事不關己)
孫組件.jsx
import Context from '../../Context';
export default()=>{
return (
<Context.Consumer>
{value=> <p>{value.count}</p>}
</Cotext.Consumer>
)
}
透過 Consumer 提供的 Render props,把對應的狀態從傳統們抽出來,並且灌進去組件裡頭,這步驟也可以在目標的父組件來做,如果以這個例子來看的話就是子組件.jsx
。
使用 useContext
優雅的提出 Consumer 的 value 而不透過 Render Props。
在 React v16.8 的 Hooks API 裡頭,提供了一個官方的 Hook — useContext,可以無腦把 value 拿出來:
孫組件.jsx
import {useContext} from 'react';
import Context from '../../Context';
export default()=>{
const value = useContext(Context);
return (
<p>{value.count}</p>
)
}
舒服
而且每一個 Provider 樹內都能拿到 Provider 的「value」傳進去的變數,其底下的 Consumer 再依照 Context 來決定取誰出來用。
意味著你可以嵌套多個 Provider ,然後在各個組件裡頭決定要 use 哪個 Context 。
最後直接來看一個使用 Context + Hooks的例子吧