爱如煙花寂寞 發表於 2023-7-24 18:41:00

React函数式组件渲染、useEffect顺序总结

<blockquote>
<p>参考资料:<br>
深入React的生命周期(上):出生阶段(Mount)<br>
深入React的生命周期(下):更新(Update)<br>
精读《useEffect 完全指南》<br>
React组件重新渲染理解 &amp; 优化大全<br>
React渲染顺序及useEffect执行顺序探究(含并发模式)</p>
</blockquote>
<h1 id="组件状态">组件状态</h1>
<p>同样还是可以把组件的状态分为mount、update和unmount。</p>
<ul>
<li>mount:组件首次出现在页面中。React会通过最后组件的<strong>函数返回值</strong>,确定它有哪些子组件,依次mount和render子组件。<strong>一个component只是定义了,但没有最后被返回,就不会被挂载和渲染。</strong></li>
<li>update:组件的重新渲染,重新渲染的条件见这里。</li>
<li>unmount:如果组件从页面消失,就会被unmount。常见于条件渲染、或者子组件未使用key标明时的位置更改。</li>
</ul>
<h1 id="react16的组件渲染过程">React16的组件渲染过程</h1>
<p>即使用<code>ReactDOM.createRoot(DOM).render(&lt;App /&gt;)</code>渲染组件时。</p>
<h2 id="单一组件渲染过程">单一组件渲染过程</h2>
<p>在组件的不同阶段,调用顺序如下</p>
<h3 id="mount">mount</h3>
<ul>
<li>函数体:此时<code>useState</code>等hooks取初始值,如果用callback初始化,则会调用初始化函数</li>
<li>effect函数:会调用一遍所有<code>useEffect</code>注册的函数,调用顺序<strong>就是<code>useEffect</code>在函数体里出现的顺序</strong></li>
</ul>
<h3 id="update">update</h3>
<ul>
<li>
<p>函数体:正常调用,取最新state和ref的值</p>
</li>
<li>
<p>clean函数:如果依赖项<code>A=[…]</code>发生改变,则会调用,但若有其它依赖项<code>B</code>也变了,却没列进依赖项里,这些未注册依赖项会使用<strong>最后一次<code>A=[…]</code>发生改变时的B的值。</strong>因为这是clean函数最新的定义。</p>
<p>样例可见React函数式组件渲染顺序探究(Demo),组件依赖了name和state,但只注册了state这一个依赖项。</p>
</li>
<li>
<p>effect函数:如果依赖项<code>A=[…]</code>发生改变,则会调用,但若有其它依赖项<code>B</code>也变了,却没列进依赖项里,这些未注册依赖项会使用<strong>最后一次<code>A=[…]</code>发生改变时的B的值。</strong>因为这是effect函数最新的定义。</p>
</li>
</ul>
<h3 id="unmount">unmount</h3>
<ul>
<li>
<p>clean函数:会调用一遍所有<code>useEffect</code>返回的clean函数,调用顺序也是注册顺序。同样,也取的是它最新的定义。</p>
<p>假设有两个effect,都有未注册依赖项<code>B</code>。但它们一个依赖项为<code>A=[…]</code>,另一个为<code>[]</code>。</p>
<p>如果最开始<code>B=1</code>,而<code>A</code>变的时候<code>B=2</code>,最后unmount的时候,前者的<code>B=2</code>,后者的<code>B=1</code>,因为后者的clean函数并未更新。</p>
</li>
</ul>
<h2 id="树型组件调用顺序">树型组件调用顺序</h2>
<h3 id="mount-1">mount</h3>
<p>如果有一个这样的component:</p>
<pre><code class="language-html">&lt;A&gt;
    &lt;A1&gt;
      &lt;A1_1/&gt;
      &lt;A1_2/&gt;
    &lt;/A1&gt;
    &lt;A2&gt;
      &lt;A2_1/&gt;
      &lt;A2_2/&gt;
    &lt;/A2&gt;
&lt;/A&gt;
</code></pre>
<ul>
<li>函数体:调用顺序是先序遍历的DFS,即<code></code></li>
<li>effect:类似于二叉树的后序遍历,先遍历孩子,再遍历根,即:<code></code>。</li>
</ul>
<h3 id="update-1">update</h3>
<p>如果上述的component变成了如下,A重新渲染。</p>
<pre><code class="language-html">&lt;A&gt;
    &lt;A2&gt;
      &lt;A2_1/&gt;
      &lt;A2_2/&gt;
    &lt;/A2&gt;
    &lt;A1&gt;
      &lt;A1_1/&gt;
      &lt;A1_2/&gt;
    &lt;/A1&gt;
&lt;/A&gt;
</code></pre>
<ul>
<li>如果A1和A2没有设置key,React会当作需要unmount旧的A1、A2,再mount新的A1、A2。</li>
<li>否则,React只会重新渲染A1、A2。</li>
</ul>
<p>假设这里<strong>A1设置了key,而A2没有:</strong></p>
<ul>
<li>函数体:按<strong>当前组件内容</strong>的先序DFS:<code></code>。</li>
<li>clean:
<ul>
<li>先执行unmount的组件的clean,执行顺序是<strong>先序DFS</strong>,即<code></code></li>
<li>再执行update组件的clean,执行顺序是<strong>后序DFS</strong>,即<code></code></li>
</ul>
</li>
<li>effect:按<strong>当前组件内容</strong>的后序DFS执行,即<code></code>。</li>
</ul>
<h1 id="react18的更新">React18的更新</h1>
<p>即使用<code>StrictMode</code>渲染组件时。</p>
<pre><code class="language-jsx">root.render(
&lt;React.StrictMode&gt;
    &lt;App /&gt;
&lt;/React.StrictMode&gt;
);
</code></pre>
<p>与16最大的区别是:</p>
<p>(1)函数体都会被调用2遍</p>
<p>(2)新mount的组件都会再次调用一遍clean和effect</p>
<p>有点类似于, <code>mount (React 18)</code>约等于<code>mount(React 16) + update(React 16)</code></p>
<p>所以:</p>
<ul>
<li>
<p>第一次body即mount的body,第二次body是update的body</p>
<p>但也不完全相同,比如,如果使用callback来初始化state的值时,mount的时候调用的两遍函数体,都是会调用这个callback的,而不是一次调用一次不调用。</p>
</li>
<li>
<p>第一次effect是mount的effect</p>
</li>
<li>
<p>接下来的clean和effect是update时的clean+effect</p>
</li>
</ul>
<h3 id="mount-2">mount</h3>
<p>如果有一个这样的component:</p>
<pre><code class="language-html">&lt;A&gt;
    &lt;A1&gt;
      &lt;A1_1/&gt;
      &lt;A1_2/&gt;
    &lt;/A1&gt;
    &lt;A2&gt;
      &lt;A2_1/&gt;
      &lt;A2_2/&gt;
    &lt;/A2&gt;
&lt;/A&gt;
</code></pre>
<ul>
<li>函数体:调用顺序是先序遍历的DFS,即<code></code>。由于会叫两遍,实际上是<code></code>
<ul>
<li>有意思的是,不是先mount DFS一遍,update DFS一遍,是在DFS的过程中直接叫两遍函数体。</li>
</ul>
</li>
<li>effect:类似于二叉树的后序遍历,先遍历孩子,再遍历根,即:<code></code>。</li>
<li>clean:后序DFS<code></code>。</li>
<li>effect:后序DFS<code></code>。</li>
</ul>
<h3 id="update-2">update</h3>
<p>如果上述的component变成了如下,A重新渲染。</p>
<pre><code class="language-html">&lt;A&gt;
    &lt;A2&gt;
      &lt;A2_1/&gt;
      &lt;A2_2/&gt;
    &lt;/A2&gt;
    &lt;A1&gt;
      &lt;A1_1/&gt;
      &lt;A1_2/&gt;
    &lt;/A1&gt;
&lt;/A&gt;
</code></pre>
<ul>
<li>如果A1和A2没有设置key,React会当作需要unmount旧的A1、A2,再mount新的A1、A2。</li>
<li>否则,React只会重新渲染A1、A2。</li>
</ul>
<p>假设这里<strong>A1设置了key,而A2没有:</strong></p>
<ul>
<li>函数体:按<strong>当前组件内容</strong>的先序DFS:<code></code>。同样会调用2次。</li>
<li>clean:
<ul>
<li>先执行unmount的组件的clean,执行顺序是<strong>先序DFS</strong>,即<code></code></li>
<li>再执行update组件的clean,执行顺序是<strong>后序DFS</strong>,即<code></code></li>
</ul>
</li>
<li>effect:按<strong>当前组件内容</strong>的后序DFS执行,即<code></code>。</li>
<li>clean:mount的组件会被update,所以会有<strong>第二轮clean</strong>,后序DFS,即<code></code></li>
<li>effect:第二轮effect,即<code></code></li>
</ul><br><br>
来源:https://www.cnblogs.com/milliele/p/17578048.html
頁: [1]
查看完整版本: React函数式组件渲染、useEffect顺序总结