首页 > 解决方案 > 如何从 React 挂钩访问功能组件的名称?

问题描述

我正在尝试编写一个自定义的 React 钩子,useLogging我想在其中根据正在执行日志记录的组件的名称对日志消息进行上下文化。

例如:

const Login: React.FunctionComponent<IProps> = (props) => {
  log = useLogging();

  log.info("Hello!")
 

  [...]

应该生产[Login] Hello!

然后,我的自定义钩子需要名称Login

export const useLogger = () => {
  // "this" is undefined
  const loggerName = ??????
  return logManager.getLogger(loggerName);
};

在类的上下文中,我正在寻找类似this.constructor.displayName. 但是,没有this设置 React 钩子,我似乎找不到有关获取对功能组件上下文的引用的文档。

——

编辑:我宁愿不传递任何参数,也不希望添加一堆样板。我的目标是该useLogging()函数将在组件重构中幸存下来,而不是依赖开发人员提供“正确”的名称。

标签: reactjsreact-hooks

解决方案


我可以想到您可以使用的其他几种方法。Drew 在评论中建议的第一个也是最简单的一个,就是简单地将日志记录名称作为参数传递:

const useLogger = (name: string) => {
  return logManager.getLogger(name)
}

const Login: React.FC<Props> = () => {
  const log = useLogger('Login')
  // ...
}

您也可以通过.displayName或获取名称.name。请注意,如果您使用.namewebpack Function.name,它可能会在生产构建中被缩小,因此您最终会得到像“t”或“s”等名称。如果您需要与组件中相同的名称,您可以分配displayName并让钩子处理它:

const useLogger = (component: React.ComponentType<any>) => {
  const name = useLogger(component.displayName || component.name);
  return logManager.getLogger(name);
}

const Login: React.FC<Props> = () => {
  const log = useLogger(Login)
}
Login.displayName = 'Login';

如果您可以将名称传递给useLogger,但不想displayName每次都设置,则可以使用类似的东西ts-nameof,旨在为您提供C# 中nameof的运算符:

const useLogger = (name: string) => {
  return logManager.getLogger(name)
}

const Login: React.FC<Props> = () => {
  const log = useLogger(nameof(Login))
  // ...
}

这里的好处是该名称将在自动重命名后继续存在。这需要一些捆绑器或 Babel 配置。我还没有测试过缩小如何影响这一点,但是您可以使用三种不同的ts-nameof(在撰写本文时):

选择与您的构建管道匹配的第一个。


或者,如果记录器不是特定于组件的,而是特定于模块的,你可以为钩子创建一个工厂,并在你的模块顶部初始化一次:

const makeUseLogger = (name: string) => () => {
  return logManager.getLogger(name)
}

// in your module

const useLogger = makeUseLogger('Module name')

const Login: React.FC<Props> = () => {
  const log = useLogger()
  // ...
}

作为对此的扩展,如果记录器本身实际上不需要成为钩子(不使用其他钩子或需要道具等),只需直接在顶层为您的模块制作一个记录器:

const log = logManager.getLogger('Module name')

const Login: React.FC<Props> = () => {
  log.info('hello')
}

此外,如果您不介意项目的目录结构泄漏到生产构建中,您可以使用 webpack 技巧:

// webpack.config.js
module.exports = {
  // ...
  node: {
    __filename: true
  }
}

接着

const log = logManager.getLogger(__filename)

在路径为/home/user/project/src/components/Login.ts且 webpack上下文为的文件中/home/user/project__filename变量将解析为src/components/Login.ts.

虽然,这可能需要您创建一个 typedef,例如在其中为 Typescriptglobals.d.ts声明全局:__filename

declare global {
  __filename: string;
}

注意如果您的构建目标是umd.


作为旁注,从技术上讲,如果您出于某种原因不想将任何参数传递给useLogging,则可以使用已弃用 Function.caller的属性,例如

function useLogging() {
  const caller = (useLogging.caller as React.ComponentType<any>);
  const name = caller.displayName || caller.name;
  console.log(name);
  return logManager.getLogger(name);
}

const Login: React.FC<Props> = () => {
   const log = useLogging()
   // ...
}

但是,该属性已被弃用,因此您迟早必须清理它,所以不要在生产代码中这样做。


推荐阅读