概要

  • vue3 を触ったのでその備忘録

立ち上げ

この備忘録の立ち上げでは、

  • vue3
  • vite
  • Tailwind CSS を使って立ち上げる。立ち上げたあと、vue3 の諸々を触れる
npm init vite@latest
npm install
npm install -D tailwindcss postcss autoprefixer

にて必要なものを install。vue で tailwindcss を適用するのはこのサイトのままやれば問題なし。

//main.ts
import { createApp } from "vue";
import App from "./App.vue";
import "./index.css";

createApp(App).mount("#app");

シンプル。

少しだけ中身を見ていくことにした。

<script setup lang="ts">
import { ref } from 'vue'

defineProps<{ msg: string }>()

const count = ref(0) //count.valueでデータを使えるようになる。html内では{{count}}と記載すれば良い
</script>

//以下の書き方がしっくりくる。上記のジェネリクスで記載している書き方は不自然に感じた
const props = defineProps({
  msg: {type: String,require: true}
});

見たことのないメソッドが存在した。

  • defineProps:props の成り代わり。
  • ref:は data メソッドの成り代わり

前はいか

props: {
msg: {
    type: string,
    require: true,
    defaut: "",
 },
},
  data() {
    return {
        count:0
    }
}
//nuxt ts && decorator
@Prop({ type: string, required: true , default: ""}) readonly msg!: string

component を呼び出す

<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import HelloWorld from "./components/HelloWorld.vue";
</script>

<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />

import するだけで components を使えるようになっている。

vue3 で気になったこと

defineComponent を記載することでライフサイクルは setup 関数に宣言するようになった

vue2 で記載されていたライフサイクルの書き方は、setup の関数を用いての書き方になった。具体的には以下の通り。

import { defineComponent } from "vue";

export default defineComponent({
  emits: ["click"],
  setup(props, context) {
    // Attributes (Non-reactive object)
    console.log(context.attrs);

    // Slots (Non-reactive object)
    console.log(context.slots);

    // Emit Events (Method)
    console.log(context.emit);
  }
});

context オブジェクトには、emit がある。emit を呼び出したい時には、definecomponent 内で emit で使われる関数名を指定してから、使うことになる。また、vue3.2 系から、export default で setup 関数を使わない書き方になっているが、ここでは省略する。

DeepReadonly はメソッドで state 管理をさせるもの

store でよく使われるのは、readonly である。readonly の実態は変数の改変を防ぐためのものである。具体的にはいかが制限される。

//プロパティの代入は制限され、エラーとなる
let obj: { readonly x: number } = { x: 1 };
obj.x = 2;

//一方で、プロパティの代入は問題ない
let obj: { readonly x: number } = { x: 1 };
obj = { x: 2 };


//実用的に
export interface TodoStore {
  state: DeepReadonly<TodoState>
  addTodo: (todo: Params) => void
}
const state = reactive<TodoState>({
  todos: []
})


const todoStore: TodoStore = {
  state: readonly(state),
  addTodo
}

という感じで、state の中身の todos のプロパティを改変しないように使われる。state.todos = aaaみたいなことはできなくなる。また、readonly のインタフェース的な役割??となっているのが DeepReadonly だと思う。

監視したい値を ref や reactive で監視する。

Vue3 系や CompositionAPI からは data()が廃止される代わりに監視したい値を ref や reactive を使用することで値の変更を監視することができるようになりました。参考

//refの使い方
import { ref } from 'vue'

const conter = ref<number>(0)

counter.value++

//reactiveの使い方
import { reactive } from 'vue'

interface FormState {
  email: string
  password: string
}

const formState = reactive<FormState>({
  email: '',
  password: ''
})

//stateではreactiveを使う
const state = reactive<TodoState>({
  todos: []
})

ref と reactive の使い分けは複数であるかどうか。ref は counter のように単一の変数を監視したい時に使う。一方で、reactive はオブジェクトのような変数を監視したい時に使う。

vue3 での状態管理

まず vuex を使うことは推奨されていないように思える。ここでは、その代わりとなる provie/inject を勉強したので記載する。

簡単にいうと親コンポーネントで provide を宣言した、store オブジェクトを子コンポーネントで使えるようにするものである。

//src/store/todo.ts
export default todoStore
export const todoKey:InjectionKey<TodoStore> = Symbol('todo')


//src/App.vue
<script lang="ts">
import { defineComponent, provide } from 'vue'
import TodoStore, { todoKey } from '../store/todo/index'
export default defineComponent({
  name: 'App',
  setup () {
    //イメージとしては、keyを設定してそのkeyを元にstoreの情報を取り出すイメージである
    provide(todoKey, TodoStore)
  }
})
</script>

//src/TodoList.vue
import { inject } from '@vue/runtime-core'
import { todoKey } from '../../store/todo/index'
export default defineComponent({
setup () {
const todoStore = inject(todoKey)

if (!todoStore) {
  throw new Error('todoStore is not provided')
  }
}})

provide(todoKey, TodoStore)にて Key で指定した Value を呼び出せる。その子コンポーネントに相当する TodoList にて key をもとにそのコンポーネントに inject してやることで、store で宣言した、オブジェクトを使うことができるようになっている。 なので、これまで親コンポーネントで取り出していた store の値を props に渡す、という作業をとる必要がなくなった。良いところはこれまでは props の値を渡すようなことが必要となっていたが、inject に対して mock すればよく todoStore の受け取るものを記載しやすくなった。

emits の書き方について

emits: ['clickDelete']のように明示的に emits を指定する必要があるようになった。emit は子コンポーネントが親コンポーネントに対して値を渡すような時に記載する方法である。

<script lang="ts">
import { defineComponent, PropType } from '@vue/runtime-core'
import { Todo } from '../../types/types'

export default defineComponent({
  props: {
    todo: {
      type: Object as PropType<Todo>,//propのオブジェクトはPropTypeで宣言しておく必要がある
      required: true
    }
  },
  emits: ['clickDelete', 'clickTitle'],//emitに使われる関数を記載しておく必要がある。
  setup (props, { emit }) {
    const clickDate = () => {
      emit('clickDelete', props.todo.id)
    }

    const clickTitle = () => {
      emit('clickTitle', props.todo.id)
    }


    return {
      clickDate,
      clickTitle
    }
  }
})
</script>

//親コンポーネント
<TodoItem  v-for="todo in todoStore.state.todos" 
:key="todo.id"
:todo="todo"
@click-delete="clickDelete"
@click-title="clickTitle"
/>

という感じに記載する必要がある。

非同期処理

非同期処理を書く際は Suspense で囲まないといけない。これが気持ち悪い、と個人的には思っているのだが、suspense で囲みデータが呼び出されるのをまつ

<div v-if="error">
  {{ error.message }}
</div>
<Suspense v-else>
  <template #default>
    <AsyncTodos />
  </template>
  <template #fallback>
    <div>Loading...</div>
  </template>
</Suspense>

上記のように、AsyncTodos で非同期処理を書くことを念頭に考えると、Suspense で囲むようにしないといけない。多分。

最後にこれが最大に参考になった。参考

プロフィール
Kobasan
現在都内のWeb会社で働いている人です.主にこれまで,Web関連及び機械学習周りのことをやってきました.このブログではそれらの内容を含む,ちょっとした私の備忘録を記載するものです.