是否可以覆盖 React 的 useState、useMemo、useEffect 钩子的相等函数?

Is it possible to override the equality function of React's useState, useMemo, useEffect hooks?

提问人:entropyfeverone 提问时间:3/23/2022 更新时间:3/24/2022 访问量:1623

问:

假设我有以下代码:

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 ?

javascript reactjs react-hooks 相等 rerender

评论

0赞 よつば 3/24/2022
用作 和 的依赖项,而不是本身。obj.valueuseMemouseEffectobj

答:

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