Javascript `useState`,仅在对象中的值更改时更新组件 问题

Javascript `useState`,仅在对象中的值更改时更新组件 问题,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,useState始终触发更新,即使数据的值没有更改 下面是该问题的工作演示: 背景 我正在使用useState钩子来更新一个对象,并且我试图让它只在对象中的值发生变化时更新。因为React使用比较算法来确定何时应该更新;具有等效值的对象仍然会导致组件重新渲染,因为它们是不同的对象 例如,即使有效负载的值保持为{foo:'bar'} const UseStateWithNewObject=()=>{ const[payload,setPayload]=useState({}); 使用效果( ()

useState
始终触发更新,即使数据的值没有更改

下面是该问题的工作演示:

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

例如,即使有效负载的值保持为
{foo:'bar'}

const UseStateWithNewObject=()=>{
const[payload,setPayload]=useState({});
使用效果(
() => {
设置间隔(()=>{
setPayload({foo:'bar'});
}, 500);
},
[设定有效载荷]
);
renderCountNewObject+=1;
返回新对象,即使具有相同的值,也将始终导致渲染:{renderCountNewObject};
};
问题:
我是否可以用钩子实现类似于
shouldComponentUpdate
的东西,告诉react仅在数据更改时重新渲染我的组件?

您可以使用记忆组件,它们仅在道具更改时重新渲染

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

const MemoizedComponent = React.memo(({payload}) => {
    return (<div>{JSON.stringify(payload)}</div>)
}, comparatorFunc);
const comparatorFunc=(上一个,下一个)=>{
返回prev.foo==next.foo
}
const MemoizedComponent=React.memo({payload})=>{
返回({JSON.stringify(有效负载)})
},comparatorFunc);

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

例如,您可以在更新状态之前手动比较
useffect
中的传入值,因为这基本上就是您询问React是否可以为您做的事情

在这种情况下,有一个库
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])
我是否可以实现像shouldComponentUpdate这样的东西,用钩子告诉react,在数据更改时只重新呈现我的组件

通常,对于状态更改,在使用functional
useState
或使用
useRef
的引用进行渲染之前,您会与以前的值进行比较:

// functional useState
useEffect(() => {
  setInterval(() => {
    const curr = { foo: 'bar' };
    setPayload(prev => (isEqual(prev, curr) ? prev : curr));
  }, 500);
}, [setPayload]);

为完整起见,“当数据更改时重新呈现我的组件?”也可能指道具,因此在本例中,您应该使用

如果您的函数组件在相同的道具下呈现相同的结果,您可以将其包装在调用React.memo中,以便在某些情况下通过记忆结果来提高性能。这意味着React将跳过渲染组件,并重用上次渲染的结果


解决这一问题的通用解决方案不需要为效果添加逻辑,而是将组件分为:

  • 状态为
  • 已使用
    React.memo进行记忆的哑控制无状态组件
您的哑组件可以是纯的(就好像它实现了
shouldComponentUpdate
),您的智能状态处理组件可以是“哑的”,不必担心将状态更新为相同的值

例如:

之前
导出默认函数Foo(){
const[state,setState]=useState({foo:“1”})
const handler=useCallback(newValue=>setState({foo:newValue}))
返回(
值:{state.foo}
)
之后
constfoochild=React.memo({foo,handler})=>{
返回(
值:{state.foo}
)
})
导出默认函数Foo(){
const[state,setState]=useState({foo:“1”})
const handler=useCallback(newValue=>setState({foo:newValue}))
返回
}

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

如果您检查object或object数组类型的道具的更改,我建议在useEffect deps上使用JSON.stringify。这非常简单

const obj={foo:'bar'}
常量[状态,设置状态]=使用状态(obj)
useffect(()=>{
设置状态(obj)

},[JSON.stringify(obj)]
您能否在
useffect
回调中添加您将在
shouldComponentUpdate
中使用的逻辑,并且仅当新值与
有效载荷
不相同时才调用
setPayload
?这里没有道具,只需在文档中声明即可React.memo不比较状态,因为没有要比较的单个状态对象。但您也可以使子对象纯,甚至可以使用useMemo优化单个子对象。"这里有一个演示它不起作用:@agconti首先,你必须学习如何使用React.memo,然后你必须自己澄清什么时候在React中实际渲染这里没有道具,这是正确的,UseStateWithNewObject组件实际重新渲染的唯一原因是因为renderCountNewObject存在于
h3。如果您删除它,您的控制台日志无论如何都会被调用,但这并不意味着每次都呈现DOM节点。而且我从来没有说过您必须在React.memo中使用newobject包装UseStateWithNewObject。您必须优化子组件。在设置状态之前检查效果是否相等是一个好主意!对我来说,这打破了separ但是,关注点的分散。
useffect
应该只关注保持状态更新。而组件应该确定如何以及何时呈现状态。例如,如果我将此逻辑转换为自定义挂钩,则另一个组件总是更新,而另一个组件只有在出现错误时才更新可能是有利的
// 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]);
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>
  )
const FooChild = React.memo(({foo, handler}) => {
  return (
    <div>
      <SomeWidget onEvent={handler} />
      Value: {{ state.foo }}
    </div>
  )
})

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

  return <FooChild handler={handler} foo={state.foo} />
}