在 useEffect 第二个参数中使用对象,而不必将其字符串化为 JSON

use object in useEffect 2nd param without having to stringify it to JSON

在 JS 中两个对象不相等。

const a = {}, b = {};
console.log(a === b);

所以我不能在 useEffect(React hooks)中使用一个对象作为第二个参数,因为它总是被认为是假的(所以它会重新渲染):

function MyComponent() {
  // ...
  useEffect(() => {
    // do something
  }, [myObject]) // <- this is the object that can change.
}

这样做(上面的代码),导致 运行每次 宁效果 组件重新渲染,因为每次都认为对象不相等。

我可以 "hack" 通过将对象作为 JSON 字符串化值传递,但 IMO 有点脏:

function MyComponent() {
  // ...
  useEffect(() => {
    // do something
  }, [JSON.stringify(myObject)]) // <- yuck

是否有更好的方法来避免不必要的效果调用?

旁注:该对象具有嵌套属性。效果必须 运行 对此对象内部的每个更改。

您可以创建一个自定义挂钩来跟踪 ref 中的先前依赖项数组并将对象与例如Lodash isEqual 并且仅在它们不相等时才运行提供的函数。

例子

const { useState, useEffect, useRef } = React;
const { isEqual } = _;

function useDeepEffect(fn, deps) {
  const isFirst = useRef(true);
  const prevDeps = useRef(deps);

  useEffect(() => {
    const isFirstEffect = isFirst.current;
    const isSame = prevDeps.current.every((obj, index) =>
      isEqual(obj, deps[index])
    );

    isFirst.current = false;
    prevDeps.current = deps;

    if (isFirstEffect || !isSame) {
      return fn();
    }
  }, deps);
}

function App() {
  const [state, setState] = useState({ foo: "foo" });

  useEffect(() => {
    setTimeout(() => setState({ foo: "foo" }), 1000);
    setTimeout(() => setState({ foo: "bar" }), 2000);
  }, []);

  useDeepEffect(() => {
    console.log("State changed!");
  }, [state]);

  return <div>{JSON.stringify(state)}</div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

@Tholle 的上述回答是完全正确的。我在 dev.to

上写了一个 post

在 React 中,可以使用 useEffect hook 在功能组件中处理副作用。在这个 post 中,我将讨论保存我们的 props/state 的依赖数组,特别是当依赖数组中有一个对象时会发生什么。

useEffect 挂钩 运行 即使依赖数组中的一个元素发生变化。 React 这样做是为了优化目的。另一方面,如果你传递一个空数组,那么它永远不会重新 运行s.

但是,如果此数组中存在对象,事情就会变得复杂。那么即使修改了对象,钩子也不会重新运行,因为它不会对那个对象的这些依赖变化做深度对象比较。有几种方法可以解决这个问题。

  1. 使用 lodash 的 isEqual method and usePrevious hook。此挂钩在内部使用一个 ref 对象,该对象包含一个可以保存值的可变 current 属性。

    It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.

    const prevDeeplyNestedObject = usePrevious(deeplyNestedObject)
    useEffect(()=>{
        if (
            !_.isEqual(
                prevDeeplyNestedObject,
                deeplyNestedObject,
            )
        ) {
            // ...execute your code
        }
    },[deeplyNestedObject, prevDeeplyNestedObject])
    
  2. 使用 useDeepCompareEffect hook 作为 useEffect 对象钩子的替代品

    import useDeepCompareEffect from 'use-deep-compare-effect'
    ...
    useDeepCompareEffect(()=>{
        // ...execute your code
    }, [deeplyNestedObject])
    
  3. 使用类似于解决方案#2

    useCustomCompareEffecthook

我准备了一个与这个post相关的CodeSandbox example。 fork 自己查一下

依赖数组中的普通(非嵌套)对象

我只是想挑战这两个答案,并询问如果 object in dependency array 没有嵌套会发生什么。如果那是没有属性的普通对象,那么更深一层。

在我看来,在这种情况下,useEffect 功能无需任何额外检查即可工作。

我只是想写这篇文章,学习并在我错的时候更好地向自己解释。非常欢迎任何建议,解释。

这里可能更容易检查和使用示例:https://codesandbox.io/s/usehooks-bt9j5?file=/src/App.js

const {useState, useEffect} = React;

function ChildApp({ person }) {
  useEffect(() => {
    console.log("useEffect ");
  }, [person]);

  console.log("Child");
  return (
    <div>
      <hr />
      <h2>Inside child</h2>
      <div>{person.name}</div>
      <div>{person.age}</div>
    </div>
  );
}
function App() {
  const [person, setPerson] = useState({ name: "Bobi", age: 29 });
  const [car, setCar] = useState("Volvo");

  function handleChange(e) {
    const variable = e.target.name;
    setPerson({ ...person, [variable]: e.target.value });
  }

  function handleCarChange(e) {
    setCar(e.target.value);
  }

  return (
    <div className="App">
      Name:
      <input
        name="name"
        onChange={(e) => handleChange(e)}
        value={person.name}
      />
      <br />
      Age:
      <input name="age" onChange={(e) => handleChange(e)} value={person.age} />
      <br />
      Car: <input name="car" onChange={(e) => handleCarChange(e)} value={car} />
      <ChildApp person={person} />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-
dom.development.js"></script>
<div id="root"></div>

你可以只扩展useEffect数组中的属性:

var obj = {a: 1, b: 2};

useEffect(
  () => {
    //do something when any property inside "a" changes
  },
  Object.entries(obj).flat()
);

Object.entries(obj) returns 对数组 ([["a", 1], ["b", 2]]) 和 .flat() 将数组展平为: ["a", 1, "b", 2]

请注意,对象中的属性数必须保持不变,因为数组的长度不能更改,否则 useEffect 将引发错误。