SoFunction
Updated on 2025-03-10

Re-rendering class components and function components in React

origin

ReactRe-rendering means that in a class function, it will be executed again.renderFunction, similarFlutterIn-housebuildFunction, function component, will be executed again

ReactComponents are in component statestateor component propertiespropsWhen changes are made, they will be re-rendered. The conditions are simple, but in fact, if you are not careful, it will cause disastrous re-rendering.

Class Components

Why talk about the class components first, how to talk about them, and better understand them? There are also some common interview questions that were popular in the past few years

ReactIn-housesetStateWhen is it synchronous and when is it asynchronous

React setStateHow to get the lateststate

What is the output value of the following code and how the page display changes

  test = () => {
    // s1 = 1
    const { s1 } = ;
    ({ s1: s1 + 1});
    ({ s1: s1 + 1});
    ({ s1: s1 + 1});
    (s1)
  };
  render() {
    return (
      <div>
        <button onClick={}>Button</button>
        <div>{.s1}</div>
      </div>
    );
  }

See these types of interview questions and be familiar with themReactYou can definitely answer the transaction mechanism, after all, isn't it difficult, ha? you do not knowReactTransaction mechanism? Baidu|Google|360|Sogou|Bing React Transaction Mechanism

React synthesis events

existReactThe event triggered by the component will be bubbleddocument(existreact v17Medium YesreactThe mounted node, for example ('#app')), thenReactCollect event callbacks on the trigger path and distribute events.

  • Is this a sudden idea? If it is disabled, the event will be prohibited from bubbled through the native event at the node that triggers the event. Is it?ReactThe event cannot be triggered? That's true, I can't bubbling.ReactIt is impossible to collect and distribute events, please note that this bubbling is notReactThe bubbling of synthetic events.
  • Another point that can be imagined after diverging,ReactEven if the event triggered during the synthesis capture stage is still triggered after the native bubble event is triggered
reactEventCallback = () => {
  // s1 s2 s3 are all 1  const { s1, s2, s3 } = ;
  ({ s1: s1 + 1 });
  ({ s2: s2 + 1 });
  ({ s3: s3 + 1 });
  ('after setState s1:', .s1);
  // 1 is still output here, page display 2, page is only re-rendered once};
<button
  onClick={}
  onClickCapture={}
>
  React Event
</button>
<div>
  S1: {s1} S2: {s2} S3: {s3}
</div>

Triggered setState after the timer callback

Timer callback executionsetStateIt is synchronous and can be executedsetStateThen, get the latest value directly, such as the following code

timerCallback = () => {
  setTimeout(() => {
    // s1 s2 s3 are all 1    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1 });
    ('after setState s1:', .s1);
    // Output 2 page rendering 3 times    ({ s2: s2 + 1 });
    ({ s3: s3 + 1 });
  });
};

Asynchronous function post-key trigger setState

Asynchronous function callback executionsetStateIt is synchronous and can be executedsetStateThen, get the latest value directly, such as the following code

asyncCallback = () => {
  ().then(() => {
    // s1 s2 s3 are all 1    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1 });
    ('after setState s1:', .s1);
    // Output 2 page rendering 3 times    ({ s2: s2 + 1 });
    ({ s3: s3 + 1 });
  });
};

Native event triggers

Native events are also unacceptableReactTransaction mechanism affects, sosetStatePerformance is also synchronous

componentDidMount() {
  const btn1 = ('native-event');
  btn1?.addEventListener('click', );
}
nativeCallback = () => {
  // s1 s2 s3 are all 1  const { s1, s2, s3 } = ;
  ({ s1: s1 + 1 });
  ('after setState s1:', .s1);
  // Output 2 page rendering 3 times  ({ s2: s2 + 1 });
  ({ s3: s3 + 1 });
};
<button >Native Event</button>

setState Modify properties that do not participate in rendering

setStateThe call will cause the component to be re-rendered, even if this state does not participate in page rendering, so please do not put non-rendering attributes.stateEven if it is putstate, please do not passsetStateModify this state and call it directly = xxxThat's fine, this property that does not participate in rendering is directly hung inthisJust above, refer to the picture below

// s1 s2 s3 is the rendered property, s4 is the non-rendered propertystate = {
  s1: 1,
  s2: 1,
  s3: 1,
  s4: 1,
};
s5 = 1;
changeNotUsedState = () => {
  const { s4 } = ;
  ({ s4: s4 + 1 });
  // The page will be re-rendered  // The page will not be rerendered  .s4 = 2;
  this.s5 = 2;
};
<div>
  S1: {s1} S2: {s2} S3: {s3}
</div>;

Just call setState, will the page be re-rendered?

Several situations are:

  • Call directlysetState, no parameters
  • setState,newstateAnd oldstateCompletely consistent, that is, the samestate
sameState = () => {
  const { s1 } = ;
  ({ s1 });
  // The page will be re-rendered};
noParams = () => {
  ({});
  // The page will be re-rendered};

These two situations are handled with ordinary modification of the statesetStateConsistent will cause re-rendering

Multiple rendering issues

Why do you need to mention the above? Look carefully. Here are many renderings.3Time, it is more suitable for our daily code writing, asynchronous function callbacks. After all, in timer callbacks or binding native events to components (it's okay to find trouble, right?), there are quite a few, but there are many asynchronous callbacks, such as network requests, change them.stateIt's quite common, but it's not possible to render it many times! But usesetStateIn fact, it is a new object merging mechanism that can merge the changed attributes into the new object and submit all changes at once, without calling them multiple times.setStateIt's

asyncCallbackMerge = () => {
  ().then(() => {
    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1, s2: s2 + 1, s3: s3 + 1 });
    ('after setState s1:', .s1);
    // Output 2 page rendering once  });
};

This way it can beReactAvoid multiple rendering issues in transaction flow

Test code

import React from 'react';
interface State {
  s1: number;
  s2: number;
  s3: number;
  s4: number;
}
// eslint-disable-next-line @iceworks/best-practices/recommend-functional-component
export default class TestClass extends <any, State> {
  renderTime: number;
  constructor(props: any) {
    super(props);
     = 0;
     = {
      s1: 1,
      s2: 1,
      s3: 1,
      s4: 1,
    };
  }
  componentDidMount() {
    const btn1 = ('native-event');
    const btn2 = ('native-event-async');
    btn1?.addEventListener('click', );
    btn2?.addEventListener('click', );
  }
  changeNotUsedState = () => {
    const { s4 } = ;
    ({ s4: s4 + 1 });
  };
  reactEventCallback = () => {
    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1 });
    ({ s2: s2 + 1 });
    ({ s3: s3 + 1 });
    ('after setState s1:', .s1);
  };
  timerCallback = () => {
    setTimeout(() => {
      const { s1, s2, s3 } = ;
      ({ s1: s1 + 1 });
      ('after setState s1:', .s1);
      ({ s2: s2 + 1 });
      ({ s3: s3 + 1 });
    });
  };
  asyncCallback = () => {
    ().then(() => {
      const { s1, s2, s3 } = ;
      ({ s1: s1 + 1 });
      ('after setState s1:', .s1);
      ({ s2: s2 + 1 });
      ({ s3: s3 + 1 });
    });
  };
  nativeCallback = () => {
    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1 });
    ('after setState s1:', .s1);
    ({ s2: s2 + 1 });
    ({ s3: s3 + 1 });
  };
  timerCallbackMerge = () => {
    setTimeout(() => {
      const { s1, s2, s3 } = ;
      ({ s1: s1 + 1, s2: s2 + 1, s3: s3 + 1 });
      ('after setState s1:', .s1);
    });
  };
  asyncCallbackMerge = () => {
    ().then(() => {
      const { s1, s2, s3 } = ;
      ({ s1: s1 + 1, s2: s2 + 1, s3: s3 + 1 });
      ('after setState s1:', .s1);
    });
  };
  nativeCallbackMerge = () => {
    const { s1, s2, s3 } = ;
    ({ s1: s1 + 1, s2: s2 + 1, s3: s3 + 1 });
    ('after setState s1:', .s1);
  };
  sameState = () => {
    const { s1, s2, s3 } = ;
    ({ s1 });
    ({ s2 });
    ({ s3 });
    ('after setState s1:', .s1);
  };
  withoutParams = () => {
    ({});
  };
  render() {
    ('renderTime', ++);
    const { s1, s2, s3 } = ;
    return (
      <div className="test">
        <button onClick={}>React Event</button>
        <button onClick={}>Timer Callback</button>
        <button onClick={}>Async Callback</button>
        <button >Native Event</button>
        <button onClick={}>Timer Callback Merge</button>
        <button onClick={}>Async Callback Merge</button>
        <button >Native Event Merge</button>
        <button onClick={}>Change Not Used State</button>
        <button onClick={}>React Event Set Same State</button>
        <button onClick={}>
          React Event SetState Without Params
        </button>
        <div>
          S1: {s1} S2: {s2} S3: {s3}
        </div>
      </div>
    );
  }
}

Function Components

The conditions for re-rendering of function components are the same as those of class components, and the properties of components arePropsand component statusStateWhen there are modifications, the component will be re-rendered, so the problem with class components also exists, and because of the function componentsstateIt's worse if it's not a person

React synthesis events

const reactEventCallback = () =&gt; {
  // S1 S2 S3 are all 1  setS1((i) =&gt; i + 1);
  setS2((i) =&gt; i + 1);
  setS3((i) =&gt; i + 1);
  // The page will only be rendered once, S1 S2 S3 are all 2};

Timer callback

const timerCallback = () =&gt; {
  setTimeout(() =&gt; {
    // S1 S2 S3 are all 1    setS1((i) =&gt; i + 1);
    setS2((i) =&gt; i + 1);
    setS3((i) =&gt; i + 1);
    // The page will only be rendered three times, S1 S2 S3 are all 2  });
};

Asynchronous function callback

const asyncCallback = () =&gt; {
  ().then(() =&gt; {
    // S1 S2 S3 are all 1    setS1((i) =&gt; i + 1);
    setS2((i) =&gt; i + 1);
    setS3((i) =&gt; i + 1);
    // The page will only be rendered three times, S1 S2 S3 are all 2  });
};

Native events

useEffect(() =&gt; {
  const handler = () =&gt; {
    // S1 S2 S3 are all 1    setS1((i) =&gt; i + 1);
    setS2((i) =&gt; i + 1);
    setS3((i) =&gt; i + 1);
    // The page will only be rendered three times, S1 S2 S3 are all 2  };
  ?.addEventListener('click', handler);
  return () =&gt; ?.removeEventListener('click', handler);
}, []);

Update unused status

const [s4, setS4] = useState&lt;number&gt;(1);
const unuseState = () =&gt; {
  setS4((s) =&gt; s + 1);
  // s4 === 2 The page is rendered once. S4 page is not used};

summary

All of the above situations areReact HookThe performance of the class component is exactly the same as that of the performance of the class component, and there is no difference, but there are also differences in the performance of the performance.

Different situations Set the same State

existReact HookSet the sameState, will not cause re-rendering, this is different from class components, but this is not necessarily the case.ReactOfficial documentation statement

If the state after you update the State Hook is the same as the current state, React will skip the rendering of the child component and will not trigger the execution of the effect. (React uses the comparison algorithm to compare state.)

It should be noted that React may still need to render the component before skipping rendering. However, since React does not render unnecessary "deep" nodes of the component tree, there is no need to worry. If you perform high overhead calculations during rendering, you can use useMemo to optimize.

Official stability mentioned, new and oldStateThe shallower is completely consistent and will not be re-rendered, but it may still lead to re-rendering.

// React Hook
const sameState = () =&gt; {
  setS1((i) =&gt; i);
  setS2((i) =&gt; i);
  setS3((i) =&gt; i);
  ();
  // The page will not be rerendered};
// In the class componentsameState = () =&gt; {
  const { s1, s2, s3 } = ;
  ({ s1 });
  ({ s2 });
  ({ s3 });
  ('after setState s1:', .s1);
  // The page will be re-rendered};

This feature exists, sometimes you want to get the latest onestate, don't want to add a functionstateDepend on or givestateAdd oneuseRef, you can use this function or thisstateLatest value

const sameState = () =&gt; {
  setS1((i) =&gt; {
    const latestS1 = i;
    // latestS1 is the latest value of S1, and you can handle some S1-related logic here    return latestS1;
  });
};

Avoid multiple renderings in React Hook

React HookmiddlestateIt is not an object, so the object will not be automatically merged and updated. How to solve this asynchronous function multiple times aftersetStateRe-rendering problem?

Merge all states into one object

const [state, setState] = useState({ s1: 1, s2: 1, s3: 1 });
setState((prevState) => {
  setTimeout(() => {
    const { s1, s2, s3 } = prevState;
    return { ...prevState, s1: s1 + 1, s2: s2 + 1, s3: s3 + 1 };
  });
});

Reference classIt is an object method, to put allstateWhen merged into a component and then update a certain property, call it directlysetStateThat is, it is exactly the same as the operation of the class component. This is a solution

Use useReducer

Although thishookIt is indeed low in presence, but multi-state components are replaced by thisuseStateIt's really good

const initialState = { s1: 1, s2: 1, s3: 1 };
function reducer(state, action) {
  switch () {
    case 'update':
      return { s1: state.s1 + 1, s2: state.s2 + 1, s3: state.s3 + 1 };
    default:
      return state;
  }
}
const [reducerState, dispatch] = useReducer(reducer, initialState);
const reducerDispatch = () => {
  setTimeout(() => {
    dispatch({ type: 'update' });
  });
};

The specific usage will not be expanded, andreduxNo big difference

The status is declared directly with Ref, and the updated function is called when it needs to be updated (not recommended)

// S4 does not participate in renderingconst [s4, setS4] = useState&lt;number&gt;(1);
// update is the dispatch of useReducer. The page is updated by calling it, which is much better than defining a state that does not render.const [, update] = useReducer((c) =&gt; c + 1, 0);
const state1Ref = useRef(1);
const state2Ref = useRef(1);
const unRefSetState = () =&gt; {
  // Priority update of the value of ref   += 1;
   += 1;
  setS4((i) =&gt; i + 1);
};
const unRefSetState = () =&gt; {
  // Priority update of the value of ref   += 1;
   += 1;
  update();
};
&lt;div&gt;
  state1Ref: {} state2Ref: {}
&lt;/div&gt;;

Do this to render thestatePut it hererefInside, there is an advantage that this does not need to be declared in the function.stateReliance, but there are many disadvantages. You must call it when updatingupdate, and at the same timerefIt's also strange to render

Custom Hook

CustomizeHookIf used in a component, any customizationHookChanges in the state will cause the component to be re-rendered, including those not used in the component, but are defined in customHookStatus in

Simple example, the following customizationhook,haveidanddataTwo states,idNot even exported, butidWhen it changes, it will still cause this referenceHookRe-rendering of components

// A simple custom Hook for requesting dataconst useDate = () =&gt; {
  const [id, setid] = useState&lt;number&gt;(0);
  const [data, setData] = useState&lt;any&gt;(null);
  useEffect(() =&gt; {
    fetch('The URL of the request data')
      .then((r) =&gt; ())
      .then((r) =&gt; {
        // Re-render the component        setid((i) =&gt; i + 1);
        // The component is re-rendered again        setData(r);
      });
  }, []);
  return data;
};
// Used in components, even if only data is exported, the id changes will also cause the component to be re-rendered. Therefore, when the component obtains data, the component will re-render twiceconst data = useDate();

Test code

// 
const useDate = () =&gt; {
  const [id, setid] = useState&lt;number&gt;(0);
  const [data, setData] = useState&lt;any&gt;(null);
  useEffect(() =&gt; {
    fetch('Data request address')
      .then((r) =&gt; ())
      .then((r) =&gt; {
        setid((i) =&gt; i + 1);
        setData(r);
      });
  }, []);
  return data;
};
import { useEffect, useReducer, useRef, useState } from 'react';
import useDate from './use-data';
const initialState = { s1: 1, s2: 1, s3: 1 };
function reducer(state, action) {
  switch () {
    case 'update':
      return { s1: state.s1 + 1, s2: state.s2 + 1, s3: state.s3 + 1 };
    default:
      return state;
  }
}
const TestHook = () =&gt; {
  const renderTimeRef = useRef&lt;number&gt;(0);
  const [s1, setS1] = useState&lt;number&gt;(1);
  const [s2, setS2] = useState&lt;number&gt;(1);
  const [s3, setS3] = useState&lt;number&gt;(1);
  const [s4, setS4] = useState&lt;number&gt;(1);
  const [, update] = useReducer((c) =&gt; c + 1, 0);
  const state1Ref = useRef(1);
  const state2Ref = useRef(1);
  const data = useDate();
  const [state, setState] = useState({ s1: 1, s2: 1, s3: 1 });
  const [reducerState, dispatch] = useReducer(reducer, initialState);
  const containerRef = useRef&lt;HTMLButtonElement&gt;(null);
  const reactEventCallback = () =&gt; {
    setS1((i) =&gt; i + 1);
    setS2((i) =&gt; i + 1);
    setS3((i) =&gt; i + 1);
  };
  const timerCallback = () =&gt; {
    setTimeout(() =&gt; {
      setS1((i) =&gt; i + 1);
      setS2((i) =&gt; i + 1);
      setS3((i) =&gt; i + 1);
    });
  };
  const asyncCallback = () =&gt; {
    ().then(() =&gt; {
      setS1((i) =&gt; i + 1);
      setS2((i) =&gt; i + 1);
      setS3((i) =&gt; i + 1);
    });
  };
  const unuseState = () =&gt; {
    setS4((i) =&gt; i + 1);
  };
  const unRefSetState = () =&gt; {
     += 1;
     += 1;
    setS4((i) =&gt; i + 1);
  };
  const unRefReducer = () =&gt; {
     += 1;
     += 1;
    update();
  };
  const sameState = () =&gt; {
    setS1((i) =&gt; i);
    setS2((i) =&gt; i);
    setS3((i) =&gt; i);
    ();
  };
  const mergeObjectSetState = () =&gt; {
    setTimeout(() =&gt; {
      setState((prevState) =&gt; {
        const { s1: prevS1, s2: prevS2, s3: prevS3 } = prevState;
        return { ...prevState, s1: prevS1 + 1, s2: prevS2 + 1, s3: prevS3 + 1 };
      });
    });
  };
  const reducerDispatch = () =&gt; {
    setTimeout(() =&gt; {
      dispatch({ type: 'update' });
    });
  };
  useEffect(() =&gt; {
    const handler = () =&gt; {
      setS1((i) =&gt; i + 1);
      setS2((i) =&gt; i + 1);
      setS3((i) =&gt; i + 1);
    };
    ?.addEventListener('click', handler);
    return () =&gt; ?.removeEventListener('click', handler);
  }, []);
  ('render Time Hook', ++);
  ('data', data);
  return (
    &lt;div className="test"&gt;
      &lt;button onClick={reactEventCallback}&gt;React Event&lt;/button&gt;
      &lt;button onClick={timerCallback}&gt;Timer Callback&lt;/button&gt;
      &lt;button onClick={asyncCallback}&gt;Async Callback&lt;/button&gt;
      &lt;button  ref={containerRef}&gt;
        Native Event
      &lt;/button&gt;
      &lt;button onClick={unuseState}&gt;Unuse State&lt;/button&gt;
      &lt;button onClick={sameState}&gt;Same State&lt;/button&gt;
      &lt;button onClick={mergeObjectSetState}&gt;Merge State Into an Object&lt;/button&gt;
      &lt;button onClick={reducerDispatch}&gt;Reducer Dispatch&lt;/button&gt;
      &lt;button onClick={unRefSetState}&gt;useRef As State With useState&lt;/button&gt;
      &lt;button onClick={unRefSetState}&gt;useRef As State With useReducer&lt;/button&gt;
      &lt;div&gt;
        S1: {s1} S2: {s2} S3: {s3}
      &lt;/div&gt;
      &lt;div&gt;
        Merge Object S1: {state.s1} S2: {state.s2} S3: {state.s3}
      &lt;/div&gt;
      &lt;div&gt;
        reducerState Object S1: {reducerState.s1} S2: {reducerState.s2} S3:{' '}
        {reducerState.s3}
      &lt;/div&gt;
      &lt;div&gt;
        state1Ref: {} state2Ref: {}
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
export default TestHook;

What should I do if I can’t remember the rules?

There are a lot of situations listed above, but these rules are inevitable.ReactThe two completely different re-rendering mechanisms caused by the transaction mechanism really make people feel a little disgusting.ReactThe official also noticed that since it is in the transaction flowsetStateCan merge, that's notReactCan the callback of the transaction flow be merged? The answer is OK.ReactThe official is actuallyReact V18middle,setStateCan merge, even in asynchronous callbacks, timer callbacks, or native event binding, the test code can be dropped directlyReact V18Try in the environment, even if the scene listed above will render multiple times, it will not be re-rendered multiple times.

You can see this address for details

Automatic batching for fewer renders in React 18

But,React V18It is best to record the above rules, which are very helpful for reducing the number of renderings.

The above is the detailed content of the re-rendering class components and function components in React. For more information about React re-rendering components, please follow my other related articles!