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;
}