【问题标题】:React Hooks: skip re-render on multiple consecutive setState callsReact Hooks:跳过多个连续 setState 调用的重新渲染
【发布时间】:2020-03-28 12:13:56
【问题描述】:

假设我有以下代码:(太冗长了)

function usePolicyFormRequirements(policy) {
  const [addresses, setAddresses] = React.useState([]);
  const [pools, setPools] = React.useState([]);
  const [schedules, setSchedules] = React.useState([]);
  const [services, setServices] = React.useState([]);
  const [tunnels, setTunnels] = React.useState([]);
  const [zones, setZones] = React.useState([]);
  const [groups, setGroups] = React.useState([]);
  const [advancedServices, setAdvancedServices] = React.useState([]);
  const [profiles, setProfiles] = React.useState([]);

  React.useEffect(() => {
    policiesService
      .getPolicyFormRequirements(policy)
      .then(
        ({
          addresses,
          pools,
          schedules,
          services,
          tunnels,
          zones,
          groups,
          advancedServices,
          profiles,
        }) => {
          setAddresses(addresses);
          setPools(pools);
          setSchedules(schedules);
          setServices(services);
          setTunnels(tunnels);
          setZones(zones);
          setGroups(groups);
          setAdvancedServices(advancedServices);
          setProfiles(profiles);
        }
      );
  }, [policy]);

  return {
    addresses,
    pools,
    schedules,
    services,
    tunnels,
    zones,
    groups,
    advancedServices,
    profiles,
  };
}

当我在我的函数组件中使用这个自定义 Hook 时,在 getPolicyFormRequirements 解析后,我的函数组件重新渲染 9 次(我调用 setState 的所有实体的计数)

我知道这个特定用例的解决方案是将它们聚合到一个状态并调用 setState 一次,但我记得(纠正我,如果我错了)事件处理程序(例如 @987654327 @) 如果连续调用多个setStates,则在事件处理程序执行完成后只会发生一次重新渲染。

我有没有办法告诉React,或者React 会知道自己,在这之后setState 会出现另一个setState,所以跳过重新渲染,直到你找到一秒钟的呼吸。

我不是在寻找性能优化技巧,我想知道上述(粗体)问题的答案!

或者你认为我想错了吗?

谢谢!

-------------


更新 我如何检查我的组件渲染了 9 次?

export default function PolicyForm({ onSubmit, policy }) {
  const [formState, setFormState, formIsValid] = usePgForm();
  const {
    addresses,
    pools,
    schedules,
    services,
    tunnels,
    zones,
    groups,
    advancedServices,
    profiles,
    actions,
    rejects,
    differentiatedServices,
    packetTypes,
  } = usePolicyFormRequirements(policy);

  console.log(' --- re-rendering'); // count of this
  return <></>;
}

【问题讨论】:

  • 有了完整的class Thing extends React.Component,您就可以拥有shouldComponentUpdate。
  • 如果它们一直一起更新,那么也许你应该考虑一个更统一的状态对象。
  • 向我们展示您如何检查组件是否呈现 9 次的示例。
  • @DennisVash ``` export default function PolicyForm({ onSubmit, policy }) { const { 地址、池、计划、服务、隧道、区域、组、高级服务、配置文件、} = usePolicyFormRequirements(policy ); console.log(' --- 重新渲染');返回 >```
  • 有一种方法可以强制更新批处理。看看this article 看看它是如何工作的。我正在使用它,它会将渲染减少到一个。

标签: reactjs performance react-hooks setstate react-state-management


【解决方案1】:

我想我会在这里发布这个答案,因为它还没有被提及。

有一种方法可以强制批量更新状态。有关说明,请参阅this article。下面是一个功能齐全的组件,它只渲染一次,无论 setValues 函数是否异步。

import React, { useState, useEffect} from 'react'
import {unstable_batchedUpdates} from 'react-dom'

export default function SingleRender() {

    const [A, setA] = useState(0)
    const [B, setB] = useState(0)
    const [C, setC] = useState(0)

    const setValues = () => {
        unstable_batchedUpdates(() => {
            setA(5)
            setB(6)
            setC(7)
        })
    }

    useEffect(() => {
        setValues()
    }, [])

    return (
        <div>
            <h2>{A}</h2>
            <h2>{B}</h2>
            <h2>{C}</h2>
        </div>
    )
}

虽然名称“不稳定”可能令人担忧,但 React 团队之前曾建议在适当的情况下使用此 API,我发现在不阻塞代码的情况下减少渲染次数非常有用。

【讨论】:

    【解决方案2】:

    如果状态更改是异步触发的,React 将不会批处理您的多个状态更新。例如,在您的情况下,由于您在解决 policyService.getPolicyFormRequirements(policy) 后调用 setState,因此反应不会对其进行批处理。

    如果只是以下方式,React 会批处理 setState 调用,在这种情况下只有 1 次重新渲染。

    React.useEffect(() => {
       setAddresses(addresses);
       setPools(pools);
       setSchedules(schedules);
       setServices(services);
       setTunnels(tunnels);
       setZones(zones);
       setGroups(groups);
       setAdvancedServices(advancedServices);
       setProfiles(profiles);
    }, [])
    

    我在网上找到了下面的代码框示例,它演示了上述两种行为。

    https://codesandbox.io/s/402pn5l989

    如果你看一下控制台,当你点击“with promise”按钮时,它会首先显示 aaa 和 b b,然后是 a aa 和 b bb。

    在这种情况下,它不会立即渲染 aa - bb,每次状态更改都会触发新的渲染,没有批处理。

    但是,当您单击“无承诺”按钮时,控制台将立即显示 a aa 和 b bb。因此,在这种情况下,React 会批量处理状态更改并同时为两者进行一次渲染。

    【讨论】:

    • 谢谢!这就是我一直在寻找的。​​span>
    • 我正在想办法告诉 React,将函数作为批处理程序。例如,在我的承诺解决后,我可以告诉React.batch(() =&gt; {// my setStates}) 你认为我在这方面想错了吗?
    • 有一种方法可以利用 ReactDOM 的不稳定批处理更新来告诉反应批处理所有 setState 调用。在useEffect 解决你的方法后,你可以做这样的事情ReactDOM.unstable_batchedUpdates(() =&gt; { setAddresses(addresses); setPools(pools); setSchedules(schedules); setServices(services); setTunnels(tunnels); setZones(zones); setGroups(groups); setAdvancedServices(advancedServices); setProfiles(profiles); });
    • 谢谢你,你值得一千个赞:)
    【解决方案3】:

    您可以将所有状态合并为一个

    function usePolicyFormRequirements(policy) {
      const [values, setValues] = useState({
        addresses: [],
        pools: [],
        schedules: [],
        services: [],
        tunnels: [],
        zones: [],
        groups: [],
        advancedServices: [],
        profiles: [],
      });
      
      React.useEffect(() => {
        policiesService
          .getPolicyFormRequirements(policy)
          .then(newValues) => setValues({ ...newValues }));
      }, [policy]);
    
      return values;
    }
    

    【讨论】:

      【解决方案4】:

      我有没有办法告诉 React,或者 React 自己知道,在这个 setState 之后另一个 setState 出现了,所以跳过重新渲染,直到你找到一秒钟的呼吸。

      你不能,React 只在事件处理程序和生命周期方法上批处理状态更新,因此像你的情况那样在 promise 中批处理是不可能的。

      要解决它,你需要将钩子状态减少到单一来源。

      【讨论】:

        【解决方案5】:

        顺便说一句,我刚刚发现 React 18 添加了开箱即用的自动更新批处理功能。阅读更多:https://github.com/reactwg/react-18/discussions/21

        【讨论】:

          猜你喜欢
          • 2019-10-28
          • 2020-02-23
          • 1970-01-01
          • 1970-01-01
          • 2020-01-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-22
          相关资源
          最近更新 更多