确定哪个依赖项数组变量导致useEffect钩子触发

Cum*_*bus 12 javascript reactjs react-hooks

有没有一种简单的方法来确定useEffectDependency数组中的哪个变量触发函数重新触发?

简单地注销每个变量可能会产生误导,如果a是函数和b对象,则它们在登录时可能看起来相同,但实际上却有所不同,从而引起useEffect触发。

例如:

React.useEffect(() => {
  // which variable triggered this re-fire?
  console.log('---useEffect---')
}, [a, b, c, d])
Run Code Online (Sandbox Code Playgroud)

我当前的方法一直是一个一个地删除依赖变量​​,直到我注意到导致过多useEffect调用的行为,但是必须有一种更好的方法来缩小此范围。

Bra*_*yan 82

我最终从各种答案中汲取了一点点来制作自己的钩子。我希望能够直接删除一些东西useEffect来快速调试正在触发的依赖项useEffect

const usePrevious = (value, initialValue) => {
  const ref = useRef(initialValue);
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
Run Code Online (Sandbox Code Playgroud)
const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
  const previousDeps = usePrevious(dependencies, []);

  const changedDeps = dependencies.reduce((accum, dependency, index) => {
    if (dependency !== previousDeps[index]) {
      const keyName = dependencyNames[index] || index;
      return {
        ...accum,
        [keyName]: {
          before: previousDeps[index],
          after: dependency
        }
      };
    }

    return accum;
  }, {});

  if (Object.keys(changedDeps).length) {
    console.log('[use-effect-debugger] ', changedDeps);
  }

  useEffect(effectHook, dependencies);
};
Run Code Online (Sandbox Code Playgroud)

下面是两个例子。对于每个示例,我假设dep2从“foo”更改为“bar”。示例 1 显示了通过的输出dependencyNames,示例 2 显示了带有 dependencyNames.

示例 1

前:

useEffect(() => {
  // useEffect code here... 
}, [dep1, dep2])
Run Code Online (Sandbox Code Playgroud)

后:

useEffectDebugger(() => {
  // useEffect code here... 
}, [dep1, dep2])
Run Code Online (Sandbox Code Playgroud)

控制台输出:

{
  1: {
    before: 'foo',
    after: 'bar'
  }
}
Run Code Online (Sandbox Code Playgroud)

对象键“1”表示更改的依赖项的索引。在这里,已dep1更改并且是依赖项中的第二项,或索引 1

示例 2

前:

useEffect(() => {
  // useEffect code here... 
}, [dep1, dep2])
Run Code Online (Sandbox Code Playgroud)

后:

useEffectDebugger(() => {
  // useEffect code here... 
}, [dep1, dep2], ['dep1', 'dep2'])
Run Code Online (Sandbox Code Playgroud)

控制台输出:

{
  dep2: {
    before: 'foo',
    after: 'bar'
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 您应该将其发布到 NPM! (25认同)
  • 您可能会收到来自 React 的警告,指出 *“React Hook useEffect 缺少依赖项:'effectHook'。”* 您可以通过将effectHook函数作为依赖项包含进来来处理此问题,只需将 `useEffect(effectHook, dependencys);` 更改为`useEffect(effectHook, [effectHook, ...依赖项]);` (8认同)

Dev*_*ode 25

这个图书馆...... ,就像一个魅力! @simbathesailor/use-what-changed

  1. Installnpm/yarn--dev--no-save
  2. 添加导入:
import { useWhatChanged } from '@simbathesailor/use-what-changed';
Run Code Online (Sandbox Code Playgroud)
  1. 称它为:
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]

useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
  // your effect
}, deps);
Run Code Online (Sandbox Code Playgroud)

在控制台中创建这个漂亮的图表:

从github加载的图像

有两个常见的罪魁祸首:

  1. 一些对象像这样传入:
// Being used like:
export function App() {
  return <MyComponent fetchOptions={{
    urlThing: '/foo',
    headerThing: 'FOO-BAR'
  })
}
export const MyComponent = ({fetchOptions}) => {
  const [someData, setSomeData] = useState()
  useEffect(() => {
    window.fetch(fetchOptions).then((data) => {
      setSomeData(data)
    })

  }, [fetchOptions])

  return <div>hello {someData.firstName}</div>
}
Run Code Online (Sandbox Code Playgroud)

在对象情况下的修复,如果可以的话,在组件渲染之外打破一个静态对象:

const fetchSomeDataOptions = {
  urlThing: '/foo',
  headerThing: 'FOO-BAR'
}
export function App() {
  return <MyComponent fetchOptions={fetchSomeDataOptions} />
}
Run Code Online (Sandbox Code Playgroud)

你也可以用 useMemo 包装:

export function App() {
  return <MyComponent fetchOptions={
    useMemo(
      () => {
        return {
          urlThing: '/foo',
          headerThing: 'FOO-BAR',
          variableThing: hash(someTimestamp)
        }
      },
      [hash, someTimestamp]
    )
  } />
}
Run Code Online (Sandbox Code Playgroud)

相同的概念在一定程度上适用于函数,除非您最终会得到陈旧的闭包。

  • (点表示值没有改变。绿色勾号表示它确实改变了。)甚至还有一个 babel 插件(认真地去明星这个家伙的项目!)https://github.com/simbathesailor/use-what-c​​hanged (2认同)
  • @JamilAlisgenderov 我认为 useWhatChanged 必须使用 console.table .. 因此,如果您尝试在不支持 console.table 的旧浏览器中进行测试,您可以检查 console.table 是否已定义。您还可以验证正常的 console.log('发生了变化', '表已定义?', !!console.table); 在 useEffect 挂钩日志中。否则......也许用你的反应版本+浏览器在github上提交一个问题 (2认同)

Rya*_*ell 9

更新

经过一些实际使用后,到目前为止,我喜欢以下解决方案,它借鉴了 Retsam 解决方案的某些方面:

const compareInputs = (inputKeys, oldInputs, newInputs) => {
  inputKeys.forEach(key => {
    const oldInput = oldInputs[key];
    const newInput = newInputs[key];
    if (oldInput !== newInput) {
      console.log("change detected", key, "old:", oldInput, "new:", newInput);
    }
  });
};
const useDependenciesDebugger = inputs => {
  const oldInputsRef = useRef(inputs);
  const inputValuesArray = Object.values(inputs);
  const inputKeysArray = Object.keys(inputs);
  useMemo(() => {
    const oldInputs = oldInputsRef.current;

    compareInputs(inputKeysArray, oldInputs, inputs);

    oldInputsRef.current = inputs;
  }, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};
Run Code Online (Sandbox Code Playgroud)

然后可以通过复制依赖数组文字并将其更改为对象文字来使用它:

useDependenciesDebugger({ state1, state2 });
Run Code Online (Sandbox Code Playgroud)

这允许日志记录知道变量的名称,而无需为此目的提供任何单独的参数。

编辑 useDependenciesDebugger


Ret*_*sam 7

据我所知,开箱即用并没有真正简单的方法可以做到这一点,但是您可以放入一个自定义钩子来跟踪其依赖项并记录哪个更改了:

// Same arguments as useEffect, but with an optional string for logging purposes
const useEffectDebugger = (func, inputs, prefix = "useEffect") => {
  // Using a ref to hold the inputs from the previous run (or same run for initial run
  const oldInputsRef = useRef(inputs);
  useEffect(() => {
    // Get the old inputs
    const oldInputs = oldInputsRef.current;

    // Compare the old inputs to the current inputs
    compareInputs(oldInputs, inputs, prefix)

    // Save the current inputs
    oldInputsRef.current = inputs;

    // Execute wrapped effect
    func()
  }, inputs);
};
Run Code Online (Sandbox Code Playgroud)

compareInputs位可能如下所示:

const compareInputs = (oldInputs, newInputs, prefix) => {
  // Edge-case: different array lengths
  if(oldInputs.length !== newInputs.length) {
    // Not helpful to compare item by item, so just output the whole array
    console.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
    console.log("Old inputs:", oldInputs)
    console.log("New inputs:", newInputs)
    return;
  }

  // Compare individual items
  oldInputs.forEach((oldInput, index) => {
    const newInput = newInputs[index];
    if(oldInput !== newInput) {
      console.log(`${prefix} - The input changed in position ${index}`);
      console.log("Old value:", oldInput)
      console.log("New value:", newInput)
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

你可以这样使用它:

useEffectDebugger(() => {
  // which variable triggered this re-fire?
  console.log('---useEffect---')
}, [a, b, c, d], 'Effect Name')
Run Code Online (Sandbox Code Playgroud)

你会得到如下输出:

Effect Name - The input changed in position 2
Old value: "Previous value"
New value: "New value"
Run Code Online (Sandbox Code Playgroud)


arc*_*inz 5

还有\xe2\x80\x99s 另一个堆栈溢出线程,声明您可以使用 useRef 来查看以前的值。

\n\n

https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

\n

  • 发布其他 StackOverflow 线程的链接可能会很有用。 (6认同)