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

时间:2019-12-03 18:37:56

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

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

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),如果您调用多个连续的setState,则事件处理程序执行完毕后,只会进行一次重新渲染。

我没有办法告诉React,或者React会知道自己,在此setState之后,又出现了另一个setState,因此,请跳过重新渲染,直到您喘口气为止。

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

还是您认为我的想法错了?

谢谢!

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


更新 我如何检查我的组件渲染了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 <></>;
}

5 个答案:

答案 0 :(得分:2)

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

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

>

答案 1 :(得分:1)

如果状态更改是异步触发的,则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);
}, [])

我在网上找到了以下codeandbox示例,演示了上述两种行为。

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

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

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

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

答案 2 :(得分:1)

  

我没有办法告诉React,或者React会知道自己,在此setState之后,又出现了另一个setState,因此跳过重新渲染,直到您需要喘口气。

您不能,React只在事件处理程序和生命周期方法上批处理状态更新,因此无法像您的情况那样按承诺进行批处理。

要解决此问题,您需要将挂钩状态减少到一个来源。

答案 3 :(得分:0)

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

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

  return values;
}

答案 4 :(得分:0)

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