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

Ard*_*adi 7 performance setstate reactjs react-state-management react-hooks

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

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,
  };
}
Run Code Online (Sandbox Code Playgroud)

当我在我的函数组件中使用这个自定义 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 <></>;
}
Run Code Online (Sandbox Code Playgroud)

And*_*orn 10

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

有一种方法可以强制批量进行状态更新。请参阅这篇文章以获取解释。下面是一个功能齐全的组件,无论 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>
    )
}
Run Code Online (Sandbox Code Playgroud)

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


Sar*_*P S 5

如果状态更改是异步触发的, 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);
}, [])
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 有一种方法可以利用`ReactDOM`的unstable_batchedUpdates来告诉react批处理所有的setState调用。在解析你的方法后的`useEffect`中,你可以做这样的事情`ReactDOM.unstable_batchedUpdates(() =&gt; { setAddresses(addresses); setPools(pools); setSchedules(schedules); setServices(services); setTunnels(tunnels); setZones(区域); setGroups(组); setAdvancedServices(高级服务); setProfiles(配置文件); });` (2认同)