本系列將講述 React Hooks 的使用方法,從 useState 開始,將包含以下內容:css
掌握 React Hooks api 將更好的幫助你在工做中使用,對 React 的掌握更上一層樓。本系列將使用大量實例代碼和效果展現,很是易於初學者和複習使用。react
今天咱們講講 Context 對象和 useContext 的使用方法。算法
考慮這樣一種場景,若是組件樹結構以下,如今想從根節點傳遞一個 userName 的屬性到葉子節點 A D F,經過 props 的方式傳遞,會不可避免的傳遞經過 B C E,即便這些組件也沒有使用這個 userName 屬性。api
若是這樣的嵌套樹形結構有5層或10層,那麼將是災難式的開發維護體驗。若是能不通過中間的節點直接到達須要的地方就能夠避免這種問題,這時 Context api 就是來解決這個問題的。ide
Context api 是在組件樹中傳遞數據但不用每層都通過的一種 api。下面咱們一塊兒看看 Context Hook 的使用方法。函數
咱們舉個例子重點看下最右邊的分支,C E F,從根節點傳遞一個變量 username 到 F 節點。測試
咱們先建立好 App, ComponentC, ComponentE, ComponentF, 以下spa
App.tsxcode
import React from 'react'
import './App.css'
import ComponentC from './components/16ComponentC'
const App = () => {
return (
<div className="App"> <ComponentC /> </div>
)
}
export default App
複製代碼
ComponentC.tsxcomponent
import React from 'react'
import ComponentE from './16ComponentE'
function ComponentC() {
return (
<div> <ComponentE /> </div>
)
}
export default ComponentC
複製代碼
ComponentE.tsx
import React from 'react'
import ComponentF from './16ComponentF'
function ComponentE() {
return (
<div> <ComponentF /> </div>
)
}
export default ComponentE
複製代碼
ComponentF.tsx
import React from 'react'
function ComponentF() {
return (
<div> ComponentF </div>
)
}
export default ComponentF
複製代碼
頁面展現以下:
接下來咱們來研究如何使用 Context 將 username 從 App 傳遞到 ComponentF,共分爲如下3個步驟
在根節點 App.tsx 中使用 createContext()
來建立一個 context
const UserContext = React.createContext('')
複製代碼
建立一個 Context 對象。當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當前的 context 值。
只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。這有助於在不使用 Provider 包裝組件的狀況下對組件進行測試。注意:將 undefined 傳遞給 Provider 的 value 時,消費組件的 defaultValue 不會生效。
在根節點中使用 Provider 包裹子節點,將 context 提供給子節點
<UserContext.Provider value={'chuanshi'}>
<ComponentC /> </UserContext.Provider> 複製代碼
每一個 Context 對象都會返回一個 Provider React 組件,它容許消費組件訂閱 context 的變化。
Provider 接收一個
value
屬性,傳遞給消費組件。一個 Provider 能夠和多個消費組件有對應關係。多個 Provider 也能夠嵌套使用,裏層的會覆蓋外層的數據。當 Provider 的
value
值發生變化時,它內部的全部消費組件都會從新渲染。Provider 及其內部 consumer 組件都不受制於shouldComponentUpdate
函數,所以當 consumer 組件在其祖先組件退出更新的狀況下也能更新。經過新舊值檢測來肯定變化,使用了與 Object.is 相同的算法。
別忘了將以前定義好的 Context export 出去,以便在子孫節點中引入
export const UserContext = React.createContext('')
複製代碼
此時 App.tsx 的完整代碼爲
import React from 'react'
import './App.css'
import ComponentC from './components/16ComponentC'
export const UserContext = React.createContext('')
const App = () => {
return (
<div className="App"> <UserContext.Provider value={'chuanshi'}> <ComponentC /> </UserContext.Provider> </div> ) } export default App 複製代碼
import context 對象
import { UserContext } from '../App'
複製代碼
使用 Consumer 進行消費
<UserContext.Consumer>
{
(user) => (
<div>
User context value {user}
</div>
)
}
</UserContext.Consumer>
複製代碼
這裏,React 組件也能夠訂閱到 context 變動。這能讓你在函數式組件中完成訂閱 context。
這須要函數做爲子元素(function as a child)這種作法。這個函數接收當前的 context 值,返回一個 React 節點。傳遞給函數的 value 值等同於往上組件樹離這個 context 最近的 Provider 提供的 value 值。若是沒有對應的 Provider,value 參數等同於傳遞給 createContext() 的 defaultValue。
完整的 ComponentF.tsx 代碼以下
import React from 'react'
import { UserContext } from '../App'
function ComponentF() {
return (
<div> <UserContext.Consumer> { (user) => ( <div> User context value {user} </div> ) } </UserContext.Consumer> </div> ) } export default ComponentF 複製代碼
效果以下
目前看只有1個 Context 的時候狀況還好,下面咱們來看看有多個 Context 的狀況
咱們在 App.tsx 中再增長一個 Context
import React from 'react'
import './App.css'
import ComponentC from './components/16ComponentC'
export const UserContext = React.createContext('')
export const ChannelContext = React.createContext('')
const App = () => {
return (
<div className="App">
<UserContext.Provider value={'chuanshi'}>
<ChannelContext.Provider value={'code volution'}>
<ComponentC />
</ChannelContext.Provider>
</UserContext.Provider>
</div>
)
}
export default App
複製代碼
接下來在 component F 中消費它們
import React from 'react'
import { UserContext, ChannelContext } from '../App'
function ComponentF() {
return (
<div>
<UserContext.Consumer>
{
(user) => (
<ChannelContext.Consumer>
{
(channel) => (
<div>
User context value {user}, channel value {channel}
</div>
)
}
</ChannelContext.Consumer>
)
}
</UserContext.Consumer>
</div>
)
}
export default ComponentF
複製代碼
頁面展現以下
雖然代碼運行沒有問題,可是美觀性和可讀性都不太好,若是使用多個 Context,有個更好的方法,就是使用 Context hook 來解決消費多個 Context 的代碼優雅問題。
舉個例子,咱們在上述的 demo 中的 component E 中經過 useContext
使用根節點建立的 Context。分爲如下步驟
useContext
這個 hook apiuseContext()
方法,將 Context 傳入ComponentE 完整代碼:
import React, { useContext } from 'react'
import ComponentF from './16ComponentF'
import {UserContext, ChannelContext} from '../App'
function ComponentE() {
const user = useContext(UserContext)
const channel = useContext(ChannelContext)
return (
<div> <ComponentF /> --- <br/> {user} - {channel} </div>
)
}
export default ComponentE
複製代碼
頁面展現以下
其關鍵的一行代碼以下
const value = useContext(MyContext)
複製代碼
useContext 方法接收一個 context 對象(
React.createContext
的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的<MyContext.Provider>
的value
prop 決定。當組件上層最近的
<MyContext.Provider>
更新時,該 Hook 會觸發重渲染,並使用最新傳遞給MyContext
provider 的 contextvalue
值。即便祖先使用React.memo
或shouldComponentUpdate
,也會在組件自己使用useContext
時從新渲染。能夠理解爲,
useContext(MyContext)
至關於 class 組件中的static contextType = MyContext
或者<MyContext.Consumer>
。
useContext(MyContext)
只是讓你可以讀取 context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用<MyContext.Provider>
來爲下層組件提供 context。
至此,關於 useContext hook api 咱們已經掌握了使用方式,能夠看到經過 useContext 能夠極大的減少多個 Context 使用的代碼複雜的問題。
下一章將講講 useReducer 的使用方法。