`useState`,仅当对象中的值发生变化时才更新组件

agc*_*nti 11 javascript reactjs react-hooks

问题

useState 即使数据的值没有改变,也总是触发更新。

这是问题的工作演示:demo

背景

我正在使用useState钩子来更新一个对象,我试图让它只在该对象中的值发生变化时才更新。因为 React 使用Object.is比较算法来决定什么时候应该更新;具有等效值的对象仍然会导致组件重新渲染,因为它们是不同的对象。

前任。即使有效载荷的值保持不变,该组件也将始终重新渲染{ foo: 'bar' }

const UseStateWithNewObject = () => {
  const [payload, setPayload] = useState({});
  useEffect(
    () => {
      setInterval(() => {
        setPayload({ foo: 'bar' });
      }, 500);
    },
    [setPayload]
  );

  renderCountNewObject += 1;
  return <h3>A new object, even with the same values, will always cause a render: {renderCountNewObject}</h3>;
};
Run Code Online (Sandbox Code Playgroud)

有没有我可以shouldComponentUpdate用钩子实现类似的东西来告诉反应只在数据更改时重新渲染我的组件?

klu*_*gjo 18

如果我理解得很好,您将尝试仅setState在状态的新值发生更改时调用,从而防止在状态未更改时进行不必要的重新渲染。

如果是这种情况,您可以利用useState的回调形式

const [state, setState] = useState({});
setState(prevState => {
  // here check for equality and return prevState if the same

  // If the same
  return prevState; // -> NO RERENDER !

  // If different
  return {...prevState, ...updatedValues}; // Rerender
});
Run Code Online (Sandbox Code Playgroud)

这是一个自定义挂钩(在 TypeScript 中),可以自动为您执行此操作。它使用isEqual来自lodash。但请随意将其替换为您认为合适的任何等式函数。

import { isEqual } from 'lodash';
import { useState } from 'react';

const useMemoizedState = <T>(initialValue: T): [T, (val: T) => void] => {
  const [state, _setState] = useState<T>(initialValue);

  const setState = (newState: T) => {
    _setState((prev) => {
      if (!isEqual(newState, prev)) {
        return newState;
      } else {
        return prev;
      }
    });
  };

  return [state, setState];
};

export default useMemoizedState;

Run Code Online (Sandbox Code Playgroud)

用法:

const [value, setValue] = useMemoizedState({ [...] });
Run Code Online (Sandbox Code Playgroud)


Tra*_*mes 4

我认为我们需要看到一个更好的现实生活示例来说明您要做的事情,但从您分享的内容来看,我认为逻辑需要向上游移动到状态设置之前的某个点。

例如,您可以在useEffect更新之前手动比较传入的值,因为这基本上就是您所要求的 React 是否可以为您做的事情。

在这种情况下,有一个库use-deep-compare-effect https://github.com/kentcdodds/use-deep-compare-effect可能对您有用,它可以处理大量涉及的手动工作,但即便如此,该解决方案假设开发人员将手动决定(基于传入的道具等)是否应该更新状态。

例如:

const obj = {foo: 'bar'}
const [state, setState] = useState(obj)

useEffect(() => {
   // manually deep compare here before updating state
   if(obj.foo === state.foo) return
   setState(obj)
},[obj])
Run Code Online (Sandbox Code Playgroud)

编辑:useRef如果您不直接使用该值并且不需要组件根据它进行更新,则使用示例:

const obj = {foo: 'bar'}
const [state, setState] = useState(obj)

const { current: payload } = useRef(obj)

useEffect(() => {
    // always update the ref with the current value - won't affect renders
    payload = obj

    // Now manually deep compare here and only update the state if 
    //needed/you want a re render
    if(obj.foo === state.foo) return
    setState(obj)
},[obj])
Run Code Online (Sandbox Code Playgroud)