如何从React钩子访问功能组件的名称?

时间:2020-10-12 18:32:49

标签: reactjs react-hooks

我正在尝试编写一个自定义的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的东西。但是,React钩子没有设置this,而且我似乎找不到有关获得对功能组件上下文的引用的文档。

-

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

1 个答案:

答案 0 :(得分:3)

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

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

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

您还可以通过.displayName.name获得名称。请注意,.name指的是Function.name,如果您使用的是webpack,则可能会在生产版本中将其缩小,因此您将以“ 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这样的名称, nameof运算符like in C#

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()
  // ...
}

对此的扩展,如果记录器本身实际上不需要是一个钩子(不需要使用其他钩子或不需要道具等),只需直接在顶层为模块创建一个记录器:< / p>

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 context /home/user/project的文件中,__filename变量将解析为src/components/Login.ts

尽管如此,这可能需要您创建一个typedef,例如globals.d.ts__filename中声明Typescript的全局变量:

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()
   // ...
}

但是,该属性已被弃用,因此您迟早必须对其进行清理,因此不要在生产代码中执行此操作