Once the parent component is rendered, all child components are rendered, even though nothing has changed for that child component. In this case, rendering of the child component becomes redundant. Here are a few ways to handle it:
shouldComponentUpdate
The ShouldComponentUpdate lifecycle function internally uses a shallow comparison between props and state to determine whether to render. If prevProp and prevState are the same as the props, the prevState function returns false. The component will not be rendered.
shouldComponentUpdate(nextProps, nextState) { if (this.props.color ! == nextProps.color) { return true; } if (this.state.count ! == nextState.count) { return true; } return false; }Copy the code
React.pureComponent
Using PureComponent should help you build in a ShouldComponentUpdate lifecycle, for example:
Class Parent extends React.Component {state = {person: {name: 'hh'}, name: 'hh',}; ComponentDidMount () {setTimeout() => {this.setState({person: {name: 'componentDidMount'}, name: 'hh'}); this.handleChange(); }, 3000); } handleChange = () => {}; Render () {return (<Child name={this.state.name}) {person={this.state.name} // does not cause Child components to change Todo ={this.handlechange} // does not cause changes />); } } class Child extends React.PureComponent { render() { console.log('dfddddd'); return <div>{this.props.name}</div>; } } export default Parent;Copy the code
If the parent component is a function component, the Memo can be used in conjunction with the useCallback hook function to avoid repeated rendering. Use the Memo function to wrap the child component, whereas in the case of functions, you need to consider whether a function is passed to the child component to make useCallback.
import Child from './child.tsx'; const App = () => { const [state,setState] = useState({}) const handleTimerPickerChange = useCallback((hour: Number, minute: number) => {// Complex processing.... . setState(s=>({a:a+1})) }, []); return ( ... <Child onChange={handleTimerPickerChange} /> ) } export default App const Child:React.FC<> = () => { return ( ... ) } export default React.memo(Child)Copy the code
Avoid using inline objects
When using an inline object, React recreates a reference to the object each time it is rendered. This causes the component receiving the object to treat it as a different object, so the component always returns false for shallow comparisons of prop, causing the component to always re-render. You can use the ES6 extension operator to deconstruct the passed object. The component receives props of the basic type and does not re-render if it finds that the prop received has not changed. The following is an example:
// Don't do this! function Component(props) { const aProp = { someProp: 'someValue' } return <AnotherComponent style={{ margin: 0 }} aProp={aProp} /> } // Do this instead const styles = { margin: 0 }; function Component(props) { const aProp = { someProp: 'someValue' } return <AnotherComponent style={styles} {... aProp} /> }Copy the code
Avoid using anonymous functions
While anonymous functions are a good way to pass functions (especially functions that need to be called with another prop as an argument), they have different references on each render.
Function Component(props) {return <AnotherComponent onChange={() => function.callback (props. Id)} />} // function Component(props) { const handleChange = useCallback(() => props.callback(props.id), [props.id]); Return <AnotherComponent onChange={handleChange} />} // () => { this.props.callback(this.props.id) } render() { return <AnotherComponent onChange={this.handleChange} /> } }Copy the code
Use react. Fragment to avoid adding extra DOM
Sometimes we need to return multiple elements in a component, such as the following element, but React dictates that a component must have a parent element.
<h1>Hello world! </h1> <h1>Hello there! </h1> <h1>Hello there again! </h1>Copy the code
Doing so creates additional div’s that are not necessary. This results in the creation of many useless elements throughout the application:
function Component() { return ( <div> <h1>Hello world! </h1> <h1>Hello there! </h1> <h1>Hello there again! </h1> </div> ) }Copy the code
In fact, the more elements on the page, the more time it takes to load. To reduce unnecessary loading time, we can make react. Fragment to avoid creating unnecessary elements. Short form of Fragments <>
function Component() { return ( <React.Fragment> <h1>Hello world! </h1> <h1>Hello there! </h1> <h1>Hello there again! </h1> </React.Fragment> ) }Copy the code
Some thoughts?
useState
Should I use a single state variable or multiple state variables? Such as
// const [width, setWidth] = useState(100); const [height, setHeight] = useState(100); const [left, setLeft] = useState(0); const [top, setTop] = useState(0); State const [state, setState] = useState({width: 100, height: 100, left: 0, top: 0});Copy the code
Differences: 1. If a single state variable is used, the previous state needs to be merged each time the state is updated. Because the setState returned by useState replaces the original value. This is different from the this.setState of a Class component. This. setState automatically merges updated fields into the this.state object. Using multiple state variables makes state more granular, making it easier to split and combine logically. 3. If the granularity is too fine, the code becomes redundant. If the granularity is too coarser, the reusability of the code is reduced. Summary:
- Split completely unrelated states into groups of states. Such as
size
和position
. - If some states are related or need to change together, they can be combined as a group of states. Such as
left
和top
.width
和height
.
const [position, setPosition] = useState({top: 0, left: 0});
const [size, setSize] = useState({width: 100, height: 100});
Copy the code
useCallBack&&useMemo
Sometimes we evaluate the cost of creating a function. Creating functions in render each time can be expensive, so to avoid multiple function creation, use useMemo or useCallback. But for modern browsers, the cost of creating functions is minuscule. Therefore, there is no need to use useMemo or useCallback to save on this performance overhead. Of course, you can safely use useMemo or useCallback if you want to ensure that the references to the callback are equal every time you render.
useMemo
UseMemo has its own overhead. UseMemo “remembers” some values and, in subsequent render, retrieves the values from the dependent array and compares them to the last recorded value. If they are not equal, the callback is reexecuted, otherwise it returns the “remembered” value. The process itself consumes memory and computing resources. Therefore, excessive use of useMemo may affect the performance of a program.
To use useMemo properly, we need to understand the scenarios for which useMemo is applicable:
- Some calculations are expensive, so we need to “remember” the return value to avoid recalculating every render.
- We also need to “remember” this value as the reference to it changes, causing downstream components to be re-rendered.
interface Props {
page: number;
type: string;
}
const App = ({page, type}: Props) => {
const result = useMemo(() => {
return getResult(page, type);
}, [page, type]);
return <child result={result}/>;
};
Copy the code
In the above example, ExpensiveComponent is expensive to render. Therefore, when the reference to the resolvedValue changes, the author does not want to re-render the component. Therefore, the author uses useMemo to avoid changing its reference to the resolvedValue every time render recalculates, thus making the downstream component re-render.
This is a valid concern, but before using useMemo, we should consider two questions:
- Passed to the
useMemo
How expensive is the function of? In the example above, considergetResolvedValue
Is the function expensive? Most methods in JS are optimized, for exampleArray.map
,Array.forEach
And so on. If the operation you are performing is not expensive, there is no need to remember the return value. Otherwise, useuseMemo
The cost itself may exceed the cost of recalculating the value. Therefore, for some simple JS operations, we do not need to use ituseMemo
To “remember” its return value. - Does the reference to the “memory” value change when the input is the same? In the example above, when
page
和type
At the same time,resolvedValue
Will the reference to the That’s where we need to think about itresolvedValue
Type of. ifresolvedValue
Is an object, and due to the use of “functional programming” on our project, a new reference is generated for each function call. However, ifresolvedValue
Is a primitive value (string
.boolean
.null
.undefined
.number
.symbol
), there is no such thing as a “reference”, the calculated value must be the same every time. In other words,ExpensiveComponent
Components are not re-rendered.
Therefore, if the overhead of getResolvedValue is small and resolvedValue returns a primitive value such as a string, we can remove useMemo altogether. Therefore, before using useMemo, we might ask ourselves a few questions:
- Is the function to remember expensive?
- Is the value returned original?
- Will the remembered value be used by other hooks or child components?
Scenarios where useMemo should be used
- Keep references equal
- Object, array, and functions used internally by components should be used if they are used in dependent arrays of other hooks or passed to downstream components as props
useMemo
. - All objects, arrays, functions, etc. exposed in custom hooks should be used
useMemo
. To ensure that references do not change when the values are the same. - use
Context
If theProvider
The value (level 1) defined in the value of theReact.memo
, still causes the child re-render. In this case, it is still recommendeduseMemo
Keep references consistent.
- Costly calculations
- Such as
cloneDeep
A very large and very hierarchical data set
Scenarios where useMemo is not required
- If the value returned is the original value:
string
.boolean
.null
.undefined
.number
.symbol
(not including dynamically declared Symbol), generally not neededuseMemo
. - Object, array, and functions that are used only within components (and are not passed as props to subcomponents) and are not used in dependency arrays of other hooks are generally not needed
useMemo
.
UseMemo is primarily used for child components rather than functions, which use useCallback. If the child component is expensive, useMemo can prevent the child component from rerender because the parent component changes. UseCallback is just a syntactic sugar for useMemo, and can be used if the return value is a function. Of course, it might be more convenient to use useCallback for individual functions. But for a set of functions, useMemo provides better uniformity.
useMemo
anduseCallback
They can’t be used blindly because they are implemented based on closures, which can eat up memory.- Consider when dependencies change frequently
UseMemo, useCallback
Is it cost-effective becauseuseCallback
Function bodies are created frequently.useMemo
Callbacks are created frequently.
Custom hooks
Business logic reuse can be realized: multiple components need to set the width of an element according to the window size
export default function useWidth() { const [refWidth,setRefWidth]= useState(0) useEffect(() => { window.addEventListener('resize', handleResize); / / to monitor window size change const clientW = document. The documentElement. ClientWidth; handleResize(clientW) return () => { window.removeEventListener('resize', handleResize); }; } []); handleResize = (clientW) => { setRefWidth(clientW) }; return refWidth }Copy the code