この記事では 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 を使えば以下のように書くことができます。
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 コンポーネントと変わりないので、props
や data
や mounted
などおなじみのプロパティを使ってコンポーネントを作成すれば 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 で記述すると分かりやすくなるかもしれませんね。