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:

  1. Split completely unrelated states into groups of states. Such assize 和 position.
  2. If some states are related or need to change together, they can be combined as a group of states. Such asleft 和 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:

  1. Passed to theuseMemoHow expensive is the function of? In the example above, considergetResolvedValueIs the function expensive? Most methods in JS are optimized, for exampleArray.map,Array.forEachAnd so on. If the operation you are performing is not expensive, there is no need to remember the return value. Otherwise, useuseMemoThe cost itself may exceed the cost of recalculating the value. Therefore, for some simple JS operations, we do not need to use ituseMemoTo “remember” its return value.
  2. Does the reference to the “memory” value change when the input is the same? In the example above, whenpage 和 typeAt the same time,resolvedValueWill the reference to the That’s where we need to think about itresolvedValueType of. ifresolvedValueIs an object, and due to the use of “functional programming” on our project, a new reference is generated for each function call. However, ifresolvedValueIs 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,ExpensiveComponentComponents 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:

  1. Is the function to remember expensive?
  2. Is the value returned original?
  3. Will the remembered value be used by other hooks or child components?

Scenarios where useMemo should be used

  1. 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 propsuseMemo.
  • All objects, arrays, functions, etc. exposed in custom hooks should be useduseMemo. To ensure that references do not change when the values are the same.
  • useContextIf theProviderThe value (level 1) defined in the value of theReact.memo, still causes the child re-render. In this case, it is still recommendeduseMemoKeep references consistent.
  1. Costly calculations
  • Such ascloneDeepA very large and very hierarchical data set

Scenarios where useMemo is not required

  1. If the value returned is the original value:string.boolean.null.undefined.number.symbol(not including dynamically declared Symbol), generally not neededuseMemo.
  2. 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 neededuseMemo.

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.

  1. useMemoanduseCallbackThey can’t be used blindly because they are implemented based on closures, which can eat up memory.
  2. Consider when dependencies change frequentlyUseMemo, useCallbackIs it cost-effective becauseuseCallbackFunction bodies are created frequently.useMemoCallbacks 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