iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0

本文章將提及以下內容

  • props傳遞的遇到什麼問題—propDrilling
  • 使用context解決propDrilling
    • Setp1 創建context環境-createContext()
    • Setp2 使用所建立的context將值設定給Provider
    • Step3 使用Consumer提取值
  • 使用useContext注意事項
  • useContext的問題
  • 放棄使用useContext?

props傳遞的遇到什麼問題—propDrilling

我們將component比擬成電梯,因此取名叫做Elevator的component,最上層是App.js、最下層是Elevator3.js

觀看以下import的階層關係

  1. App import Elevator1
  2. Elevator1 import Elevator2
  3. Elevator2 import Elevator3

範例如下

//App.js檔案
import React, { useState } from 'react'
import Elevator1 from './Elevator1'
const App = () => {
  const [user, setUser] = useState("Tim");
  return (
    <>
      <Elevator1 user={user} />
    </>
  );
};
export default App;
//Elevator1.js檔案
import React from 'react'
import Elevator2 from './Elevator2'
const Elevator1 = ({ user }) => {//為了向下傳遞接收上層傳來的props
  return (
    <>
       //為了向下傳遞必須撰寫props
      <Elevator2 user={user} />
    </>
  )
}
export default Elevator1
//Elevator2.js檔案
import React from 'react'
import Elevator3 from './Elevator3'
const Elevator2 = ({ user }) => {//為了向下傳遞接收上層傳來的props
  return (
    <>
      //為了向下傳遞必須撰寫props
      <Elevator3 user={user} />    
    </>
  )
}
export default Elevator2
//Elevator3.js檔案
import React from 'react'
const Elevator3 = ({ user }) => {
  return (
    <>
      {user}
    </>
  )
}
export default Elevator3

如果想要將App的值傳遞到Elevator3的話就得在每一層藉由props傳遞,其中Elevator2和Elevator1都沒有用到這個變數卻還是得宣告在props,因此會出現(Prop Drilling)過度傳遞問題。

使用context解決propDrilling

為了避免過度傳遞,react官方提出可以使用context(備註)來解決上述問題

備註:如果你去查context的中文,得到的翻譯會是上下文、來龍去脈、背景、我們可以比喻成我們創造了一個背景空間,提供橋梁使得各個元件來這裡就可以得到共用值。

使用context我們關注以下三點

  1. createContext()
  2. Context.Provider
  3. Context.Consumer

另外useContext只是Context.Consumer的語法糖,稍後會提到解釋。

Setp1 創建context環境-createContext()

首先我們先引入createContext,他是用來製造context的函式。
這邊我將檔案給分離出來命名成createUseContext.js
程式碼如下

import React, { createContext } from 'react'
export const UserContext = createContext();

Setp2 使用所建立的context將值設定給Provider

第二步引入剛剛所創建的檔案後,將準備值提供給Provider。

使用方式是先將剛剛所建立的context帶入到jsx裡面,透過物件取值提取Provider並設定props屬性為value,而value的值是你要共用的值,以下面範例所指的共用的值就是user這個變數。

Provider需要放在component較上層的地方,以這個範例來說App是Elevator1、Elevator2、Elevator3的上層。

在App.js引入這支檔案

import React, { useState } from "react";
import { UserContext } from './createUserContext';
import Elevator1 from './Elevator1'
const App = () => {
  const [user, setUser] = useState("Tim");
  return (
    <>
      <UserContext.Provider value={user}>
        <Elevator1 />
      </UserContext.Provider>
    </>

  );
}
export default App

Step3 使用Consumer提取值

最後要在使用值的地方藉由Consumer來提取值,記得先引入剛剛所建立的UserContext,並且使用contextconsumer來提取共用的值。範例如下

//在Elevator3.js
import React from 'react'
import { UserContext } from './createUserContext'
const Elevator3 = () => {
  return (
    <>
      <UserContext.Consumer>
        {value => <h1>{value}</h1>}
      </UserContext.Consumer>
    </>
  )
}
export default Elevator3

如期我們就可以在Elevator3提取到value

使用useContext

由於要提取共用的值需要撰寫<UserContext.Consumer>{value => <h1>{value}</h1>}的語法過於冗長,因此使用useContext可以簡化。

使用的方式將剛剛所建立的context整個帶入到useContext函式裡面作為參數即可,useContext回傳的值就會是在Provider所建立的值。
範例如下

//在Elevator3.js
import React, { useContext } from 'react'
import { UserContext } from './createUserContext'
const Elevator3 = () => {
  const name = useContext(UserContext);
  return (
    <>
        <h1>{name}</h1>
    </>
  )
}
export default Elevator3

使用useContext注意事項

官方提到以下幾個重點

useContext 的參數必需為 context object 自己

  • 正確: useContext(MyContext)
  • 錯誤: useContext(MyContext.Consumer)
  • 錯誤: useContext(MyContext.Provider)
    他只是簡化了consumer的寫法,在最上層一樣需要撰寫Provider

巢狀的context僅會顯示內層

以先前的範例如果在Provider撰寫如下的方式,最後consumer只會取得比較靠近的provider的值。
範例如下

<UserContext.Provider value={user}>
  <UserContext.Provider value={"test"}>
    <Elevator1 />
  </UserContext.Provider >
</UserContext.Provider>

useContext的問題

當context的state改變的時候底下的consume將全部重新渲染

換句話說,如果整個Application的state只使用一個context的話,那麼將會造成大量的重新渲染。

長久累積將會產生效能問題。

放棄使用useContext?

既然官方提出consumer這個hook一定有它的用處,

面對不常變動的state就可以考慮使用useContext

另外面對重新渲染的問題也可以透過宣告不同的context來解決或是搭配useMemo來做最佳化效能處理。

官方issue有提出一些方式可以參考看看或是也可以選擇使用知名第三方套件redux的解決方式。

參考資料

上一篇
從useRef理解forwardRef再解說useImperativeHandle—目的與用法
下一篇
為什麼useReducer,reducer詞彙解釋—用流程圖解釋useReducer
系列文
從Create到React—用來實作使用者介面的JavaScript函式庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言