Rails + Vue3 + TypeScriptで新しいプロジェクトを作ってみた
Vue3がリリースされたみたいなのでRailsとvue、tsで新しくプロジェクトを作てみました。
後から気づいたけど、vue/cliを使えばもvue3とTypeScriptを同時インストールできたかも?
コード: https://github.com/tOba1357/vue3_rails/tree/73e679541da8308010fde8eab0da3298b77a6f48
setup rails
rbenv local 2.7.2
Gemfile作成
source 'https://rubygems.org'
gem 'rails', '6.0.3.4'
railsプロジェクト作成
bundle install
bundle exec rails new -d postgresql --api --skip-action-mailer --skip-active-storage --skip-action-cable .
# database.ymlを編集
./bin/rails s
install vue3
参考: https://v3.vuejs.org/guide/installation.html
npm init vite-app frontend
cd frontend
nodenv local 14.15.0
npm install
defaultのportが3000でrailsと被っているのでviteのportを8080に変更とaliasの設定をします。
webpackと違って@...ができないみたいなので代わりに'/@/...'を使います。https://github.com/vitejs/vite/issues/88
frontend/vite.config.js
const path = require('path')
export default {
    port: 8080,
    alias: {
        '/@/': path.resolve(__dirname, 'src')
    }
}
npm run dev
あとは、http://localhost:8080/ にアクセスしてみて表示されればおk
setup TypeScript
参考: https://v3.vuejs.org/guide/typescript-support.html
tsconfig.json作成
// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // this enables stricter inference for data properties on `this`
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "/@/*": [
        "src/*"
      ]
    }
  }
}
install TypeScript
npm install --global @vue/cli
vue add typescript
frontend/src/App.vueを編集
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>
<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import { defineComponent } from 'vue'
export default defineComponent({
  components: {
    HelloWorld
  }
})
</script>
フロントのサーバをリスタートして http://localhost:8080/ にアクセスして、同じように表示されればおk
vueからRailsApiを叩く
ここでは、ユーザの作成して一覧取得する機能を作成します。
userモデルとcontroller作成
nameだけ持ったuserを作成します。
bin/rails g model user
migration
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.timestamps
    end
    add_index :users, :name, unique: true
  end
end
bin/rails g db:migrate
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    render json: User.all
  end
  def create
    user = User.new(user_params)
    if user.save
      render json: user, status: 201
    else
      render json: user.errors.full_messages, status: 400
    end
  end
  private
    def user_params
      params.require(:user).permit(:name)
    end
end
config/routes.rb
Rails.application.routes.draw do
  resources :users
end
作ったAPIをAdvanced REST clientを使って叩いてみる。
user作成
user取得
vueからuser作成、一覧取得
CORSの対応
rack-cors(https://github.com/cyu/rack-cors)というgemを使ってCORSを許可。
Gemfileに
gem 'rack-cors'
を追加、bundle installして config/initializers/cors.rbを下記のよう編集
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:8080'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
axiosの設定
api通信するようにaxiosをinstall。
npm install axios
hostが異なるので、hostを設定したaxiosを使うようします。
frontend/src/lib/axios.ts
import axios from 'axios'
export default axios.create({
    baseURL: 'http://localhost:3000/',
})
user一覧取得と、作成
ここからは私の理想のtsの構成になっていますので、参考程度にしてください。
Userモデルを作成。
フロントで扱うuserモデルを定義します。
dayjsを使っているのでdayjsをインストールしてください。
frontend/src/models/user.ts
import dayjs, {Dayjs} from 'dayjs';
export default class User {
    id: number
    name: string
    createdAt: Dayjs
    updatedAt: Dayjs
    constructor(id: number, name: string, createdAt: string, updatedAt: string) {
        this.id = id
        this.name = name
        this.createdAt = dayjs(createdAt)
        this.updatedAt = dayjs(updatedAt)
    }
}
フロントでrailsのapiを叩く機能を抽出します。
userを作成する時のパラメータの定義と、responseからUserモデルを作成する機能をここで持ちます
frontend/src/apis/users_api.ts
import axios from "/@/lib/axios"
import User from "/@/models/user"
function createUserFromResponse(res: any): User {
    return new User(res.id, res.name, res.created_at, res.updated_at)
}
export const getUsers: () => Promise<User[]> = async () => {
    const res = await axios.get('/users')
    return res.data.map((res: any) => createUserFromResponse(res))
}
export interface UserCreateParams {
    name: String
}
export const createUser: (params: UserCreateParams) => Promise<User> = async (params: UserCreateParams) => {
    const res = await axios.post('/users', {user: params})
    return createUserFromResponse(res.data)
}
最後にコンポーネントを作って終わりです。
frontend/src/App.vue
<template>
  <div>
    <form>
      <label>
        name
        <input v-model="form.name" type="text"/>
      </label>
      <button @click.prevent="createUser">保存</button>
    </form>
    <table>
      <thead>
      <tr>
        <th>id</th>
        <td>name</td>
        <td>createdAt</td>
        <td>updatedAt</td>
      </tr>
      </thead>
      <tbody>
      <tr v-for="user in users" :key="user.id">
        <th>{{ user.id }}</th>
        <td>{{ user.name }}</td>
        <td>{{ user.createdAt.format() }}</td>
        <td>{{ user.updatedAt.format() }}</td>
      </tr>
      </tbody>
    </table>
  </div>
</template>
<script lang="ts">
import {createUser, getUsers} from '/@/apis/users_api'
import {defineComponent} from 'vue'
import User from '/@/models/user'
export default defineComponent({
  data() {
    return {
      form: {
        name: '' as string
      },
      users: [] as User[]
    }
  },
  methods: {
    async createUser() {
      const user = await createUser(this.form)
      this.users.push(user)
      this.form.name = ''
    }
  },
  async created() {
    this.users = await getUsers()
  }
})
</script>
defineComponentにして型定義するだけで、あとはVue2と変わらないですね。
最後に
意外と簡単に型の指定できました。Vue2でもCompositionAPIを入れればできたのかな?
今度はVuexを使った実装もやっていきます!
Discussion