2018.10.22

JSXを使ってVueコンポーネントを記述する


この記事では JSX を使って Vue コンポーネントを記述する方法を紹介します。

JSX といえば React というイメージがあるかもしれませんが、Vue でも JSX でコンポーネントを書くことができます。専用の Babel プラグイン があって特に複雑な設定も必要ありません。

準備

パッケージをインストールする

まずは必要なパッケージをインストールします。

$ yarn add -D babel-plugin-syntax-jsx \
  babel-plugin-transform-vue-jsx \
  babel-helper-vue-jsx-merge-props \
  babel-preset-env

.babelrc

.babelrc に以下の設定を追記します。

{
  "presets": ["env"],
  "plugins": ["transform-vue-jsx"]
}

Vue CLI などで作成した既存のアプリケーションでは "presets" はすでに設定されているでしょう。その場合は "plugins" のみの追記で構いません。

コンポーネントを書く

そもそも Vue では <template> を使えばコンポーネントを書けるのになぜ JSX を使うのでしょうか?たとえば Bootstrap のボタンのように、<a> でも <button> でも同じようにボタンの見た目になるコンポーネントを作りたいとしましょう。

<MyButton href="https://jp.vuejs.org/">
  Primary link.
</MyButton>

<MyButton @click="onClick">
  This is a button.
</MyButton>

href 属性を渡すかどうかで <a><button> を出し分けようとしても、以下のようには書くことができません。<template> 直下のルート要素はただ一つでなくてはいけないからです。

こういう風には書けない
<template>
  <a v-if=""><!-- Content --></a>
  <button v-else><!-- Content --></button>
</template>

<div> などで囲めば動作はしますが、ボタンにいちいち <div> がくっついていたら HTML として扱いづらいです。JSX を使えば以下のように書くことができます。

MyButton.js
export default {
  render(h) {
    if (this.$attrs.href) {
      return <a class="my-button">{this.$slots.default}</a>;
    }

    return (
      <button class="my-button" onClick={this.handleClick}>
        {this.$slots.default}
      </button>
    );
  },
  methods: {
    handleClick() {
      this.$emit("click");
    }
  }
};

React と同じように、Vue でも描画関数は render メソッドです。つまり render メソッドから返却された JSX が コンポーネントとなります。render メソッドの第一引数は要素を生成するために必要な createElement 関数です。JSX を使う場合には明示的には用いられませんが、記述されている必要があります。習慣的に h という仮引数名を使用することが多いそうです。

$attrs にはコンポーネントに渡した HTML 属性が、$slots にはスロットが入っています。そのほかにもコンポーネントのメソッド内で this から参照できる情報はいくつかありますので API ドキュメント を参照してください。href 属性自体は <a> に渡していませんが、Vue では無効化しない限りコンポーネントに渡された属性はルート要素に引き渡される(継承される)ので、明示的に記載する必要はありません、target なども <MyButton> に書けば意図通り動作します。

また、methods プロパティを記述していますが、マークアップが render メソッドから返される JSX で記述されること以外は普通の Vue コンポーネントと変わりないので、propsdatamounted などおなじみのプロパティを使ってコンポーネントを作成すれば OK です。

この例のように、要素の返却にあたってプログラムでの制御が必要な場合は、JSX を使う選択をすると便利かもしれません。

Vue 風味の JSX

Vue で JSX を用いる場合は、属性部分の表現が <template> を使う場合とも React での JSX とも少し異なるので、基本的な記述方法を紹介します。

HTML 属性

まず、HTML 属性はそのまま書けます。オリジナルのコンポーネントプロパティも同様です。

render: (h) => <div id="foo" />
render: (h) => <Todo tasks={list} />

クラスとスタイルについては属性値が特殊な表現になります。

class

クラス属性はオブジェクトまたは配列で記述します。

render: (h) => <div class={{ foo: true, bar: false }} />
render: (h) => <div class={['foo', 'baz']} />

className と呼ばなくていいのは React と異なる点ですね。

style

インラインのスタイルはオブジェクトで表現します。

render: (h) => <div style={{ color: 'red', fontSize: '14px' }} />

イベントハンドラ

イベントハンドラは onイベント名 という名前になります。

render: (h) => <div onClick={this.clickHandler} />

innerHTML

コンテンツをエスケープせずに HTML として扱いたい場合は domPropsInnerHTML を用います。

render: (h) => <div domPropsInnerHTML="bar" />

そのほかの機能に関してはプラグインのドキュメントを参照してください。

Nuxt.js

ちなみに Nuxt.js では追加の設定なしに JSX を使用することができます

ここではオマケとして少し分かりづらかった点を紹介しましょう。assets ディレクトリで画像を管理している場合、以下のように require することが必要です。

<img src={require('~/assets/images/foo.png')} />

テンプレートを用いていれば暗黙的に処理されていたところを、JSX では明示的に記載しなければいけないのですね。


以上、Vue で JSX を使用する方法を紹介しました。<template> 内の制御構文が複雑になってきたときには JSX で記述すると分かりやすくなるかもしれませんね。