React Hooks 系列之3 useContext

本系列將講述 React Hooks 的使用方法,從 useState 開始,將包含以下內容:css

  • useState
  • useEffect
  • useContext
  • useReducer
  • useCallBack
  • useMemo
  • useRef
  • custom hooks

掌握 React Hooks api 將更好的幫助你在工做中使用,對 React 的掌握更上一層樓。本系列將使用大量實例代碼和效果展現,很是易於初學者和複習使用。react

今天咱們講講 Context 對象和 useContext 的使用方法。算法

什麼是 Context Api

考慮這樣一種場景,若是組件樹結構以下,如今想從根節點傳遞一個 userName 的屬性到葉子節點 A D F,經過 props 的方式傳遞,會不可避免的傳遞經過 B C E,即便這些組件也沒有使用這個 userName 屬性。api

若是這樣的嵌套樹形結構有5層或10層,那麼將是災難式的開發維護體驗。若是能不通過中間的節點直接到達須要的地方就能夠避免這種問題,這時 Context api 就是來解決這個問題的。ide

Context api 是在組件樹中傳遞數據但不用每層都通過的一種 api。下面咱們一塊兒看看 Context Hook 的使用方法。函數

使用 Context

咱們舉個例子重點看下最右邊的分支,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個步驟

建立 context

在根節點 App.tsx 中使用 createContext() 來建立一個 context

const UserContext = React.createContext('')
複製代碼

建立一個 Context 對象。當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當前的 context 值。

只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。這有助於在不使用 Provider 包裝組件的狀況下對組件進行測試。注意:將 undefined 傳遞給 Provider 的 value 時,消費組件的 defaultValue 不會生效。

提供 Provider

在根節點中使用 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 複製代碼

在使用的節點處消費 Context

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 的狀況

多個 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 的代碼優雅問題。

useContext

舉個例子,咱們在上述的 demo 中的 component E 中經過 useContext 使用根節點建立的 Context。分爲如下步驟

  1. 從 react 對象中 import useContext 這個 hook api
  2. import 根節點建立的 Context 對象(能夠導入多個)
  3. 執行 useContext() 方法,將 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 的 context value 值。即便祖先使用 React.memoshouldComponentUpdate,也會在組件自己使用 useContext 時從新渲染。

能夠理解爲,useContext(MyContext) 至關於 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext(MyContext) 只是讓你可以讀取 context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用 <MyContext.Provider> 來爲下層組件提供 context。

至此,關於 useContext hook api 咱們已經掌握了使用方式,能夠看到經過 useContext 能夠極大的減少多個 Context 使用的代碼複雜的問題。

下一章將講講 useReducer 的使用方法。

相關文章
相關標籤/搜索