iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
Modern Web

React 新手村 - 填坑記系列 第 14

React 新手村 - 填坑記 - Day14 開發問題(十)

  • 分享至 

  • xImage
  •  

React Hooks - createContext 常見問題

createContent是React提供用來處理資料全域共享的API,
它可用來避免多層傳遞props或是元件之間無上下層關係的情況,
可以透過Provider component來存入資料,Consumer component或者useCoontext來獲取資料。
而雖然讓多層與組件使用資料時更方便,但如果使用不當則會發生效能議題

import { useState, useContext, createContext } from "react";
import axios from 'axios';

export const StoreContext = createContext({});

export default function App() {
  const [menuList, setMenuList] = useState([])
  
   useEffect(() => {
    const fetchMenuData = async () => {
      try{
        const result_menu = await axios('XXX')
        setMenuList(result_menu)
      }catch(error) {
      }
    }
    fetchMenuData()
  },[])
  
  //效能問題 StoreContext.Provider 父组件渲染導致所有子組件跟著渲染
  return (
    <div className="App">
      <StoreContext.Provider value={{ menuList, setMenuList }}>
        <ChangeButton />
        <Theme />
        <Other />
      </StoreContext.Provider>
    </div>
  );
}
//帶入的位置
function Theme() {
  const ctx = useContext(StoreContext);
  const { menuList } = ctx;
  return <div>theme: {menuList}</div>;
}
//變更資料狀態
function ChangeButton() {
  const ctx = useContext(StoreContext);
  const { setMenuList } = ctx;
  console.log("setMenuList 未改變,此處也不該渲染!!!");
  return (
    <div>
      <button
        onClick={() => setMenuList((v) => (v === "light" ? "dark" : "light"))}>變更menu</button>
    </div>
  );
}
//其餘組件
function Other() {
  console.log("Other render。此處不應發生渲染");
  return <div>other此處不應發生渲染</div>;
}

上述Code發生了兩個問題

問題一(整體重複渲染):

上述中StoreContext.Provider包住的所有子組件,當使用React.createElement(type, props: {}, ...)創建組件,每次props:{}都會是一個新的元件,就會造成Provider組件渲染,而這時導致整個包住的子組件跟著重新渲染。

問題二(局部重複渲染):

createContext是依照發布訂閱模式來實現值的變化,所以`Provider的value值每次發生變化時,就會通知所有使用useContext的組件重新渲染。

解決問題一:

把StoreContext抽離,讓子組件通過props的children來傳遞,這樣即使StoreContext.Provider重新渲染,children也不會改變,這樣就不會因為value值的改變而導致所有包含的子組件跟著重新渲染。

作法如下:

import { useState, useContext, createContext, useMemo } from "react";
import axios from 'axios';

export const StoreContext = createContext({});

export default function App() {
  return (
    <div className="App">
      <ContextProvider>
        <ChangeButton />
        <Theme />
        <Other />
      </ContextProvider>
    </div>
  );
}

function ContextProvider({children}){
    const [menuList, setMenuList] = useState([])
    useEffect(() => {
        const fetchMenuData = async () => {
          try{
            const result_menu = await axios('XXX')
            setMenuList(result_menu)
          }catch(error) {
          }
        }
        fetchMenuData()
    },[])
    return (
        <StoreContext.Provider value={{ menuList, setMenuList }}>
          {children}
        </StoreContext.Provider>
    );
}

解決問題二:

使用useMemo來「避免重複進行複雜耗時的計算」,而是不是每個子組件都使用useMemo就要在額外分析了。

作法如下:

function ChangeButton() {
  const ctx = useContext(StoreContext);
  const { setMenuList } = ctx;
  //使用 useMemo
  const dom = useMemo(() => {
    console.log("re-render Change");
    return (
        <div>
          <button
            onClick={() => setMenuList((v) => (v === "light" ? "dark" : "light"))}>變更menu</button>
        </div>
      );
  }, [setMenuList]);

  return dom;
}

上一篇
React 新手村 - 填坑記 - Day13 開發問題(九)
下一篇
React 新手村 - 填坑記 - Day15 生命週期知識(一)
系列文
React 新手村 - 填坑記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言