Reactみたいなフレームワークを作る(その1)

このような記事があることをしりました。ぶっちゃけ自分の記事よりよっぽどしっかりしてますので、そっちをみた方がよいと思います。

Reactみたいなフレームワーク

tsxを使って、こんな感じで書けることを目指します。要は、Reactみたいに書けて動けばよいのです。

const App = () => {
    return (
      <div>
        <MyComponent value="value" onOk={() => console.log("hello world.")}>
      </div>
    );
}

const MyComponent = (props) => {
  return (
    <div>
      <input value={props.value} />
      <button onclick={() => props.onOk()}>complete</button>
    </div>
  )
}

出来上がったものがこちらです。

tsxを吐くためのjsxFactoryを実装する

まずは、tsx(jsx)が変換されたあとのコードがどのようになるか知っておく必要があります。

const App = (props) => {
  return (
    <div>
      Hello world
      <button class="btn" onclick={() => console.log("click")}>click</button>
      {props.parameter}
    </div>
  );
}

render(App);

上記のようなコードは以下のような形に変換されます。 なお、jsxFactoryとしてhが指定されているものとします。

const App = () => {
  return (
    h("div", {}, 
      "Hello world",
      h("button", { class: "btn", onclick: () => console.log("click") }, "click"),
      props.parameter
    )
  );
}

render(App);

jsxFactoryとして指定された関数 h に対して、divやbuttonが第一引数に、classやonclickなどのアトリビュートがオブジェクトとして第二引数に、第三引数以降に子要素が挿入されるようになっています。

ふーむ、なるほど。これを画面に描画するのはそんなに難しくなさそうですね。 では、まずは静的要素のみを描画できるようにしてみましょう。

静的要素のみを描画する

第一引数がタグ名、第二引数にアトリビュート、第三引数以降をchildrenとしてまとめて配列で受け取れるようにして、描画できるようにしてみます。

まずは、h関数を実装します。

export function h(tagName, attributes, ...children) {
  return {tagName, attributes, children};
}

もらったものをそのままオブジェクトに変換してるだけですね。 これによって、上のtsxをもとに以下のようなオブジェクトが構築されます。

{
  tagName: "div",
  attributes: {},
  children: [
    "Hello world",
    {
      tagName: "button", 
      attributes: { class: "btn", onclick: () => console.log("click") },
      children: [ "click" ]
    },
    props.parameter
  ]
}

これをVirtualDomと呼びます。仮想的なDOM構造を模したオブジェクトだと思っていただければよいです。 続きまして、それを実際に画面上に描画するrender関数を実装してみましょう。

function createElement(vdom) {
  if (typeof vdom === "string") {
    return vdom;
  }

  const el = document.createElement(vdom.tagName);
  Object.keys(vdom.attributes || {}).forEach(key =>
    el.setAttribute(key, vdom.attributes[key])
  );
  vdom.children.forEach(child => {
    const childEl = createElement(child);
    el.append(childEl);
  });
  return el;
}
export function render(component) {
  const virtualDom = component();
  const element = createElement(virtualDom);
  document.body.append(element);
}

VirtualDomを受け取り、それを元に HTMLElementを構築し、それをdocument.bodyにappendしています。

これをコピペしたのが以下のものになります。 ボタンをクリックした時にログが出ないこと以外は想定通りなのではないでしょうか。

codesandbox.io

それでは、今回は以上とします。 次は、ボタンをクリックした時にコンソールにログを出すところをやっていきましょう。

よろしくお願いいたします。