饰演者 發表於 2022-12-13 22:22:00

React报错之Too many re-renders

<h2 id="总览">总览</h2>
<p>产生"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误有多方面的原因:</p>
<ol>
<li>在一个组件的渲染方法中调用一个设置状态的函数。</li>
<li>立即调用一个事件处理器,而不是传递一个函数。</li>
<li>有一个无限设置与重渲染的<code>useEffect</code>钩子。</li>
</ol>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f4dfde02264497e894185435e43f6a6~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>这里有个示例来展示错误是如何发生的:</p>
<pre><code class="language-jsx">import {useState} from 'react';

export default function App() {
const = useState(0);

// ⛔️ Too many re-renders. React limits the number
// of renders to prevent an infinite loop.
return (
    &lt;div&gt;
      &lt;button onClick={setCounter(counter + 1)}&gt;Increment&lt;/button&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>上述代码问题在于,我们在<code>onClick</code>事件处理器中立即调用了<code>setCounter</code>函数。</p>
<blockquote>
<p>该函数是在页面加载时立即被调用,而不是事件触发后调用。</p>
</blockquote>
<h2 id="传递函数">传递函数</h2>
<p>为了解决该错误,为<code>onClick</code>事件处理器传递函数,而不是传递调用函数的结果。</p>
<pre><code class="language-jsx">import {useState} from 'react';

export default function App() {
const = useState(0);

return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setCounter(counter + 1)}&gt;Increment&lt;/button&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>现在,我们为事件处理器传递了函数,而不是当页面加载时调用<code>setCounter</code>方法。</p>
<blockquote>
<p>如果该方法在页面加载时被调用,就会触发一个<code>setState</code>动作,组件就会无限重新渲染。</p>
</blockquote>
<p>如果我们试图立即设置一个组件的状态,而不使用一个条件或事件处理器,也会发生这个错误。</p>
<pre><code class="language-jsx">import {useState} from 'react';

export default function App() {
const = useState(0);

// ⛔️ Too many re-renders. React limits the number
// of renders to prevent an infinite loop.
setCounter(counter + 1);

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>问题在于,<code>setCounter</code>函数在组件渲染时被调用、更新状态,并导致重新渲染,而且是无限重新渲染。</p>
<p>你可以通过向<code>useState()</code>钩子传递一个初始值或一个函数来初始化状态,从而解决这个错误。</p>
<pre><code class="language-jsx">import {useState} from 'react';

export default function App() {
const = useState(() =&gt; 100 + 100);

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>我们向<code>useState</code>方法传递了一个函数。这个函数只会在组件第一次渲染时被调用,并且会计算出初始状态。你也可以直接向<code>useState</code>方法传递一个初始值。</p>
<p>另外,你也可以像前面的例子那样使用一个条件或事件处理器。</p>
<pre><code>import {useState} from 'react';

export default function App() {
const = useState(0);

// 👇️ your condition here
if (Math.random() &gt; 0.5) {
    setCounter(counter + 1);
}

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<blockquote>
<p>如果你像上面的例子那样使用一个条件,请确保该条件不总是返回一个真值,因为这将导致无限的重新渲染循环。</p>
</blockquote>
<p>"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误也会在使用<code>useEffect</code>方法时发生,该方法的依赖会导致无限重新渲染。</p>
<pre><code class="language-jsx">import {useEffect, useState} from 'react';

export default function App() {
const = useState(0);

useEffect(() =&gt; {
// ⛔️ Too many re-renders. React limits the number
// of renders to prevent an infinite loop.
    setCounter(counter + 1);
}); // 👈️ forgot to pass dependency array

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>上述代码问题在于,我们没有为<code>useEffect</code>钩子传递依赖数组。</p>
<p>这意味着该钩子会在每次渲染时运行,它会更新组件的状态,然后无限重新运行。</p>
<h2 id="传递依赖">传递依赖</h2>
<p>解决该错误的一种办法是,为<code>useEffect</code>提供空数组作为第二个参数。</p>
<pre><code class="language-jsx">import {useEffect, useState} from 'react';

export default function App() {
const = useState(0);

useEffect(() =&gt; {
    setCounter(counter + 1);
}, []); // 👈️ empty dependencies array

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<blockquote>
<p>如果你为<code>useEffect</code>方法传递空数组依赖作为第二个参数,该方法只在组件的初始渲染时运行。</p>
</blockquote>
<p>该代码将计数器递增到<code>1</code>,并且不再运行,无论<code>App</code>组件是否被重新渲染。</p>
<p>如果你必须指定一个依赖来无限地重新渲染你的组件,试着寻找一个可以防止这种情况的条件。</p>
<pre><code class="language-jsx">import {useEffect, useState} from 'react';

export default function App() {
const = useState(0);

useEffect(() =&gt; {
    // 👇️ some condition here
    if (Math.random() &gt; 0.5) {
      setCounter(counter + 1);
    }
}, );

return (
    &lt;div&gt;
      &lt;h1&gt;Count: {counter}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<blockquote>
<p>有可能是某些逻辑决定了状态是否应该被更新,而状态不应该在每次重新渲染时被设置。</p>
</blockquote>
<p>确保你没有使用一个在每次渲染时都不同的对象或数组作为<code>useEffect</code>钩子的依赖。</p>
<pre><code class="language-jsx">import {useEffect, useState} from 'react';

export default function App() {
const = useState({country: '', city: ''});

const obj = {country: 'Chile', city: 'Santiago'};

useEffect(() =&gt; {
    // ⛔️ Too many re-renders. React limits the number
    // of renders to prevent an infinite loop.
    setAddress(obj);
    console.log('useEffect called');
}, );

return (
    &lt;div&gt;
      &lt;h1&gt;Country: {address.country}&lt;/h1&gt;
      &lt;h1&gt;City: {address.city}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>问题在于,在JavaScript中,对象是通过引用进行比较的。<code>obj</code>变量存储了一个具有相同键值对的对象,但每次渲染时的引用不同(在内存中的位置不同)。</p>
<h2 id="移入依赖">移入依赖</h2>
<p>解决该错误的一种办法是,把这个对象移到<code>useEffect</code>钩子里面,这样我们就可以把它从依赖数组中移除。</p>
<pre><code class="language-jsx">import {useEffect, useState} from 'react';

export default function App() {
const = useState({country: '', city: ''});

useEffect(() =&gt; {
    // 👇️ move object inside of useEffect
    // and remove it from dependencies array
    const obj = {country: 'Chile', city: 'Santiago'};

    setAddress(obj);
    console.log('useEffect called');
}, []);

return (
    &lt;div&gt;
      &lt;h1&gt;Country: {address.country}&lt;/h1&gt;
      &lt;h1&gt;City: {address.city}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<h2 id="传递对象属性">传递对象属性</h2>
<p>另一个解决方案是将对象的属性传递给依赖数组。</p>
<pre><code>import {useEffect, useState} from 'react';

export default function App() {
const = useState({country: '', city: ''});

const obj = {country: 'Chile', city: 'Santiago'};

useEffect(() =&gt; {

    setAddress({country: obj.country, city: obj.city});
    console.log('useEffect called');
    // 👇️ object properties instead of the object itself
}, );

return (
    &lt;div&gt;
      &lt;h1&gt;Country: {address.country}&lt;/h1&gt;
      &lt;h1&gt;City: {address.city}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p>现在React不是在测试一个对象是否发生了变化,而是在测试<code>obj.country</code>和<code>obj.city</code>字符串在渲染之间是否发生了变化。</p>
<h2 id="记忆值">记忆值</h2>
<p>另外,我们可以使用<code>useMemo</code>钩子来获得一个在不同渲染之间不会改变的记忆值。</p>
<pre><code class="language-jsx">import {useEffect, useMemo, useState} from 'react';

export default function App() {
const = useState({country: '', city: ''});

// 👇️ get memoized value
const obj = useMemo(() =&gt; {
    return {country: 'Chile', city: 'Santiago'};
}, []);

useEffect(() =&gt; {
    setAddress(obj);
    console.log('useEffect called');
}, );

return (
    &lt;div&gt;
      &lt;h1&gt;Country: {address.country}&lt;/h1&gt;
      &lt;h1&gt;City: {address.city}&lt;/h1&gt;
    &lt;/div&gt;
);
}
</code></pre>
<blockquote>
<p>我们将对象的初始化包裹在<code>useMemo</code>钩子里面,以获得一个不会在渲染之间改变的记忆值。</p>
</blockquote>
<p>我们传递给<code>useMemo</code>钩子的第二个参数是一个依赖数组,它决定了我们传递给<code>useMemo</code>的回调函数何时被重新运行。</p>
<p>需要注意的是,数组在JavaScript中也是通过引用进行比较的。所以一个具有相同值的数组也可能导致你的<code>useEffect</code>钩子被无限次触发。</p>
<pre><code class="language-jsx">import {useEffect, useMemo, useState} from 'react';

export default function App() {
const = useState();

const arr = ;

useEffect(() =&gt; {
    // ⛔️ Too many re-renders. React limits the number
    // of renders to prevent an infinite loop.
    setNums(arr);

    console.log('useEffect called');
}, );

return &lt;div&gt;{nums}&lt;/div&gt;;
}
</code></pre>
<p>数组在重新渲染之间存储相同的值,但指向内存中的不同位置,并且在每次组件重新渲染时有不同的引用。</p>
<p>在处理数组时,我们用于对象的方法同样有效。例如,我们可以使用<code>useMemo</code>钩子来获得一个在渲染之间不会改变的记忆值。</p>
<pre><code class="language-jsx">import {useEffect, useMemo, useState} from 'react';

export default function App() {
const = useState();

const arr = useMemo(() =&gt; {
    return ;
}, []);

useEffect(() =&gt; {
    setNums(arr);
    console.log('useEffect called');
}, );

return &lt;div&gt;{nums}&lt;/div&gt;;
}
</code></pre>
<p>我们将数组的初始化包裹在<code>useMemo</code>钩子里面,以获得一个不会在不同渲染之间改变的记忆值。</p><br><br>
来源:https://www.cnblogs.com/chuckQu/p/16980859.html
頁: [1]
查看完整版本: React报错之Too many re-renders