React Hooks:在多个连续的 setState 调用中跳过重新渲染

React Hooks: skip re-render on multiple consecutive setState calls

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

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 一次,但我记得(纠正我,如果我错了)事件处理程序(例如onClick)如果你调用多个连续的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 <></>;
}

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

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

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

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

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

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

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

但是,当你点击“without promise”按钮时,控制台会立即显示a aa 和b bb。所以在这种情况下,React 会批处理状态更改并同时为两者进行一次渲染。

Isn't there any way I could tell React, or React would know itself, that, after this setState another setState is coming along, so skip re-render until you find a second to breath.

你不能,React 批处理(对于 React 17)仅在事件处理程序和生命周期方法上更新状态,因此像你的情况一样在 promise 中进行批处理是不可能的。

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

从 React 18 开始,您有 automatic batching even in promises

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

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;
}

我想我 post 在这里回答这个问题,因为它还没有被提及。

有一种方法可以强制对状态更新进行批处理。有关解释,请参阅 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,而且我发现在不阻塞的情况下减少渲染数量非常有用上传我的代码。

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