将函数放在 useEffect 中是一种好习惯吗?

Posted

技术标签:

【中文标题】将函数放在 useEffect 中是一种好习惯吗?【英文标题】:Is it good practice to put functions inside useEffect? 【发布时间】:2022-01-20 07:41:45 【问题描述】:

问题是:

假设我在组件中有相当广泛的逻辑。我应该将所有这些逻辑放在useEffects 中,还是应该将这些函数移到useEffect 之外并使用useCallback

我找不到任何经过同行评审的方法来解决这个主题。

下面是我的意思的一个例子:

    useEffect 方法中的函数
  useEffect(() => 
    const fun1 = () => 
      /**
       * 50 lines of code
       */
    
    fun1()
  , [var1, var2])

  useEffect(() => 
    const fun2 = () => 
      /**
       * 50 lines of code
       */
    
    fun2()
  , [var3, var4])
    useEffect 方法之外的函数
  const fun1 = useCallback(() => 
    /**
     * 50 lines of code
     */
  , [var1, var2])

  const fun2 = useCallback(() => 
    /**
     * 50 lines of code
     */
  , [var3, var4])

  useEffect(() => 
    fun1()
  , [fun1])

  useEffect(() => 
    fun2()
  , [fun2])

让我们假设它更复杂。首选哪种方法?

【问题讨论】:

你把它们放在里面还是外面有区别吗?如果有怎么办? 我认为这两种方法在功能方面没有太大区别。区别在于可读性。我不知道在性能方面是否有什么不同。在我看来,较小的useEffect 会带来更好的可读性。如果只是因为我可以快速查看useEffect 总共有多少,并且我可以并排定义所有函数,上面useEffect Dylan gave you some good generic advice,但无法为您的案例提供建议,因为您尚未展示您的功能的作用。很明显,它们是围绕组件状态的闭包,但除此之外是不透明的。根据他们所做的以及您愿意进行哪些类型的重构(例如更改参数、返回值等),有不止一种优化方法,甚至不考虑 DX。 【参考方案1】:

有很多方法可以在effect hook 中重构closure。因为你还没有展示你的函数代码,所以考虑一下计数器组件的常见示例:

鉴于以下效用函数在范围内...

function isSingular (n: number): boolean 
  return Math.abs(n) === 1;

function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  useEffect(() => 
    const updateMessage = () => 
      setMessage(`You clicked $count time$isSingular(count) ? '' : 's'`);
    ;
    updateMessage();
  , [count, setMessage]);

  return (
    <div>
      <div>message</div>
      <button onClick=() => setCount(n => n + 1)>Increment</button>
    </div>
  );

该组件使用两个状态变量,countmessage(根据 count 计算得出)。闭包updateMessage 定义在效果挂钩的回调中,它封装了countsetMessage。有很多方法可以重构它:

重要提示:无论您使用哪种方法,如果涉及使用dependency array,请确保依赖项列表正确且详尽。


您可以在效果挂钩之外定义闭包,并将其作为回调直接传递(这是您询问的那个):

function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  const updateMessage = () => 
    setMessage(`You clicked $count time$isSingular(count) ? '' : 's'`);
  ;

  useEffect(updateMessage, [count, setMessage]);

  return // the same JSX...

<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.6/babel.min.js"></script><script>Babel.registerPreset('tsx', presets: [[Babel.availablePresets['typescript'], allExtensions: true, isTSX: true]]);</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">

// You'd use ESM:
// import ReactDOM from 'react-dom';
// import default as React, useEffect, useState from 'react';

// This snippet uses UMD:
const useEffect, useState = React;

function isSingular (n: number): boolean 
  return Math.abs(n) === 1;


function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  const updateMessage = () => 
    setMessage(`You clicked $count time$isSingular(count) ? '' : 's'`);
  ;

  useEffect(updateMessage, [count, setMessage]);

  return (
    <div>
      <div>message</div>
      <button onClick=() => setCount(n => n + 1)>Increment</button>
    </div>
  );


ReactDOM.render(<Example />, document.getElementById('root'));

</script>

您可以将其定义为 impure function 而不是闭包:

function updateMessage (
  count: number,
  setString: React.Dispatch<React.SetStateAction<string>>,
): void 
  setString(`You clicked $count time$isSingular(count) ? '' : 's'`);
;

function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');

  useEffect(() => updateMessage(count, setMessage), [count, setMessage]);

  return // the same JSX...


您可以将与message 状态相关的所有内容提取到custom hook:

function useMessage (count: number): string 
  return `You clicked $count time$isSingular(count) ? '' : 's'`;
;

function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const message = useMessage(count);

  return // the same JSX...


您可以使用memo hook:

function Example (): React.ReactElement 
  const [count, setCount] = useState(0);
  const message = useMemo(() => `You clicked $count time$isSingular(count) ? '' : 's'`, [count]);

  return // the same JSX...


希望这能给您一些观点,然后您可以将其应用于您的组件代码。

【讨论】:

这个useEffect(updateMessage, [count, setMessage]); 是我要找的。现在看来很明显了。谢谢! :)【参考方案2】:

假设我在组件中有相当广泛的逻辑。

您可能应该将逻辑从组件中移出并放入钩子中,因为这是钩子尝试solve 的主要问题之一。

除此之外,您的第二种方法引入了useCallback 的附加依赖项。你这样做有什么收获?不多。但是丢失了很多东西(更复杂的加上额外的依赖)。

【讨论】:

从我的角度来看,我更愿意先在一个地方声明所有函数,然后在下面开始使用它们。但我明白你的意思。我同意你对答案的第一部分的看法。如果这些问题开始出现,可能是组件中的逻辑过多。

以上是关于将函数放在 useEffect 中是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

将函数放入 javascript 对象中是一种好习惯吗? [复制]

将 if 语句放在 main() 或函数内部以响应用户输入是一种好习惯吗?

将 csrf 令牌存储在元标记中是一种好习惯吗?

将嵌入式码头和 GRPC 服务器运行在同一个 JVM 中是一种好习惯吗?

将所有环境(dev uat prod)的 jar 和配置打包在一个 zip 中是一种好习惯吗?

使用表单属性并将输入元素放在 HTML 中的表单标记之外是一种好习惯吗?