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