React.memoしたコンポーネントのdisplayNameを取得する


Reactのデバッグ用に、コンポーネントの名前を出力していたのですが、その過程でReact.memoしたコンポーネントは特殊な扱いが必要でした。

displayNameとは

以前にべからず集でも触れましたが、コンポーネントにdisplayNameがセットしてあると、それがデバッグ時にコンポーネント名として表示されます。

そして、React公式サイトにあるコード片にもWrappedComponent.displayName || WrappedComponent.nameのようなコードがあるように、関数宣言やクラスなどでnameが設定されていれば、それで代用できます。

React.memoを使った場合

ところが、React.memoを使った場合、displayNameはセットされません(もちろん関数生成ではないので、自動的にnameが付くこともありません)。なので、displayName || nameのコードでは何も取れません。

ただ、ReactのデバッグツールではMemo(WrappedComponent)のような名前がしっかりと出ています。このような名前を取得できないか調べてみました。

react-isとは

そして、調べてみると、React.memoで生成したコンポーネントにはtypeというプロパティがあって、ここにもとのコンポーネントが来ることが判明しました。あとはメモ化コンポーネントを識別できれば、要件は片付きます。

もちろん内部データにアクセスして調べられなくもないのかもしれませんが、それをやっていると将来的に内部構造が変化したときに死にます。そこでReactチームが公式に用意している手法として、react-isというライブラリがあります(GitHub)。

以下のようなメソッド・定数が用意されています。

  • ReactIs.isValidElementType(arg)argがReactコンポーネントにできるもの(タグ名の文字列・関数コンポーネント・クラスコンポーネントなど)かを判定する
  • ReactIs.typeOf(arg)argの種類を、以下の定数のどれかで返す
    • ReactIs.ConcurrentMode
    • ReactIs.ContextConsumer
    • ReactIs.ContextProvider
    • ReactIs.Element
    • ReactIs.ForwardRef
    • ReactIs.Fragment
    • ReactIs.Memo
    • ReactIs.Lazy
    • ReactIs.Portal
    • ReactIs.Profiler
    • ReactIs.StrictMode
    • ReactIs.Suspense
  • ReactIs.is***(上の定数に対応したメソッドがあります)…それぞれの種類かを判定する

なお、どういうわけかimport ReactIs from 'react-is'の形では読み込めず、import {isMemo} from 'react-is'と単品で呼ぶか、全部読み込む場合はimport * as ReactIs from 'react-is'とする必要があります。

実際に書いてみた

素材が揃ったので、あとはコードに起こすだけです。

import {isMemo} from 'react-is';

function getDisplayName(component) {
  const {name, displayName} = component;
  // displayNameがついていればそれを採用
  if(displayName) return displayName;
  // メモ化コンポーネントの場合
  if(isMemo(component)) return `Memo(${getDisplayName(component.type)})`;
  // あとはnameなどをチェック
  return name || null;
}