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.ConcurrentModeReactIs.ContextConsumerReactIs.ContextProviderReactIs.ElementReactIs.ForwardRefReactIs.FragmentReactIs.MemoReactIs.LazyReactIs.PortalReactIs.ProfilerReactIs.StrictModeReactIs.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;
}