Vue3 + TSX を触ってみての所感

4 min read読了の目安(約3900字

初めに

なぜこれをやろうと思ったのか。

これに尽きる。
そのタイミングでLINEさんのこちらのPodcastがTLに流れてきたのでチャレンジしてみた。

https://uit-inside.linecorp.com/episode/82

本記事ではTodoアプリを題材にVue3におけるTSXの私個人の記述方法や所感などを書いていきます。

https://v3.vuejs.org/api/composition-api.html#setup

公式サイトを見てもReactみたいな書き方ではなく、しっくりこなかったので自分なりに書いてみました。
基本的な書き方は結果のコードを参考にしていただければと。

結果はこちら。

https://github.com/shippokun/todo-vue3

基本的なアーキテクチャについてはこちらの記事を参考に作成しています。いつもお世話になっております。

https://dev.to/puku0x/angular-react-2h4j

本題

記述方法

Composition-API を使い、 setupRenderFunction を返します。

import { defineComponent, ref } from 'vue';
import { TestComponent } from './test.component.tsx'

export default defineComponent({
  components: { TestComponent },
  setup() {
    const test = ref('test');
    const changeFuga = () => {
      test.value = 'fuga'
    };
    
    return () => (
      <div>
        <TestComponent test={test.value} changeFuga={changeFuga} />
      </div>
    );
  }
})

ここで型の恩恵を最大限受けるために、 emits は全て props で代替しました。
また props で全て代替することで、親コンポーネントから子コンポーネントを呼び出すときに型のチェックが行やすくなりました。
props のリレーも特に問題なくできるレベルになっていると思います。

import { defineComponent, PropType } from 'vue';
import { Todo, TodoUpdateDto } from '@/models'

interface Props {
  todo: Todo | null;
  onUpdate: (id: string, todo: TodoUpdateDto) => void;
};

export const TodoEditComponent = defineComponent({
  name: 'TodoEditComponent',
  props: {
    todo: {
      type: Object as PropType<Props['todo'],
      default: null,
    },
    onUpdate: {
      type: Function as PropType<Props['onUpdate'],
      required: true,
    },
  },
  setup(props: Props) {
    // propsに型がついているので、型の補完が効く
    ...
  }
});

注意する点としては今まで template で使用する際に暗黙的に展開されていた

  • props
  • refのvalue

は都度記述する必要があったけど、型による恩恵があるのでそこまで気になるようなことではなかったです。

引っかかった箇所

SFCtemplate で書いていたものを全てTSXへ移行して行った際にいくつか問題が出てきたので、解決したものと解決していないもの分けて紹介します。

解決したもの

  • vue-router<router-view><router-link>

こちらはそれぞれ vue-router が提供する RouterViewRouterLink で代替することができました。

import { RouterLink, RouterView } from 'vue-router';

export default defineComponent({
  setup() {
    return () => (
      <div>
        <RouterView />
	<RouterLink to={{ path: '/todos', query: { completed: "true" } }} >
	  <a>TodoList</a>
	</RouterLink>
      </div>
    );
  }
})
  • クラスとスタイルのバインディング

https://jp.vuejs.org/v2/guide/class-and-style.html
特定のクラスだけ特定の条件下でアクティブにしたいときなどに使用していましたが、こちらは三項演算子を用いて回避しました。
  • v-model

リアクティブにレンダリングしたいときにはどうしても必要だったので、 jsx-nextbabel に組み込むことでそのまま v-model を使えるようにしました。

https://github.com/vuejs/jsx-next

解決しなかったもの

  • style scoped

小さいプロジェクトだとここはあまり気になるポイントではないけれど、選択肢としてないのはつらいですよね。
どうやってstyleの対象範囲を限定できるのか方法が見つからなかったため、断念しました。

あとは特に支障はなく書けました。

まとめ

やはり型による恩恵を最大限受けれるのは大きなメリットと言えました。
また template では[1]できなかった optional chainingnullish coalescing などが使えるもよかった点でした。

Vue2で Composition-API を使うための @vue/composition-api では setup による render function はサポートしておらず[2]、上記で述べている vue-router についてもnextである v4 からのサポートなのであいにくVue2での導入は厳しいと言わざるおえないです。

「Vueを導入しても型がちょっと、、、」と言われがちですが[3]、一定の設計を元に導入を行えば、Vue3では無理なくTypescriptで書くことは可能かと思います。
今後Vue3を検討している人に対して何か参考になれば幸いです。

型はいいぞ。

雑談

Vue3になってからテストの書き方が結構変わっているので、マイグレーションがかなりつらい印象。
createLocalVue はどこにいったんだ。
storeはいいけど、routerはどうすれば。

一応 createRouter した router 自体をテストで読み込んでみて、新しく増えた global: plugins に突っ込むことでスナップショットまでは取れたけど、これでいいのか感が、、、。
詳しい人いたら教えていただけるととても助かります。

https://github.com/shippokun/todo-vue3/blob/main/src/pages/todo/todo-list/components/todo-list/todo-list.component.spec.tsx

あとLINEさん[4]

Vue test utilsが .tsx の読み込み時に型を補完してくれない

とあったけど、特に問題なく補完できたのでそもそもの書き方が違うのだろうか。今回書いたコードにテストをきちんと全部書いていないので考慮が足りない部分があるかもしれない。

脚注
  1. 私が知らないだけで使う方法はあるのかもしれませんが ↩︎

  2. https://github.com/vuejs/composition-api ↩︎

  3. 私の観測範囲の話しです ↩︎

  4. この記事から抜粋 ↩︎