提问人:entropyfeverone 提问时间:3/23/2022 更新时间:3/24/2022 访问量:1623
是否可以覆盖 React 的 useState、useMemo、useEffect 钩子的相等函数?
Is it possible to override the equality function of React's useState, useMemo, useEffect hooks?
问:
假设我有以下代码:
const [obj, setObj] = useState({ value: 0 });
// somewhere else
setState({value: 0});
// somewhere else
const value = useMemo(() => obj.value, [ obj ]);
// somewhere else
useEffect(() => { console.log(obj.value) }, [ obj ] );
是否有可能拥有类似的东西:
const [ obj, setObj ] = useStateWithSelector(obj, (prev, next) => prev.value === next.value);
所以我可以告诉 react 只有在 equalityFunction 时才能重新渲染
(prev, next) => prev.value === next.value
返回 false ?
答:
2赞
よつば
3/24/2022
#1
React 建议的成语
解决方案是不要使用复杂的数据类型(如 Object 或 Array)作为 React 效果的依赖项。使用代替 .[obj.value]
[obj]
来自 useEffect 文档:
依赖项数组不会作为参数传递给效果函数。不过,从概念上讲,这就是它们所代表的:效果函数中引用的每个值也应该出现在 dependencies 数组中
Run在下面的代码示例中,输入一个值并单击几次。请注意,仅当在输入中键入新值时,状态才会更改。Set
function App() {
const [input, setInput] = React.useState("")
const [obj, setObj] = React.useState({ value: input })
React.useEffect(_ => {
console.log("state changed", obj.value)
}, [obj.value]) // ✅ don't use objects as dependencies
return <div>
<input
onChange={e => setInput(e.target.value)}
value={input}
placeholder="enter any value"
/>
<button
onClick={_ => setObj({ value: input })}
children="Set"
/>
<p>Repeated presses of <kbd>Set</kbd> will not triggger state change</p>
<pre>{JSON.stringify(obj)}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
定制钩子解决方案
如果必须用作依赖项,则可以按照建议编写自定义钩子。[obj]
useStateWithGuard
function useStateWithGuard(initState, guard) {
const [value, setValue] = React.useState(initState)
return [
value,
next => setValue(prev => guard(prev, next) ? prev : next)
]
}
Run在下面的代码示例中,输入一个值并单击几次。 用作依赖项,但仅当新值通过保护时,状态才会更改。Set[obj]
function App() {
const [input, setInput] = React.useState("")
const [obj, setObj] = useStateWithGuard(
{ value: "" },
(prev, next) => prev.value === next.value
)
React.useEffect(_ => {
console.log("state changed", obj.value)
}, [obj]) // ⚠️ works now but maybe still a bad practice
return <div>
<input
onChange={e => setInput(e.target.value)}
value={input}
placeholder="enter any value"
/>
<button
onClick={_ => setObj({ value: input })}
children="Set"
/>
<p>Repeated presses of <kbd>Set</kbd> will not triggger state change</p>
<pre>{JSON.stringify(obj)}</pre>
</div>
}
function useStateWithGuard(initState, guard) {
const [value, setValue] = React.useState(initState)
return [
value,
next => setValue(prev => guard(prev, next) ? prev : next)
]
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
评论
0赞
entropyfeverone
3/24/2022
guard 函数无法按预期工作,因为它绝对应该将对象设置为下一个值属性(可能另一个属性已更改),但如果属性值已更改,则只有“触发器”才会做出反应,因为该值是我想进行一系列操作的原因。
0赞
よつば
3/24/2022
如果另一个属性发生更改,则表示状态已更改,应触发重新呈现。当你调用钩子时,你写了守卫,所以它可以是你想要的任何东西。这是一个示例,代码非常简单,您可以对其进行更改以满足您的确切需求。useStateWithGuard
评论
obj.value
useMemo
useEffect
obj