自定义 useEffect 第二个参数

IT技术 javascript reactjs
2021-04-28 17:41:41

新的 React API 包括useEffect(),它的第二个参数采用Object哪个 React 差异来查看组件是否更新。

例如

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

[props.source]问题的论点在哪里

我的问题是:我可以定义一个自定义函数来运行以检查 prop 是否已更改吗?

我有一个自定义对象,而 React 似乎无法判断它何时发生了变化。

3个回答

AFAIK 目前不可能。有一些解决方法:

1)在里面手动做深度比较useEffect存储上一个。您可以使用的value,useState或者甚至更好,useRef如下所示:https : //overreacted.io/a-complete-guide-to-useeffect/

2) 与JSON.stringify(props.source). 可以,如果数据不是太大。请注意,这stringify可能会产生不一致的结果(更改顺序的对象中的键将更改输出)。

3)散列md5(props.source)(或其他一些快速/轻量级散列)。比以前更真实但更慢。

这个答案归功于@Tholle 在 useEffect 2nd param 中使用对象,而无需将其字符串化为 JSON

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>

为什么用useRef而不是useState

通过使用从 useState 返回的函数,组件将被重新渲染,在这种情况下这是不希望的

从其他答案中汲取灵感,我一直在使用以下钩子

import React from 'react';

import IsEqual from 'lodash/isEqual';

/**
 * The role of this function is to ensure that useEffect doesn't not
 * fire unless the value of a varr (variable but "var" is a reserved word 😉)
 * changes. For examples:
 * const a = { 'hi': 5 }
 * const b = { 'hi': 5 }
 *
 * a === b // false
 * IsEqual(a, b) // true
 *
 * By default the React.useEffect(() => {}, [dependencies]) only does "===" but
 * for cases where we only want useEffect to re-fire when the true value
 * of a varr changes "IsEqual", we use this.
 *
 * @param varr: any
 * @returns {function(): *}
 */
const useMostRecentImmutableValue = (varr) => {
  let mostRecentValue = varr;
  const mostRecentPointer = React.useRef(varr);

  return () => {
    // short circuit if "shallow equality"
    if (mostRecentPointer.current === varr) {
      return mostRecentValue;
    }

    // mostRecentValue only updates when the true value of varr changes
    if (!IsEqual(varr, mostRecentPointer.current)) {
      mostRecentValue = varr;
    }

    // mostRecentPointer changes every time "shallow equality" fails but
    // after we've checked deep equality
    mostRecentPointer.current = varr;

    return mostRecentValue;
  };
};

export default useMostRecentImmutableValue;

然后在一个钩子中,我会做这样的事情:

import useMostRecentImmutableValue from 'use-most-recent-immutable-value.hook';

const useMyHook = (foo = { 'hi': 5 }) => {
  const mostRecentFoo = useMostRecentImmutableValue(foo);

  React.useEffect(() => {
    const unsubscribe = mySubscriptionFunction(mostRecentFoo);
    return () => {
      unsubscribe();
    };
  }, [mostRecentFoo]);

};

export default useMyHook;