🧀

ソースコードからReact Componentがレンダリングされる条件をなんとなく理解しています

2022/12/04に公開

前提知識

  1. なにかstateが変わるときには、schedulerというパッケージの経由うでFiberツリー全探索を促しています。そして、各ComponentのFiberに辿り着いたときに、Componentを実行するかどうかが条件あり。

  2. ComponentがレンダリングさることはComponent関数が実行されることです。

ソースコードからの解析

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3950-L4013

実行するかどうかは上記なところから判断行われています。
つまり、実行しないなら、attemptEarlyBailoutIfNoScheduledUpdateになります。

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3958

それ以外な場合は、下のswitch文にはいって、Componentの種類によって、それぞれの分岐を実行します。

attemptEarlyBailoutIfNoScheduledUpdateにに入るためには以下の3つのところの条件を満たす必要があります。

レンダリングしない条件1

現在のpropsと前のpropsは同じメモリアドレスする必要があります。

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3955

データ構造はこのようになっています

{
  type: 'div',
  props: {
    count: 0,
    children:  {
      ....
     }
  }
}

レンダリングしない条件2

LegacyContextの変化がないこと(LegacyContextよくわからない)

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3956

レンダリングしない条件3

updateとcontextの変化がないこと

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3966-L3971

checkScheduledUpdateOrContextの結果を見ています。checkScheduledUpdateOrContextの中身をみてみると以下の処理がされてます。

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3691-L3710

主に二箇所をみています。

Componentのlaneが変化があるかどうか。コメントのようにcomponentのstateの変化もしくはcontextの変化がるかどうかを検知

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3698

contextの変化があるかどうか。ここはよくわかりません。コメントのように、contextの変化が遅くなる場合も検知されられます。

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js#L3705

例から説明

例1

import React, { useState } from 'react';

const A = () => {
    console.log("A")
    return (<div>
        <div>A</div>
        <B/>
    </div>)
};

const B = (props) => {
    console.log("B")
    return (<C/>)
}

const C = (props) => {
    console.log("C")
    const [count, setCount] = useState(0);
    return <div>
        <button onClick={_=> setCount(f=>f + 1)}>click</button>
    </div>
}

export default A

ボタンをクリックしたら、Cしか実行されてません。
流れは以下です

  1. ボタンクリックしたら、Fiberツリー全探索開始
  2. Aにたどりつけ、propsが変わってない、内部にstateとcontext変化なし、実行しません。
  3. Bにたどり着け、propsが変わってない、内部にstateとcontext変化なし、実行しません。
  4. Cにたどりつけ、propsが変わってないですが、内部にstate変化があり、実行します。

例2

import React, { useState } from 'react';

const A = () => {
    console.log("A")
    const [count, setCount] = useState(0);
    return (
    <div>
        <button onClick={_=> setCount(f=>f + 1)}>click</button>
        <div>A</div>
        <B/>
        <C/>
    </div>
    )
};

const B = (props) => {
    console.log("B")
    return (<div>B</div>)
}

const C = (props) => {
    console.log("C")
    return <div>
        C
    </div>
}

export default A

ボタンをクリックしたら、A,B,C全部実行されます。
流れは以下です。

  1. ボタンクリックしたら、Fiberツリー全探索開始
  2. Aにたどりつけ、propsが変わってないですが、内部にstate変化なし、実行します。そして、BとCのpropsを再作成します。
  3. Bにたどりつけます、step2により、propsが変わりましたので、実行します。
  4. Cにたどりつけます、step2により、propsが変わりましたので、実行します。

例3

import React, { useState } from 'react';

const A = () => {
    console.log("A")
    return (<div>
        <div>A</div>
        <B/>
        <C/>
    </div>)
};

const B = (props) => {
    console.log("B")
    return (<div>B</div>)
}

const C = (props) => {
    console.log("C")
    const [count, setCount] = useState(0);
    return <div>
        <button onClick={_=> setCount(f=>f + 1)}>click</button>
    </div>
}

export default A


ボタンをクリックしたら、Cしか実行されてません。
流れは以下です

  1. ボタンクリックしたら、Fiberツリー全探索開始
  2. Aにたどりつけ、propsが変わってない、内部にstateとcontext変化なし、実行しません。
  3. Bにたどり着け、propsが変わってない、内部にstateとcontext変化なし、実行しません。
  4. Cにたどりつけ、propsが変わってないですが、内部にstate変化があり、実行します。

参考リンク

https://jser.dev/react/2022/01/07/how-does-bailout-work.html

Discussion