ryokatsu.dev

Vue3+Vite環境でTSXを雑に書いてみた。


雑に書いたコードは以下になります。

https://github.com/ryokatsuse/vue3-tsx-sample-with-vite

TSXのサポート

Vue3からTypeScript JSXがサポートされています。Vue2時代ではvue-tsx-supportbabelのプラグインでtsxが使えましたが、Vue3からネイティブでサポートされているのでプラグイン拡張がなくても使えます。

Vue.jsにおけるjsxk記法のRFC

書いてみる

サクッと書きたいだけだったのでVite環境を使用しました。ViteはVue.jsはもちろんですが、ReactはPreactも高速でバンドルできるツールです。

とりあえず以下のコマンドでVue3の環境が手に入り、ローカルホストが起動します。

 $ yarn create @vitejs/app my-vue-app --template vue
 $ yarn
 $ yarn dev

App.vueをApp.tsxにリネームして以下のように書いて保存してみます。

import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export const App = defineComponent({
  render() {
    return (
      <>
        <HelloWorld />
      </>
    )
  }
})

しかしこのままだとVite環境でコンパイルできませんでした。公式サイトによるとViteに限っては、プラグインが必要らしいので別でインストールする必要があります。

$ yarn add @vitejs/plugin-vue-jsx

公式通りvite.config.tsを以下のように設定してコンパイルできました。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      // options are passed on to @vue/babel-plugin-jsx
    })
  ],
})

今回は超雑なTODOアプリを作るので、先程のApp.tsxを以下のように書き換えます。

import { defineComponent, reactive } from 'vue';
import TodoInput from './TodoInput'; // 子コンポーネントもtsxファイルなので拡張子なしimport
import { TodoList } from './TodoList'; // nameを書かずにexport const TodoList ...みたいな感じでimportできる

type TodoDataList = {
  title: string;
  isCompleted: boolean;
}

export default defineComponent({
  name: 'TodoApp',
  setup() { // Composition APIだとrender()関数が不要
    const todoDataList = reactive([
      { title: '歯を磨く', isCompleted: false},
      { title: '掃除をする', isCompleted: false},
      { title: '買い物リストを確認する', isCompleted: false},
    ]);

    const handlePushTodo = (data: TodoDataList) => {
      todoDataList.push(data);
      console.table(todoDataList)
    }
    return () => ( // SFCでいうtemplate部分
      <div class="wrapper"> // classNameと書かなくてもよい。
        <TodoInput submitTodo={handlePushTodo} /> //{{  }}の記法は書けない
        <TodoList todos={todoDataList} />
      </div>
    );
  },
});

コメントをつけましたが、SFCと比べて見通しがだいぶ変わります。またJSXなのでstyleタグが記述できません。

cssの取り扱いは以下のいずれかになりそうです。

  • scssファイルを別ファイルにしてimportする(ただしScoped CSSではないので注意)
  • styled-componentsを使う。(Vueだとvue-styled-components)
  • CSSフレームワークやTailwindCSSなどを使ってCSSを一切書かないようにする

この時点で感じたメリットとしては、SFCと違って型チェックが全域に広がったという点でした。特にtemplateタグでは、型のチェックが効かなくてしょんぼりすることがあったのですが、tsxにすることでJavaScriptの世界になるのでエディタ上でエラーを確認できます。(Optional Chainingも当然使えます!!)

また、JSX記法だとJavaScriptの予約語は使用できないのですが、VueのTSX記法では使用可能なので、classforなどが普通に記述できます。

todoリストを繰り返し表示するコンポーネントを見てみましょう。


import { defineComponent } from "vue";
import Todo from "./Todo";

export default defineComponent({
  name: 'TodoList',
  props: {
    todos: {
      type: Array,
      required: true
    }
  },
  setup(props) {
    return () =>
      <div>{props.todos.map((todo: any, i) => <Todo todo={todo} key={i} />)}</div>
  }
})

SFCではVue独自記法のv-forv-if などを使用できますが、JSXなので使えません。map関数などを使って書いていきます。また今回は型を誤魔化していますが、props自体に型も付けれます。

まとめ

  • Vueっぽさがなくなるがファイルが肥大化しにくく小さい単位でコンポーネントを作れる
  • CSSフレームワークなどを使っている場合は変にコンポーネントに混ぜることもないので、安全に使用できる。
  • 逆にCSSをしっかり書きたい場合は厳密なルール化が必要。
  • 型チェックがいい感じ。
  • default exportsしかSFCの場合はできないがTSXだと名前空間のexportができる

参照

potato4dさんの以下のスライドとブログ記事が分かりやすいので参照としてリンクします。

https://speakerdeck.com/potato4d/vue-dot-js-with-tsx-from-vue-2-dot-x-to-vue-3-number-v-tokyo11 https://d.potato4d.me/entry/20200830-tsx-in-vue/