有时我们只需要运行一次代码。 useRunOnce 是一个钩子,它在组件安装时运行一次函数,或者每个浏览器会话运行一次。这篇文章解释了这个钩子的常见用例以及什么时候不使用它。

EDITED:更新了这篇文章,强调这个钩子应该只在特殊情况下使用,而不是日常使用。多次运行 useEffect 并以干净的方式处理后果通常是更好的解决方案。在使用它之前,请阅读有关何时不使用此钩子的信息,并尝试找出是否有不使用它的方法。

感谢 Luke Shiru,他发表的评论启发了我,用例不是很清楚。

在本文中

  • 使用RunOnce Hook

  • 在山上运行一次

  • 每个会话运行一次

  • 何时不使用

  • 用例

  • 示例

  • 摘要

使用RunOnce Hook

下面你可以看到 useRunOnce 钩子是如何在 JavaScript 和 typescript 中实现的。该钩子可用于在挂载或每个浏览器会话上运行一次函数。

该钩子将一个对象作为参数,具有两个可用属性。首先,一个必需的 fn 属性,它将运行的回调函数。如果没有传递其他属性,则每次挂载组件时回调函数都会运行一次。

如果传递了第二个属性 sessionKey,则挂钩将改为利用会话存储在每个浏览器会话中运行一次回调函数。这将在本文后面进一步解释。

该代码也可在CodeSandbox和GitHub获得。您可以在 CodeSandbox 上试用它,但我将在本文中详细解释它的工作原理。

JavaScript

import { useEffect, useRef } from "react";

const useRunOnce = ({ fn, sessionKey }) => {
  const triggered = useRef(false);

  useEffect(() => {
    const hasBeenTriggered = sessionKey
      ? sessionStorage.getItem(sessionKey)
      : triggered.current;

    if (!hasBeenTriggered) {
      fn();
      triggered.current = true;

      if (sessionKey) {
        sessionStorage.setItem(sessionKey, "true");
      }
    }
  }, [fn, sessionKey]);

  return null;
};

export default useRunOnce;

进入全屏模式 退出全屏模式

打字稿

import React, { useEffect, useRef } from "react";

export type useRunOnceProps = {
  fn: () => any;
  sessionKey?: string;
};

const useRunOnce: React.FC<useRunOnceProps> = ({ fn, sessionKey }) => {
  const triggered = useRef<boolean>(false);

  useEffect(() => {
    const hasBeenTriggered = sessionKey
      ? sessionStorage.getItem(sessionKey)
      : triggered.current;

    if (!hasBeenTriggered) {
      fn();
      triggered.current = true;

      if (sessionKey) {
        sessionStorage.setItem(sessionKey, "true");
      }
    }
  }, [fn, sessionKey]);

  return null;
};

export default useRunOnce;

进入全屏模式 退出全屏模式

[React hook useRunOnce Forest Gump meme](https://res.cloudinary.com/practicaldev/image/fetch/s--jcaHXCoB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:// www.perssondennis.com/images/articles/react-hook-use-run-once/react-hook-run-once-gump-meme.webp)

_ Forest Gump 从未听说过分段错误 _

在安装上运行一次

如果您想在组件安装后运行函数,只需将回调函数传递给参数对象的 fn 属性。回调只会触发一次。除非组件正在卸载并再次安装,否则在这种情况下,它将再次触发。

useRunOnce({
    fn: () => {
        console.log("Runs once on mount");
    }
});

进入全屏模式 退出全屏模式

每个会话运行一次

如果您希望每个会话只运行一次函数,则可以将 sessionKey 传递给钩子。然后钩子将使用会话存储来确保回调函数在每个会话中只运行一次。

换句话说,当传递一个 sessionKey 时,传入的函数只会在用户访问您的网站时运行一次。即使用户使用浏览器的重新加载按钮重新加载网站,回调函数也不会再次触发。

为了使回调函数再运行一次,用户需要关闭浏览器选项卡或浏览器,然后在另一个选项卡或浏览器会话中重新访问该网站。这都是根据会话存储文档

useRunOnce({
    fn: () => {
        // This will not rerun when reloading the page.
        console.log("Runs once per session");
    },
    // Session storage key ensures that the callback only runs once per session.
    sessionKey: "changeMeAndFnWillRerun"
});

进入全屏模式 退出全屏模式

注意。会话和本地存储的一个常见问题是您不能强制用户关闭他们的浏览器。虽然,在某些情况下,可能需要清除存储空间。最简单的方法是使用另一个存储密钥。因此,如果您将此钩子与 sessionKey 一起使用,并希望所有客户端重新运行该钩子,即使他们没有关闭浏览器,只需使用另一个 sessionKey 再次部署您的应用程序。

何时不使用

有时,当我认为我需要这个钩子时,我会三思而后行,然后意识到我真的不需要。下面是一些我不使用钩子的情况。

  1. 当用户首次访问您的页面时,在 Web 控制台中编写问候消息。

  2. 通过调用其中一个 init 函数来初始化第三方库。

  3. 当用户访问您的网站时发送分析数据(并在用户重新加载页面时重新发送)。

  4. 组件挂载时获取数据。

1\。当用户首次访问您的页面时,在 Web 控制台中编写问候消息

您可能不需要钩子的一个原因是,如果您不需要读取或设置组件的内部状态,则无需使用钩子/useEffect。向 Web 控制台写入问候消息与 React 组件或其生命周期无关,您可以在纯 JavaScript 中执行此操作,并且没有理由在 React 组件中执行此操作。

2\。通过调用其中一个初始化函数来初始化第三方库

初始化第三方库时不使用此钩子的原因与向 Web 控制台写入消息时相同。初始化第三方库可能包括将插件注册到日期库、在 i18n 库中配置语言等。

这样的逻辑很少依赖于 React 组件中的数据,因此应该在组件之外进行初始化。只需将代码放在 React 组件正上方的文件中,它将运行一次且仅一次,这就是ES6 模块的设计方式。请参阅 Reacts 文档](https://beta.reactjs.org/learn/synchronizing-with-effects#not-an-effect-initializing-the-application)中何时[不使用 useEffect 的示例。

3\。当用户访问您的网站时发送分析数据(并在用户重新加载页面时重新发送)

您也会在用例中找到这一点。这实际上取决于您要测量的内容。当用户使用 Web 浏览器的重新加载按钮重新加载页面时,您想重新发送分析数据吗?

在这种情况下,如果您不需要读取或设置组件的内部状态,则可以如上所述在 React 组件之外获取数据。另一方面,如果您不想在重新加载页面时重新获取数据,则可以使用 useRunOnce 挂钩并为其提供 sessionKey

4\。组件挂载时获取数据

如果您不想在代码中引入大量错误,这一点非常重要。在 React 18Strict Mode中,在开发模式下挂载组件时,useEffects 将运行两次。在未来的版本中,有时也会在生产中发生。

出于这个原因,你应该小心在 useEffects 中发送网络请求。这个钩子包含一个 useEffect 并且没有以最佳实践方式处理它,因为它不包括 useEffects 依赖项列表中的所有实际依赖项。

您应该经常避免在 useEffects 中发送网络请求。 POST、PUT、PATCH 或 DELETE 类型的网络请求几乎不应该放在 useEffects 中,它们通常是作为用户操作的直接结果触发的,因此应该由 onClick 处理程序触发,而不是在 useEffect 中。

在 useEffects 中获取数据可能没问题,但是这样做时,您必须确保处理接收到两次或三次数据的情况。换句话说,你的回调函数必须是幂等的。你最好使用像useSWR这样的钩子来处理缓存和请求重复数据删除。 React 在他们的文档](https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development)中有[记录了如何处理这样的案例,请务必阅读它,您最终需要学习它。

用例

什么时候想使用这个钩子?以下是一些示例用例。

  1. 在用户访问您的网站时获取数据(每个会话一次)。

  2. 组件挂载时发送分析数据。

  3. 当用户访问您的网站时发送分析数据(每个会话一次)。

  4. 运行应该在客户端运行一次而不是在服务器端运行的代码。

  5. 计算用户访问您网站的次数。

1\。当用户访问您的网站时获取数据(每个会话一次)

首先,如果您还没有阅读过关于在组件挂载](#4-fetch-data-when-a-component-mount)时不使用此挂钩来[获取数据的信息,请先执行此操作。但是,如果您确实有理由在每个会话中仅获取一次数据,则可以使用此挂钩。然后将它与传入的 sessionKey 属性一起使用。

2\。安装组件时发送分析数据

这可能是最常见的用例。 React 18](https://beta.reactjs.org/learn/synchronizing-with-effects#sending-analytics)的[文档介绍了如何在严格模式下处理分析数据。他们提到的是,让它在开发模式下发送两次是个好主意。

无论如何,他们展示的是一个简单的案例。您可能不够幸运,您的分析请求仅依赖于单个 url 变量。它可能取决于更多变量,您可能不想发送分析请求 30 次。

您可以使用类似于此挂钩包含的代码在您的代码中轻松解决该问题,或者您可以使用此挂钩。

3\。当用户访问您的网站时发送分析数据(每个会话一次)

由于此挂钩包含一个包含 sessionKey 的选项,因此您还可以在每个浏览器会话中发送一次分析数据。这允许您仅发送一次分析请求,即使用户将浏览器选项卡保持打开数天并偶尔重新加载一次。

4\。运行应该在客户端运行一次而不是在服务器端运行的代码

React 支持服务器端渲染 (SSR),并且存在多个基于 React 构建的支持 SSR 甚至静态站点生成 (SSG) 的框架,其中之一就是 Next.js。

在服务器端渲染 React 时,全局 windowdocument 对象不可用。尝试访问服务器上的这些对象之一会引发错误。因此,不可能遵循Reacts 关于如何检测应用程序何时初始化的建议。因此,这个钩子在处理运行在服务器端的框架时非常有用,因为这个钩子只会触发客户端的回调函数。

5\。计算用户访问您网站的次数

为什么不计算用户访问量?有时它可能很有用。在这种情况下,你可以指望这个钩子。

[React hook useRunOnce meme](https://res.cloudinary.com/practicaldev/image/fetch/s--ep05vCNN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www. perssondennis.com/images/articles/react-hook-use-run-once/react-hook-run-once-meme.webp)

修复错误最简单的方法是删除代码

示例

下面的代码说明了如何在组件挂载时使用 useRunOnce 挂钩发送分析数据。为了演示,它还在组件中设置内部状态并呈现文本。

import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'

const MyComponent = () => {
  const [analyticsHasBeenSent, setAnalyticsHasBeenSent] = useState(falsse)

  useRunOnce({
    fn: () => {
      sendAnalytics()
      setAnalyticsHasBeenSent(true)
    }
  });

  return <>{analyticsHasBeenSent ? 'Analytics has been sent' : 'Analytics has not been sent'}</>
}

export default MyComponent

进入全屏模式 退出全屏模式

在下面的示例中,我们改为登录到已发送分析的本地存储。这样,您可能不需要使用此钩子。原因是回调函数中的任何内容都不依赖于组件的内部状态。回调中的代码是纯 JavaScript,可以从 React 组件中提取出来。

import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'

const MyComponent = () => {

  useRunOnce({
    fn: () => {
      sendAnalytics()
      localStorage.setItem('analytics-has-been-sent', 'true')
    }
  });

  return <>MyComponent</>
}

export default MyComponent

进入全屏模式 退出全屏模式

如果我们移除钩子并取出获取数据并将其存储在本地存储中的代码,这就是上面代码的样子。

import React from 'react'
import fetchData from 'services/fetchData'

sendAnalytics()
localStorage.setItem('analytics-has-been-sent', 'true')

const MyComponent = () => {
  return <>MyComponent</>
}

export default MyComponent

进入全屏模式 退出全屏模式

如果我们不想在网站重新加载时重新发送分析,我们可以使用钩子来确保每个浏览器会话只发送一次数据,然后看起来像这样。

import React from 'react'
import useRunOnce from 'hooks/useRunOnce'
import fetchData from 'services/fetchData'

const MyComponent = () => {

  useRunOnce({
    fn: () => {
      sendAnalytics()
      localStorage.setItem('analytics-has-been-sent', 'true')
    },
    sessionKey: "anyStringHere"
  });

  return <>MyComponent</>
}

export default MyComponent

进入全屏模式 退出全屏模式

总结

useRunOnce 是一个可以用于两个用例的钩子。

  1. 当您想在每次安装或重新安装组件时运行一些代码时。

  2. 当您想在每个浏览器会话中运行一次代码时。

由于钩子包装了一个 useEffect,因此在函数挂载时运行代码可以推断出 React 18 严格模式下的副作用。阅读React 的文档,了解如何处理。

该挂钩使用会话存储在每个浏览器会话中运行一次代码。因此,一旦启动新会话,挂钩就会运行其代码,有关详细信息,请参阅会话存储文档或通读本文。

[

perssondennis 图像

](/persondennis)

丹尼斯佩尔森关注

我以前是一名教师,撰写有关软件开发及其相关内容的文章。我的志向是为全世界的人们提供免费的教育和幽默的阅读。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐