【问题标题】:Custom useEffect Second Argument自定义 useEffect 第二个参数
【发布时间】:2019-05-05 05:30:56
【问题描述】:

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

例如

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

[props.source] 是有问题的参数。

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

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

【问题讨论】:

  • 嘿,是的,here's 一个指向 GitHub 代码的链接,他在那里进行自定义相等性检查。

标签: javascript reactjs


【解决方案1】:

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

1) 在useEffect 中手动进行深度比较。存储上一个。值你可以使用useState,或者更好的是useRef,如下所示:https://overreacted.io/a-complete-guide-to-useeffect/

2) 使用JSON.stringify(props.source) 进行散列。可以的,如果数据不是太大的话。请注意stringify 可能会产生不一致的结果(对象中的键更改顺序会改变输出)。

3) 使用md5(props.source) 进行散列(或其他一些快速/轻量级散列)。比以前更现实,但速度较慢。

【讨论】:

  • 你能澄清一下手动深度比较的第一点吗?我尝试使用返回自定义比较的函数,但没有运气。
  • 视频不是免费的。说我需要注册,每月花费 250 美元或 40 美元。此处的答案至少需要 40 美元才能查看,这并不酷!
  • @CraigV 当我发布答案时它是免费的。他们可能改变了条款。您可以免费查看 YouTube 上的 LevelUp 教程。他们最近开始了一个很棒的钩子系列。
  • @CraigV 也是,代码是开源的。你可以看到它here
  • 好的。我换了链接。现在是 Dan 关于同一主题的 Abramov 文章。
【解决方案2】:

这个答案的功劳归于@Tholle use object in useEffect 2nd param without having to stringify it to 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 返回的函数,组件将被重新渲染,这在这种情况下是不需要的

【讨论】:

    【解决方案3】:

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

    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;
    

    【讨论】:

      猜你喜欢
      • 2021-04-02
      • 2021-01-28
      • 2021-03-11
      • 2019-08-09
      • 2020-08-17
      • 2017-10-16
      • 2022-10-31
      • 1970-01-01
      • 2020-02-28
      相关资源
      最近更新 更多