preface
CreateContext is an API provided by React for global state management. You can inject state through the Provider and obtain state through the Consumer or useContext API (useContext is recommended).
CreateContext makes communication between components easier, but can cause significant performance problems if used improperly. Below we discuss the causes of performance problems and how to optimize them.
Root cause of the performance problem
Let’s start with an example: createContext performance problem cause, and note the two problem points in this example.
import { useState, useContext, createContext } from "react";
import { useWhyDidYouUpdate } from "ahooks";
const ThemeCtx = createContext({});
export default function App() {
const [theme, setTheme] = useState("dark");
/** * Performance problem cause: * The parent component rendering themectx.provider causes all child components to render */
return (
<div className="App">
<ThemeCtx.Provider value={{ theme.setTheme}} >
<ChangeButton />
<Theme />
<Other />
</ThemeCtx.Provider>
</div>
);
}
function Theme() {
const ctx = useContext(ThemeCtx);
const { theme } = ctx;
useWhyDidYouUpdate("Theme", ctx);
return <div>theme: {theme}</div>;
}
function ChangeButton() {
const ctx = useContext(ThemeCtx);
const { setTheme } = ctx;
useWhyDidYouUpdate("Change", ctx);
// Problem 2: Unchanged value in value state causes component rendering
console.log("The setTheme hasn't changed and I shouldn't have rendered it either!!");
return (
<div>
<button
onClick={()= >setTheme((v) => (v === "light" ? "Dark" : "light"))} > Change theme</button>
</div>
);
}
function Other() {
// Problem 1: render child components independent of value state
console.log("Other render. I shouldn't have rerendered it!!");
return <div>Other component, to be fair, I shouldn't have rendered it!</div>;
}
Copy the code
Problem 1 (overall repeat rendering) :Provider
Component wrapped child components are all rendered
Provider will cause all child components to be re-rendered each time the themectx. Provider component is rendered. This is because the React. CreateElement (type, props: {},…). Each time the component is created, props: {} is a new object.
Problem 2 (partial repeat rendering) : useuseContext
Causes components to render
CreateContext is implemented according to the publish-subscribe model, and every time the Provider’s value changes, all components that use it (those that use useContext) are notified to re-render.
The solution
Above we analyzed the root cause of the problem, now we begin to solve the problem. Again, take a look at the optimized example: createContext performance optimization.
import { useState, useContext, createContext, useMemo } from "react";
import { useWhyDidYouUpdate } from "ahooks";
import "./styles.css";
const ThemeCtx = createContext({});
export default function App() {
return (
<div className="App">
<ThemeProvide>
<ChangeButton />
<Theme />
<Other />
</ThemeProvide>
</div>
);
}
function ThemeProvide({ children }) {
const [theme, setTheme] = useState("dark");
return (
<ThemeCtx.Provider value={{ theme.setTheme}} >
{children}
</ThemeCtx.Provider>
);
}
function Theme() {
const ctx = useContext(ThemeCtx);
const { theme } = ctx;
useWhyDidYouUpdate("Theme", ctx);
return <div>{theme}</div>;
// return <ThemeCtx.Consumer>{({ theme }) => <div>{theme}</div>}</ThemeCtx.Consumer>;
}
function ChangeButton() {
const ctx = useContext(ThemeCtx);
const { setTheme } = ctx;
useWhyDidYouUpdate("Change", ctx);
/** * Solution: use useMemo ** /
const dom = useMemo(() = > {
console.log("re-render Change");
return (
<div>
<button
onClick={()= >setTheme((v) => (v === "light" ? "Dark" : "light"))} > Change theme</button>
</div>
);
}, [setTheme]);
return dom;
}
function Other() {
console.log("Other render, actually I should not re-render!!");
return <div>Other, be reasonable, I shouldn't have done it!</div>;
}
Copy the code
Solving problem 1
The ThemeContext is pulled out and the child component is passed in via the children property of props. Children will not change even if themecontext.provider is re-rendered. This will not cause all child components to be re-rendered because of a value change.
Problem Solving 2
The above method can be used to solve the problem of repeated overall rendering, but the problem of local rendering is more complicated. We need to use useMemo to modify the sub-components one by one, or use React. Memo to refine the sub-components.
reference
UseContext adds react usecontext_React performance optimization to useMemo