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,在数据更改时只重新呈现我的组件
通常,对于状态更改,在使用functionaluseState
或使用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} />
}