【React】ReactでHTMLカスタムタグを作る

はじめに

こんにちは、SHOJIです。

ReactでHTMLカスタムタグを実装しようと調べていたら、素晴らしいコードを提供している方がいらっしゃったので紹介させて頂きます。

React Custom Tags

https://codepen.io/BradDenver/pen/ALrXaW?css-preprocessor=less

こちら、Reactを使ってVue.js等で見られるHTMLのカスタムタグを実装したサンプルコードです。

リンク先が全てなのでこれ以上の説明は蛇足なのですが、自分の勉強も兼ねてリンク先のコードが何をしているのか説明していきたいと思います。

render関数が行っていること

カスタムタグを実現する上で一番基本となっているのがrender関数です。

// custom tags
function render (tag, Comp) {
  document.createElement(tag);

  const nodes = Array.from(document.getElementsByTagName(tag));
  nodes.map((node, i) => renderNode(tag, Comp, node, i));

  return Comp;
}

コメントに custom tags とあるように、ここでカスタムタグを登録しています。

登録というと少し語弊がありますね。実態に即した表現をすると、「カスタムタグとみなしたタグの下に、React要素を配置している」になります。

ここちょっと細かい話になるのですが、この表現の違いから意識してほしいのは処理の順番が違うということです。

事前にカスタムタグをどこかに登録しておくと、そのどこかがHTML内のカスタムタグを自動的に処理してくれるというわけではありません。render関数が呼び出されるたび、引数で指定されたタグをHTMLから見つけ出して、その場で一つ一つにReact要素を配置しています。


  const nodes = Array.from(document.getElementsByTagName(tag));
  nodes.map((node, i) => renderNode(tag, Comp, node, i));

このように、上の行で引数のtagと合致するタグをHTMLから全て取得して、下の行で取得したタグを引数に renderNode関数を呼び出しています。

renderNode関数が行っていること

続きまして、render関数から呼び出されるrenderNode関数について。

function renderNode (tag, Comp, node, i) {
  let attrs = Array.prototype.slice.call(node.attributes);
  let props = {
    key: `${ tag }-${ i }`,
  };

  attrs.map((attr) => props[attr.name] = attr.value);

  if (!!props.class) {
    props.className = props.class;
    delete props.class;
  }

  ReactDOM.render(
    <Comp { ...props }/>,
    node
  );
}

これはReact要素の配置を実際に行っている関数です。


  let attrs = Array.prototype.slice.call(node.attributes);

ここで node(置き換え対象のタグ)に設定されている全ての属性を取得しています。 属性というのは、サンプルHTMLの name="Jack" です。今回は一つですが、複数設定されていれば複数取得します。


  attrs.map((attr) => props[attr.name] = attr.value);

取得した属性を propskey-value の形で格納し、

  ReactDOM.render(
    <Comp { ...props }/>,
    node
  );
}

最後に props をReact要素の Comp に引き渡しています。 この Comp は元を辿っていくとReactクラスの Hello が設定されているので、ここで node (置き換え対象のタグ)に属性を引き継いだ Hello を設定していると分かります1


おや?と思うとしたらこちらでしょうか。

  if (!!props.class) {
    props.className = props.class;
    delete props.class;
  }

ここではタグの属性である classclassName に置き換えてます。 Reactでは classclassName と書く決まりがあるのでそれに合わせた形ですね。props に一番最初に設定した key もReactのルールに合わせたものです。

まとめ

  • render関数:引数で指定されたタグをHTMLから見つけ出して、renderNode関数を呼び出す。

  • renderNode関数:引数で指定されたDOMノードの属性を引き継いだ上でReact要素をレンダーする。

  • これらによって呼び出し元は render(カスタムタグ名, React要素) とするだけでよい。


今回ご紹介した方法は、結果を見たあとでは公式のレンダー方法を踏襲した非常に素直な方法に見えると思います。ただ、これが自分ではなかなか発想できないんですよね。

少なくとも僕は思いつかなくて、ソースを見た時に「その手があったか!」と思いつけなかったことに悔しさを感じました。

いずれはこういう方法を思いついて発信できる側になりたいですね。



  1. React要素をDOMノードにレンダーする方法はこちらをご参照ください。https://ja.reactjs.org/docs/rendering-elements.html#rendering-an-element-into-the-dom