использовать объект в useEffect 2-й параметр без необходимости его преобразования в JSON - PullRequest
1 голос
/ 23 апреля 2019

В JS два объекта не равны.

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

Так что я не могу использовать объект в useEffect (React hooks) в качестве второго параметра, так как он всегда будет считаться ложным (поэтому он будет перерисован):

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

Выполнение этого (код выше) приводит к выполнению эффекта каждый раз повторный рендеринг компонента, поскольку объект считается не равным каждый раз.

Я могу «взломать» это, передав объект в виде строкового значения JSON, но это немного грязное IMO:

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

Есть ли лучший способ сделать это и избежать нежелательных вызовов эффекта?

Примечание: объект имеет вложенные свойства. Эффекты должны запускаться при каждом изменении внутри этого объекта.

1 Ответ

2 голосов
/ 23 апреля 2019

Вы можете создать пользовательский хук, который отслеживает предыдущий массив зависимостей в ref и сравнивает объекты, например, с Lodash isEqual, и запускает предоставленную функцию, только если они не равны.

Пример

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

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

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

    if (isFirst.current || !isSame) {
      fn();
    }

    isFirst.current = false;
    prevDeps.current = deps;
  }, 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>
...