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

发布于 2025-01-16 19:37:52 字数 548 浏览 1 评论 0原文

假设我有这样的代码:

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);

所以我可以告诉反应仅当 equalFunction

(prev, next) => prev.value === next.value

返回 false 时才重新渲染?

Assume I have this code:

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 ] );

Is it possible to have something like:

const [ obj, setObj ] = useStateWithSelector(obj, (prev, next) => prev.value === next.value);

So I can tell react to rerender only if the equalityFunction

(prev, next) => prev.value === next.value

return false ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

_畞蕅 2025-01-23 19:37:53

react 建议的习惯用法

解决方案是不要使用复杂的数据类型(如对象或数组)作为 React 效果的依赖项。使用 [obj.value] 而不是 [obj]

来自 useEffect 文档

依赖项数组不会作为参数传递给效果函数。但从概念上讲,这就是它们所代表的:效果函数内引用的每个值也应该出现在依赖项数组中

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)
  ]
}

运行下面的代码示例,输入一个值并单击设置几次。 [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>

react suggested idiom

The solution is not to use complex data types like Object or Array as dependencies for React effects. Use [obj.value] instead of [obj].

From the useEffect docs:

The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array

Run the code example below, enter a value and click Set several times. Notice the state is only changed when a fresh value is typed in the input.

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>

custom hook solution

If you must use [obj] as a dependency, you could write a useStateWithGuard custom hook like you suggested.

function useStateWithGuard(initState, guard) {
  const [value, setValue] = React.useState(initState)
  return [
    value,
    next => setValue(prev => guard(prev, next) ? prev : next)
  ]
}

Run the code example below, enter a value and click Set several times. [obj] is used as a dependency but the state only changes when the new value passes the guard.

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文