Render the bailout

React created fiber’s logic render and bailout.

  • render: callrenderFunction (component), returnsJSX, andold fiberfordiffAfter creatingfiber.
    • ClassComponentperformrenderMethods.Function ComponentExecute yourself.
  • bailout: does not performrenderReuse,old fiber.
  • redenrbailout:renderIf no update is required, execute the updatebailoutIn the case. For example,shouComponentUpdate.

In order to reduce render, we need to understand the logic of performing bailout.

Bailout function logic

Try to reuse fiber without render. Fiber multiplexing, determine whether the subtree of fiber (childLanes) has work.

  • Return child and continue traversing the subtree.
  • None: Returns NULL and skips the subtree.
function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) :Fiber | null {
  if(current ! = =null) {
    // Reuse previous context dependencies
    workInProgress.dependencies = current.dependencies;
  }
  // check whether there is work for childLanes
  if(! includesSomeLane(renderLanes, workInProgress.childLanes)) {// None, skip the subtree
    return null;
  }
  // The subtree has work, so continue traversing the subtree
    
  // workinprogress. child converts to workInProgress
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}
Copy the code

Fiber performs bailout timing

In fiber traversal, judge in the beginWork function. Prerequisite: This parameter must be update. So you have to have old fiber.

The condition of the bailout

Key points: props, Lanes, and individual components.

Render judgment before

Do not execute render, do bailout.

// Delete some code
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) :Fiber | null {
    // This is the update phase,
    if(current ! = =null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
      // props need updateoldProps ! == newProps ||// Old version context (not used now)
      hasLegacyContextChanged() ||
      // dev determines type, which is used for hot overloading(__DEV__ ? workInProgress.type ! == current.type :false)) {// Update required. (Does not necessarily mean updates, such as memo)
      didReceiveUpdate = true;
    } else {
      // Whether this fiber has lanes update task
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current,renderLanes);
      if (
        / / not updateLane! hasScheduledUpdateOrContext &&// There is no Suspend, error boundary passing.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        didReceiveUpdate = false;
        // No update, attempted bailout.
        // Most components will baliout directly.
        Suspend, Offscreen components may not perform bailout.
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      // There is an update task, but the props is not changed
      // The component is set to true when it is later updated definitively
      didReceiveUpdate = false; }}else {
    // mount
  }
  / /... The bailout code
}
Copy the code

You can derive oldProps === newProps & flags without updateLanes& flags without Suspend, error boundary when you attempt bailout.

  • Most components will proceed directlybaliout.
  • Suspend,OffscreenThe component may not executebailout.

oldProps === newProps

The bailout logic can be entered only when the props are universal.

JSX props is different every time?

Whenever render is executed, createElement is called to regenerate JSX. Each time the JSX props is a new object.

function createElement(type, config, children) {
   // New object every time
  const props = {};
   // Delete the rest of the code.
  return ReactElement( type, props, );
}

// The JSX function is used by v17 instead of createElement.
function jsx(type, config, children) {
   // New object every time
  const props = {};
   // Delete the rest of the code.
  return ReactElement( type, props, );
}
// Note: rootFiber props is null.
Copy the code

Meaning, just execute render, oldProps! NewProps == newProps = true.

The context value of the previous version is unchanged

After V16.3, the new context will be used, so ignore it and assume that the old context will not change.

Dev Environment determines the type

Dev, type ubiquitousness, used for thermal reloading. The production environment does not.

Lanes without task

Fiber’s Lanes have no task, continue with Baliout logic.

function checkScheduledUpdateOrContext(current: Fiber,renderLanes: Lanes,) :boolean {
  // Check whether fiber.lanes has a task
  const updateLanes = current.lanes;
  if (includesSomeLane(updateLanes, renderLanes)) {
    return true;
  }
  / /... Lazy context logic.
  / /... The lazy context is still being tested and is not enabled.
  return false;
}
Copy the code

Here are lanes that are not connected to the heart tree.

(workInProgress.flags & DidCapture) === NoFlags

Fiber does not Suspend, error pass, perform bailout. This is the end of the bailout logic.

After the render judgment

After redner executed, it was found that there was no need to update the case, tried bailout, and reduced the subtree render. Each component update is judged separately.

ClassComponent

// If update is required, delete non-update code
function updateClassInstance (){
  / /...
  // Whether an update is required
   const shouldUpdate =
    // Whether ForceUpdate exists
    checkHasForceUpdateAfterProcessing() ||
    // Check whether the component is to be updated
    // 1. If there is a shouldComponentUpdate, it should be controlled by it.
    // 2. If PureComponent, check whether props and state are equeal.
    // 3. ShouldComponentUpdate is not PureComponent.
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    )
  / /... Then execute the finishClassComponent
  return shouldUpdate
}

// bailout logic, delete code that is not bailout
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes,
) {
  // ...
  constdidCaptureError = (workInProgress.flags & DidCapture) ! == NoFlags;// No updates are required, and there are no error boundaries
  if(! shouldUpdate && ! didCaptureError) {// bailout
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  // ... 
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
Copy the code

There is no ForceUpdate& components do not need to update when performing bailout.

A situation where a component does not need to be updated
  • Judge in 3 logical order.
    1. If you have anyshouldComponentUpdateThey control it.
    2. ifPureComponent, the judgmentprops&stateWhether or notequeal.
    3. There is noshouldComponentUpdateIs notPureComponentIt needs to be updated.

FunctionComponent

// bailout logic, delete code that is not bailout
function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  / /... Processing function component
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );
  // current exists and does not need to be updated.
  // beginWroke didReceiveUpdate set to false.
  if(current ! = =null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
    
  // ...  
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
Copy the code

Update time & component does not need to update when doing bailout.

For example,
function A() {
    const [v, setV] = useState(1)
    console.log('A')
    return <div onClick={()= > setV(2)}><AA/></div>
}
function AA() {
    console.log('AA')
    return 'AA'
}
Copy the code

When first clicked on ‘A’, ‘AA’. When you click ‘A’ the second time. (Bailout tree). When the third click does not respond. (Same state, no update added).

MemoComponent

There are SimpleMemo components and Memo components. Both use SimpleMemo by default

updateMemoComponent
// Delete the code irrelevant to bailout logic
function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) :null | Fiber {
  if (current === null) {
    const type = Component.type;
    // Simple Memo, non-class, no compare, no default props,
    if (
      isSimpleFunctionComponent(type) &&
      Component.compare === null &&
      Component.defaultProps === undefined
    ) {
      let resolvedType = type;
      
      // Fiber is labeled SimpleMemoComponent
      / / this fiber follow-up, walk updateSimpleMemoComponent function, not to enter this function.
      workInProgress.tag = SimpleMemoComponent;
      workInProgress.type = resolvedType;
         / /... Create the SimpleMemo component
      return updateSimpleMemoComponent();
    }
      / /... The mount logic
    return child;
  }
    
  const currentChild = ((current.child: any): Fiber); 
  // Check if there is an updateLanes task, the logic is written above.
  const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current,renderLanes);
  // There is no update task
  if(! hasScheduledUpdateOrContext) {const prevProps = currentChild.memoizedProps;
    letcompare = Component.compare; compare = compare ! = =null ? compare : shallowEqual;
    // Caompare is the same and ref is congruent
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      / / the bailout
      returnbailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); }}/ /... Create the newChild
   const newChild = createWorkInProgress(currentChild, nextProps);
   return newChild;
}
Copy the code

updateSimpleMemoComponent

/ / SimpleMemoComponent logic
function updateSimpleMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) :null | Fiber {
  if(current ! = =null) {
    const prevProps = current.memoizedProps;
    if (
      // props is shallow compare equal
      shallowEqual(prevProps, nextProps) &&
      / / ref are congruent
      current.ref === workInProgress.ref &&
      // For thermal overloading
      (__DEV__ ? workInProgress.type === current.type : true)
    ) {
      didReceiveUpdate = false;
      // Fiber. Lanes has no task
      if(! checkScheduledUpdateOrContext(current, renderLanes)) { workInProgress.lanes = current.lanes;/ / implementation of bailout
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderLanes,
        );
      } else if((current.flags & ForceUpdateForLegacySuspense) ! == NoFlags) { didReceiveUpdate =true; }}}// Can't bailout, enter FC
  return updateFunctionComponent(current,workInProgress,Component,nextProps,renderLanes);
}
Copy the code

Bailout logic

New and old props equal & Ref congruent & Lanes without task when performing bailout.

ContextProvider

Contex updates the Memo, Pure, shouComponentUpdate Context.

function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  const providerType: ReactProviderType<any> = workInProgress.type;
  const context: ReactContext<any> = providerType._context;
  const newProps = workInProgress.pendingProps;
  const oldProps = workInProgress.memoizedProps;
  const newValue = newProps.value;

  if (enableLazyContextPropagation) {
    / /.. There is no logic
  } else {
    // oldProps is not null, so value must be passed
    if(oldProps ! = =null) {
      const oldValue = oldProps.value;
      // Compare the old and new values with object. is. More rigorous than congruence.
      if (is(oldValue, newValue)) {
        if (
          / / the children are congruent
          oldProps.children === newProps.children &&
          // The old context does not change
          // The context of the previous version is not used.! hasLegacyContextChanged() ) {// bailout
          returnbailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes); }}else {
        // Note that an LANES task is assigned to all child components that use this contextpropagateContextChange(workInProgress, context, renderLanes); }}}const newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}
Copy the code

oldProps ! == null& is(oldValue, newValue)&oldProps. Children === newProps. Children

conclusion

  • In the componentstate.contexttheupdateWill causeEntire subcomponentrender.
    • Child components can pass throughMemoComponentTo reducerender.
  • To reducepropsAttribute changes inPureComponent.MemoComponent.shouldComponentUpdateIn order to come into play.
    • In the componentrendnerAfter, the child component default is torednerCan be avoided through the method of appealrender.
  • ContextProviderWill comparechildren,value, so use as many subcomponents as possiblechildrenThe transfer guarantee is unchanged.

How to reduce render times

Move State Down — State sinks

Sink state into a separate child component instead of being controlled by the parent. Example: The props for the StaticText generated by App rerender are new objects. Cause StaticText cannot bailout must render.

// Each time the component changes state,
// Both rerender the StaticText and print the log
function App() {
    let [v, setV] = useState('value');
    return (
        <div>
            <input value={v} onChange={(e)= > setV(e.target.value)} />
            <p>{v}</p>
            <StaticText/>
        </div>
    );
}

function StaticText() {
    console.log('StaticText');
    return null
}
Copy the code

Create a child component From and sink state into the child component.

function App() {
    return (
        <>
            <Form />
            <StaticText />
        </>)}// State affects only this child component
function Form() {
    let [v, setV] = useState('value');
    return (
        <>
            <input value={v} onChange={(e)= > setV(e.target.value)} />
            <p>{v}</p>
        </>
    );
}
// StaticText is no longer rendenr
function StaticText() {
    console.log('StaticText');
    return null
}
Copy the code

State is now only associated with From and will not cause StaticText to be rendenr.

Lift Content Up — Children upgrade

The children of the component are provided by the children of props. For example: After sinking with state, the Form component still has a StaticText in it.

function Form() {
    let [v, setV] = useState('value');
    return (
        <div>
            <input value={v} onChange={(e)= > setV(e.target.value)} />
            <a href={v}>
                <p>{v}</p>
                <StaticText />
                <StaticText />
            </a>
        </div>
    );
}
// Form causes StaticText to rendenr.
function StaticText() {
    console.log('StaticText');
    return null
}
Copy the code

Pull out components that do not use state and provide them through Chidlren.

// Promote a child component that does not use state to the parent, passed by children.
function FormCore() {
    return (
        <Form>
            <StaticText />
            <StaticText />
        </Form>)}function Form({children}) {
    let [v, setV] = useState('value');
    return (
        <div>
            <input value={v} onChange={(e)= > setV(e.target.value)} />
            <a href={v}>
                <p>{v}</p>
                {children}
            </a>
        </div>
    );
}
// Form will not cause StaticText to rendenr
function StaticText() {
    console.log('StaticText');
    return null
}
Copy the code

Context read-write separation

Divide reads and writes into two contexts so that reads and writes do not affect each other redner. In other words, each manages updates to the Context user. For example, when the context changes, both Read and Write are rendenr.

const Context = React.createContext();

function Provider({children}) {
    const [v,setV] = useState('v')
    return (
        <Context.Provider value={{setV, v}} >
            {children}
        </Context.Provider>
    );
}
function Read() {
    console.log('read')
    const {v} = useContext(Context)
    return v
}
function Write() {
    console.log('write')
    const {setV} = useContext(Context)
    return <input type='text' onChange={(e)= >setV(e.target.value)}/>
}

function App() {
    return (
        <Provider>
            <Read/>
            <Write/>
        </Provider>
    );
}
Copy the code

Now separate the read from the write

// Read and write two contexts
const ReadContext = React.createContext();
const WriteContext = React.createContext();

function Provider({children}) {
    const [v, setV] = useState(' ')
    // Use the useCallback
    const write = useCallback((v) = > setV(v.trim()), [])
    // 2 provide
    return (
        <WriteContext.Provider value={write}>
            <ReadContext.Provider value={v}>
                {children}
            </ReadContext.Provider>
        </WriteContext.Provider>
    );
}
/ / use ReadContext
function Read() {
    console.log('read')
    const v = useContext(ReadContext)
    return v
}
/ / use WriteContext
function Write() {
    console.log('write')
    const write = useContext(WriteContext)
    return <input type="text" onChange={(e)= > write(e.target.value)} />
}
// Wirte will no longer be rendered repeatedly
function App() {
    return (
        <Provider>
            <Read />
            <Write />
        </Provider>
    );
}
Copy the code

Read and write the context separately, use whichever one you need.

Reduce Props changes

function

Use useCallback to keep the function unchanged.

const handleClick = useCallback(() = > {
  / *... * /} []);return <App onClick={handleClick} />;
Copy the code

object

Avoid object literals and use useMemo, ref instead

// bad, object literals are new objects every time
return <App value={{number:1}} />;

// Good uses useMemo
const obj = useMemo(() = >({number:1}), [])return <App value={obj} />;

// good uses ref
const objRef = useRef({number:1})
return <App value={objRef.current} />;
Copy the code

Non-essential state, do not use state

Data should be placed in state only if it is source data and needs to be rendered to the view. Causality can be derived without rendering, do not use state, and avoid irrelevant rendering.

PureComponent & shouldComponentUpdate

The Class component can be used to compare state and props to reduce re-rendering.

Reaact.memo

The Function component is used for props only, reducing re-rendering.

reference