如何从 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的内容。然而,React钩子没有this集,而且我似乎找不到关于获得对功能组件上下文的引用的文档。

---

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

我认为还有其他几种方法可以使用。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运算符,就像C#中一样:

const useLogger = (name: string) => {
return logManager.getLogger(name)
}
const Login: React.FC<Props> = () => {
const log = useLogger(nameof(Login))
// ...
}

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

  • TypeScript编译器的编译时转换:ts的名称
  • Babel宏:ts-nameeof.macro
  • Babel插件:Babel插件的名称

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


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

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,例如globals.d.ts,在其中为Typescript:声明__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()
// ...
}

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

最新更新