Zustand selector 发生 infinate loops的原因以及解决
Zustand selector 发生 infinate loops
做zustand tutorial project的时候,使用选择器方法引入store,出现Maximum update depth exceeded,也就是组件一直重新渲染,改成直接使用store就没有不会出现这个问题。如下:
// const [xIsNext, setXIsNext] = useGameStore((state) => [
// state.xIsNext,
// state.setXIsNext,
// ]);
const { history, setHistory, xIsNext, setXIsNext } = useGameStore()
目前看到有关这个问题的博客很少,特此记录一下。
感谢一下我的mentor们,如此认真地教我,给我充足的时间学习
问题产生原因
官方文档的解释如下:
文档链接
也就是说,这是因为Zustand v5的改动,当selector返回一个新的引用,可能导致无限循环
细究了一下,问题产生原因如下:
- react的默认比较为引用比较,在zustandV5中沿用了这种方式。
- 每次重新渲染,useStore都会再执行一次,selector每次都返回的是一个新的引用
- zustand使用浅比较判断状态变化,触发重新渲染,以此往复
比如像下面这样使用selector
const { history, setHistory} = useGameStore(((state) => ({
history: state.history,
setHistory: state.setHistory,
})));
const [searchValue, setSearchValue] = useStore((state)
=> [ state.searchValue, state.setSearchValue,])
以上两种方法selector分别返回一个对象和一个数组,每次重新渲染,其引用都会发生变化,也就触发了infinate loops
解决方法
- 避免在selector中返回对象
这是我认为最直接的办法:
const history = useGameStore((state) => state.history);
const setHistory = useGameStore((state) => state.setHistory);
//不要像下面这样
// const { history, setHistory} = useGameStore(((state) => ({
// history: state.history,
// setHistory: state.setHistory,
// })));
- 采用文档中介绍的使用useShllow解决:
但是useShllow比较对象只会比较最浅层的值,要注意可能产生的副作用
const { history, setHistory, xIsNext, setXIsNext } = useGameStore(useShallow((state) => ({
history: state.history,
setHistory: state.setHistory,
xIsNext: state.xIsNext,
setXIsNext: state.setXIsNext,
})));
附: zustand实现原理
zustand源代码中useShallow实现部分如下(也就是比较值而非比较引用):
import React from 'react'
import { shallow } from '../vanilla/shallow.ts'
export function useShallow<S, U>(selector: (state: S) => U): (state: S) => U {
const prev = React.useRef<U>(undefined)
return (state) => {
const next = selector(state)
return shallow(prev.current, next)
? (prev.current as U)
: (prev.current = next)
}
}