TypeScript と Vue(ClassComponent) と JSX な環境

なんとなく。

## 依存パッケージ

以下のものが必要なのでインストールします。

```bash
npm i vue vue-class-component

npm i -D \
  @types/vue \
  typescript \
  babel-core \
  babel-preset-env \
  babel-plugin-transform-vue-jsx \
  babel-plugin-syntax-jsx \
  babel-helper-vue-jsx-merge-props \
  webpack \
  awesome-typescript-loader
```

あと開発に便利なこんなのも入れる。

```bash
npm i -D webpack-dev-server html-webpack-plugin
```

## コンパイルの流れ

Vueでは、JSXを[`VNode`](https://github.com/vuejs/vue/blob/dev/src/core/vdom/vnode.js)というものへ変換する必要があります。この変換は、

1. **`.tsx`で書く**  `<div>hoge</div>`
2. **`.jsx`に変換**  `<div>hoge</div>`
3. **そしてBabelで`.js`に変換**  `h('div', null, ['hoge'])`

という感じでやります。

## 環境構築

### TypeScript

こんな設定にします。

```json:tsconfig.json
{
  "compilerOptions": {
    "jsx": "preserve",
    "experimentalDecorators": true,
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015", "dom"],
    "strict": true
  }
}
```

重要なのが上2つで、`jsx:preserve`を指定することで、JSX部分はそのまんまで`.jsx`に変換します。`experimentalDecorators:true`は、デコレーターを噛ますのに必要です。今回では`vue-class-component`の`@Component`ですね。

（他はだいたい天ぷら🍤）

### Babel

[`VNode`](https://github.com/vuejs/vue/blob/dev/src/core/vdom/vnode.js)へ変換するために、`babel-plugin-transform-vue-jsx`を使います。

```json:.babelrc
{
  "presets": ["env"],
  "plugins": ["transform-vue-jsx"]
}
```

### Webpack

`awesome-typescript-loader`の`options`で`useBabel:true`すると、TypeScript変換後にバベるようになります。これで上記のコンパイルの流れで変換します。

```js:webpack.config.js
module.exports = {
  entry: __dirname + '/app.tsx',
  output: {
    path: __dirname + '/dev',
    filename: 'app.js',
  },
  module: {
    rules: [
      {
        test: /\.tsx$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'awesome-typescript-loader',
            options: {
              useBabel: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin()
  ],
}
```

## コードを書く

適当に用意しました。

```ts:app.tsx
import Vue, {CreateElement, VNode} from 'vue';
import Component from 'vue-class-component';

@Component({
  props: {
    name: {
      type: String,
      required: true
    },
  }
})
class Hello extends Vue {
  name: string;

  // mounted() {}

  // handleClick = () => {}

  render(h: CreateElement): VNode {
    return (
      <div>
        Hello, {this.name} {this.$slots.default}
      </div>
    );
  }
}

@Component
class App extends Vue {
  render(h: CreateElement): VNode {
    return (
      <div>
        <Hello
          name="nju33"
        >!!!!!</Hello>
      </div>
    );
  }
}

const div = document.createElement('div');
document.body.appendChild(div);

new Vue({
  render(h: CreateElement): VNode {
    return <App />;
  }
}).$mount(div);

```

**必ず`@Component`と付けるように**します。じゃないとエラーです。

`methods`や`data`なんかで包んでいた値は、そのままプロパティ値やメソッドとして定義して使っても大丈夫で、`components`とかもスコープ上にあれば直接使えちゃいます。（Reactみたいに）
また、Reactで言う`this.props.children`は、`this.$slots.xxx`に入ってますね。


ただ、上記だと`render`の戻り値が`any`でエラーが出るみたいなのでJSX.Elementをこんな感じで型定義して`VNode`にキャストできるようにしときます。

```ts:jsx.d.ts
import Vue, {VNode} from 'vue';
declare global {
  namespace JSX {
    interface Element extends VNode {}
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [element: string]: any;
    }
  }
}

```

## 結果

上のコードを実行するとこんな感じで表示されます。

<img width="288" alt="Webpack_App_🔊.png" src="https://qiita-image-store.s3.amazonaws.com/0/84144/2cc6eb3c-7660-9e8e-417e-aad2b2c5bc88.png">

- あ、これReactだ。
- `.vue`とどっちを使うかはアナタ次第って感じじゃ。はたまたミックスか🤔