This is the 10th day of my participation in the More text Challenge. For more details, see more text Challenge
preface
Hooks are new in React 16.8. It lets you use state and other React features without having to write a class
Hooks and functional components make it easier to develop React applications. This article introduces several methods for performance optimization of functional components.
Reduce render times
Use the React. Memo
React V16.6.0 provides the React.memo() API to solve the problem of the same props and still render components.
If your component renders the same result with the same props, you can improve the performance of the component by wrapping it in react.memo and memorizing the result of the component rendering. This means that in this case React will skip the render component and directly reuse the results of the most recent render.
The following is an example (from the official document) :
const MyComponent = React.memo(function MyComponent(props) {
/* Render with props */
});
Copy the code
Note: React. Memo
React.memo
Check only props changes. If the functional component has hooks for useState, useReducer, and useReducer inside, it will still rerender when the context changes.React.memo
This is a shallow comparison of props for complex objects. If you want to customize the comparison, you need to pass the comparison function as the second argument.
The following is an example (from the official document) :
function MyComponent(props) {
/* Render with props */
}
function areEqual(prevProps, nextProps) {
Return true if passing nextProps to render returns the same result as passing prevProps to render, false otherwise */
}
export default React.memo(MyComponent, areEqual);
Copy the code
Props passed to child components using the useCallBack, useMemo cache
The following code exists:
/ / the parent component
export default function App() {
const [appCount, setAppCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const handleAppAdd = () = > {
setAppCount(appCount + 1);
};
const handleChildAdd = () = > {
setChildCount(childCount + 1);
};
console.log('app render');
return (
<div>
<h2>App</h2>
<button onClick={handleAppAdd}>appCount + 1</button>
<p>appCount: {appCount}</p>
<h2>child</h2>
<button onClick={handleChildAdd}>childCount + 1</button>
<Child value={childCount} />
</div>
);
}
/ / the child component
import React from 'react';
const Child = props= > {
console.log('child render');
const { value } = props;
return <div>Child: {value}</div>;
};
export default React.memo(Child);
Copy the code
The above Child component has been optimized using React. Memo so that it will not be rerendered if only the parent appCount changes.
Now we are modifying the Child component:
import React from 'react';
const Child = props= > {
console.log('child render');
const { value, handleAdd } = props;
return <div>
<p>Child: {value}</p>
<button onClick={handleAdd}>btn in child: childCount + 1</button>
</div>;
};
export default React.memo(Child);
Copy the code
In the above code, we also added a button to the child component that can increment or subtraction ChildCount. When clicked, it calls the props. HandleAdd passed in the parent component.
// app.jsx
export default function App() {
const [appCount, setAppCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const handleAppAdd = () = > {
setAppCount(appCount + 1);
};
const handleChildAdd = () = > {
setChildCount(childCount + 1);
};
console.log('app render');
return (
<div>
<h2>App</h2>
<button onClick={handleAppAdd}>appCount + 1</button>
<p>appCount: {appCount}</p>
<h2>child</h2>
<button onClick={handleChildAdd}>childCount + 1</button>
<Child value={childCount} handleAdd={handleChildAdd} />
</div>
);
}
Copy the code
The above code passes the Child the props of the handleAdd. When we click appCount + 1 again, childCount does not change, but the Child is refreshed. The log looks like this:
From this we can see that it was the new props. HandleAdd passed to the Child that caused the re-render. Because handleAdd is regenerated as a new function each time in the App component, react. memo changes the props and rerender the Child.
So how do we solve the problem of functions on props? React. UseCallback can solve this problem.
Passing the inline callback function and the array of dependencies as arguments to useCallback returns a memoized version of the callback function that is updated only if one of the dependencies changes.
UseCallback (fn, deps) equals useMemo(() => fn, deps).
As you can see from the official documentation, useCallback returns the same function as long as deps are unchanged. Similarly, if props are complex objects, we can use useMemo to handle them.
Child repeats render’s problem, we deal with it as follows:
// App.js
const handleChildAdd = useCallback(() = > {
setChildCount(childCount + 1);
}, [childCount]);
Copy the code
Click here for sample code
Render optimization for placeholder components
We usually have a requirement that a child component has a placeholder area that needs to be passed in via props. We can reduce the rendering of the placeholder component by creating a react.element in the parent component.
The code is as follows:
const Content = () = > {
console.log('Content render');
return <div>content</div>;
};
/ / sample 1
export default function App() {
const Content = () = > {
console.log('Content render');
return <div>content</div>;
};
return <Child content={Content} />;
}
const Child = (props) = > {
const Content = props.content
return (
<>
<header>The head</header>
<main>
<Content />
</main>
</>)}/ / sample 2
export default function App() {
return <Child content={Content} />;
}
const Child = (props) = > {
const Content = props.content
return (
<>
<header>The head</header>
<main>
<Content />
</main>
</>)}/ / sample 3
export default function App() {
return <Child content={<Content />} / >;
}
const Child = (props) = > {
const Content = props.content
return (
<>
<header>The head</header>
<main>
{props.content}
</main>
</>)}Copy the code
In the above code, we need to pay attention to the way the Child component is passed into the content.
- Example 1: It is passed every time
render
It’s all regeneratedThe Content components - Example 2: The Content component is passed in
- Example 3: What is passed is
<Content />
In the form ofReact.Element
The performance comparison is as follows
- Example 1andExample 2It’s all coming in
Content
Components, they are inChild
Every timerender
Needs to be rerenderedContent
component - Example 1In the parent component
render
Will generate a newThe Content components, which leads to every timerender
In the process ofThe Content componentsThe generatedReact.Element
The correspondingtype
Will be different, soThe Content componentsIt’s constantly destroying and creating. - Example 3Relatively speakingThe optimal solution. The relativeExample 1The parent component
render
Does not destroy the creation whenContent
. The relativeExample 2.Child
componentrender
时Content
Components do not reworkrender
.
React.Context read and write separation
The read and write analysis section is fromThis article
Before separation:
const LogContext = React.createContext();
function LogProvider({ children }) {
const [logs, setLogs] = useState([]);
const addLog = (log) = > setLogs((prevLogs) = > [...prevLogs, log]);
return (
<LogContext.Provider value={{ logs.addLog}} >
{children}
</LogContext.Provider>); } In the morning dream//juejin.cn/post/6889247428797530126Source: gold mining copyright belongs to the author. Commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.Copy the code
When the Provider’s value changes, all the consuming components within it are rerendered. Providers and their internal Consumer components are not subject to the shouldComponentUpdate function, so consumer components can update if their ancestors quit updating.
In the pre-optimized code, value is passed in as soon as setLogs update the state. All the functions that use logContext will be updated.
The optimized code:
function LogProvider({ children }) {
const [logs, setLogs] = useState([]);
const addLog = useCallback((log) = > {
setLogs((prevLogs) = >[...prevLogs, log]); } []);return (
<LogDispatcherContext.Provider value={addLog}>
<LogStateContext.Provider value={logs}>
{children}
</LogStateContext.Provider>
</LogDispatcherContext.Provider>); } In the morning dream//juejin.cn/post/6889247428797530126Source: gold mining copyright belongs to the author. Commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.Copy the code
Above we use LogDispatcherContext and LogStateContext to separate read and write. Components that only use addLog are not refreshed as logs change.
Lazy initial value
For lazy initial values, refer to the hook-FAQ section of the React official documentation
Create the initial state lazily
Normal use of useState:
const [state, setState] = useState(initialState);
Copy the code
The initialState parameter is only used in the initial rendering of the component and is ignored in subsequent renderings. If the initial state needs to be obtained through complex calculations, you can pass in a function that evaluates and returns the initial state, which is called only at the time of the initial rendering.
Lazy initial state document
Lazily creates the initial value of useRef()
UseRef does not accept a special function overload as useState does. Instead, you can write your own functions to create and set them to lazy
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver is created lazily only once
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// Call getObserver() when you need it
// ...
}
Copy the code
The resources
- React official documents
- hooks-faq
- What did I learn at work writing React? Performance Optimization