2 votes

Comment accéder au nom du composant fonctionnel à partir d'un hook React ?

J'essaie d'écrire un hook React personnalisé, useLogging Je voudrais contextualiser le message d'enregistrement en fonction du nom du composant qui effectue l'enregistrement.

Par exemple :

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

  log.info("Hello!")

  [...]

Devrait produire [Login] Hello!

Mon crochet personnalisé, alors, a besoin du nom Login :

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

Dans le contexte d'une classe, ce que je recherche est quelque chose comme this.constructor.displayName . Cependant, un hook React n'a pas this et je ne parviens pas à trouver de documentation sur la façon d'obtenir une référence au contexte du composant fonctionnel.

--

Modifier : Je préférerais ne pas passer d'arguments et ne pas ajouter un tas d'arguments passe-partout. Mon objectif est que le useLogging() survivra au remaniement des composants et ne dépendra pas du développeur pour fournir un nom "correct".

5voto

cubrr Points 4061

Il y a quelques autres moyens auxquels je pense que vous pourriez utiliser. La première et la plus simple est celle que Drew a suggérée dans le commentaire, qui consiste à passer simplement le nom du journal comme argument :

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

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

Vous pouvez également obtenir le nom via .displayName o .name . Remarquez que .name se réfère à Function.name qui, si vous utilisez webpack, seront probablement minifiés dans une version de production, de sorte que vous vous retrouverez avec des noms comme "t" ou "s", etc. Si vous avez besoin du même nom que dans votre composant, vous pouvez assigner displayName et laisser le crochet s'en occuper :

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';

Si vous êtes d'accord pour passer un nom à useLogger mais je ne veux pas mettre displayName à chaque fois, vous pourriez utiliser quelque chose comme ts-nameof qui vise à vous donner un nameof opérateur comme en C# :

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

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

L'avantage est que le nom survivra aux renommages automatiques. Cela nécessite une certaine configuration du bundler ou de Babel. Je n'ai pas testé l'impact de la miniaturisation sur ce point, mais il existe trois versions différentes de ts-nameof (au moment de la rédaction) que vous pouvez utiliser :

Choisissez le premier qui correspond à votre pipeline de construction.


Alternativement, si le logger n'est pas spécifique aux composants mais spécifique au module vous pourriez créer une fabrique pour le crochet, et l'initialiser une fois au début de votre module :

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

// in your module

const useLogger = makeUseLogger('Module name')

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

Dans le prolongement de ceci, si le logger lui-même n'a pas besoin d'être un hook (il n'utilise pas d'autres hooks ou n'a pas besoin de props, etc.), créez simplement un logger pour votre module au niveau supérieur directement :

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

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

En outre, si la structure des répertoires de votre projet ne vous dérange pas, vous pouvez utiliser une astuce de webpack :

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

et ensuite

const log = logManager.getLogger(__filename)

Dans un fichier dont le chemin est /home/user/project/src/components/Login.ts et l'outil webpack contexte es /home/user/project le __filename sera résolue comme étant src/components/Login.ts .

Cependant, cela nécessitera probablement de créer un typedef, par exemple globals.d.ts où vous déclarez le __filename global pour Typescript :

declare global {
  __filename: string;
}

Note : ce sera no fonctionne si votre cible de construction est umd .


En passant, techniquement, si pour une raison quelconque vous ne voulez pas passer d'arguments à useLogging vous pourrait utiliser le déprécié Function.caller la propriété, par exemple

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

Cependant, cette propriété est déprécié donc tu devras nettoyer ça tôt ou tard, donc ne pas faire cela dans le code de production .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X