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

Zan*_*aes 2 reactjs react-hooks

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

例如:

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

  log.info("Hello!")
 

  [...]
Run Code Online (Sandbox Code Playgroud)

应产生 [Login] Hello!

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

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

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

----

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

cub*_*brr 5

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

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

const Login: React.FC<Props> = () => {
  const log = useLogger('Login')
  // ...
}
Run Code Online (Sandbox Code Playgroud)

您也可以通过.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';
Run Code Online (Sandbox Code Playgroud)

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

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

const Login: React.FC<Props> = () => {
  const log = useLogger(nameof(Login))
  // ...
}
Run Code Online (Sandbox Code Playgroud)

这里的好处是该名称将在自动重命名后继续存在。这需要一些 bundler 或 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()
  // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

const Login: React.FC<Props> = () => {
  log.info('hello')
}
Run Code Online (Sandbox Code Playgroud)

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

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';
Run Code Online (Sandbox Code Playgroud)

进而

const log = logManager.getLogger(__filename)
Run Code Online (Sandbox Code Playgroud)

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

虽然,这可能需要您创建一个 typedef,例如globals.d.ts在其中声明__filenameTypescript的全局变量:

declare global {
  __filename: string;
}
Run Code Online (Sandbox Code Playgroud)

注意:这会不会,如果您的构建目标工作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()
   // ...
}
Run Code Online (Sandbox Code Playgroud)

但是,该属性已被弃用,因此您迟早必须对其进行清理,因此请勿在生产代码中这样