うしろのこの本ください

なんでもかきます

NuxtとComposition APIとtsxで素振り

した

github.com

setupとtsxを紐づけるため別途プラグインが必要だが、普通にかける。

以下の流れで環境を作れる。

プロジェクト生成

npx create-nuxt-app

必要なモジュールのインストール

yarn add @nuxt/typescript-runtime @vue/composition-api
yarn add -D @nuxt/typescript-build babel-preset-vca-jsx

package.jsonを修正

  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start",
    "generate": "nuxt-ts generate"
  },

composition apiプラグインに登録

滅多にないだろうけど.tsにしとけばtypoが減る。

import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Vue.use(VueCompositionApi)

nuxt.config.jsを修正(ついでにts化)

拡張子を.tsに変更

import { Configuration } from '@nuxt/types'

const config: Configuration = {
  buildModules: ['@nuxt/typescript-build'],

  ~省略~

  /*
  ** Plugins to load before mounting the App
  */
  plugins: ['@/plugins/composition-api'],

  ~省略~

  build: {
    babel: {
      presets({ isServer }) {
        return [
          [require.resolve('babel-preset-vca-jsx')],
          [
            require.resolve('@nuxt/babel-preset-app'),
            {
              targets: isServer ? { node: 'current' } : { ie: '9' },
            },
          ],
        ]
      },
    },
  },
}

export default config

tsconfig.jsonをルートに作成

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "preserve", // これないと動きません
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

ルートにshimsを置いてtsx用の型を拡張

shims-tsx.d.ts

import Vue, { VNode } from 'vue'
import { ComponentRenderProxy } from '@vue/composition-api'

declare global {
  namespace JSX {
    interface Element extends VNode {}
    interface ElementClass extends ComponentRenderProxy {}
    interface ElementAttributesProperty {
      $props: any;
    }
    interface IntrinsicElements {
      [elem: string]: any;
    }
  }
}

あとはお好みでtypescript-eslintを入れたりする。今回は省略。

書き方

SFCではなくtsxファイルで書く。exportするオブジェクトは createComponent でラップすると型推論が効くようになる。

propsは PropType に型を渡してアサーションすることで型付けする。setup()の第一引数内でも型推論してくれるようになるためpropsが増えまくってもtypoとかはなさそう。第二引数にコンテキストが入っていてemitとか使える。tsxはまあtsxって感じ。

import { createComponent, PropType, reactive } from '@vue/composition-api'

interface IncrementalObjProps {
  buttonText: string
  value: number
}

export default createComponent({
  props: {
    incrementalObj: {
      type: Object as PropType<IncrementalObjProps>,
      required: true
    },
  setup({ incrementalObj },{ emit }) {
    const r = reactive({count: incrementalObj.value})
    return () => (
      <div>
        <div>{r.count}</div>
        <button onClick={() => {
            r.count++
            emit('countUp', { count:r.count } )
          } } >{incrementalObj.buttonText}</button>
      </div>
    )
  }
})

読み込み側では普通にimportしてコンポーネントとして利用できる。SFCから読み込むことも可能。

Composition APIの出来が良くてReactじゃなくVueで書きたいけどテンプレートは嫌いなんだぜって人は良さそう。

余談

サンプルのGanahaBirthday.tsxは微妙に動かないんだけどいまいち原因がわからない。setupが1度しか評価されないのでpropsで渡ってきても変更を追えてない気がする。compositionはスマートにかけてとても良いんだけど今までのVueの直感とは微妙に違う点でハマりそうな気がしてる。

それからComposition APIでやるならTSは必須だと思っていて、Option APIでは必ず値の出所がわかる(methods,computedなど)ようになっていたけどそれがsetup()に埋もれてしまい、どこから出てきた値なのかがコードで追い辛い。型情報があればマシになるだろうという考え。