“ useState”,仅当对象中的值更改时更新组件

时间:2020-01-13 17:08:10

标签: 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>;
};

问题

我是否可以使用钩子实现类似shouldComponentUpdate的东西,以告诉他们仅在数据更改时才重新渲染组件?

5 个答案:

答案 0 :(得分:2)

我认为我们需要看到一个更好的现实生活示例,说明您想做什么,但是从您共享的观点来看,我认为逻辑需要在状态被设定之前向上游移动。

例如,您可以在更新状态之前手动比较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])

编辑:如果您不直接使用该值并且不需要该组件根据其更新,则使用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])

答案 1 :(得分:1)

我是否可以使用钩子来实现诸如shouldComponentUpdate之类的内容,以告知仅在数据更改时才重新渲染组件?

常见的,对于状态更改,您在使用功能useState或使用useRef进行引用之前将其与先前的值进行比较:

// functional useState
useEffect(() => {
  setInterval(() => {
    const curr = { foo: 'bar' };
    setPayload(prev => (isEqual(prev, curr) ? prev : curr));
  }, 500);
}, [setPayload]);
// with ref
const prev = useRef();
useEffect(() => {
  setInterval(() => {
    const curr = { foo: 'bar' };
    if (!isEqual(prev.current, curr)) {
      setPayload(curr);
    }
  }, 500);
}, [setPayload]);

useEffect(() => {
  prev.current = payload;
}, [payload]);

为完整起见,“在数据更改时重新渲染组件?”可能也称为道具,因此在这种情况下,您应该使用React.memo

如果给定相同的道具,功能组件呈现相同的结果,则可以将其包装在对React.memo的调用中,以在某些情况下通过记忆结果来提高性能。这意味着React将跳过渲染组件,并重用最后渲染的结果。

Edit affectionate-hellman-ujtbv

答案 2 :(得分:0)

对此通用解决方案不涉及在效果中添加逻辑,而是将组件拆分为:

  • 状态呈示...的不受控制的容器
  • 已由obj.increment 记忆的
  • 由哑巴控制的无状态组件

您的笨拙组件可以是纯净的(就像实现了React.memo一样,您的智能状态处理组件也可以是“笨拙”的,而不必担心将状态更新为相同的值。

示例:

之前

shouldComponentUpdate

之后

export default function Foo() {
  const [state, setState] = useState({ foo: "1" })
  const handler = useCallback(newValue => setState({ foo: newValue }))

  return (
    <div>
      <SomeWidget onEvent={handler} />
      Value: {{ state.foo }}
    </div>
  )

这使您可以分离所需的逻辑。

答案 3 :(得分:0)

如果您检查类型为object或对象数组的props的更改,我建议在useEffect deps上使用JSON.stringify。很简单。

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

useEffect(() => {
    setState(obj)
},[JSON.stringify(obj)])

答案 4 :(得分:-1)

您可以使用已记忆的组件,它们只会在更改道具时重新呈现。

const comparatorFunc = (prev, next) => {
  return prev.foo === next.foo
}

const MemoizedComponent = React.memo(({payload}) => {
    return (<div>{JSON.stringify(payload)}</div>)
}, comparatorFunc);