Open13

Vueのドキュメントを読む

なんか vue@latest が Vue 3 になったのにあわせてか、ドキュメントがリニューアルされていた。せっかくなのでVueをしっかりやっていこうと思う。

https://vuejs.org/

簡単なコンポーネントの例。ボタンをクリックするとカウントが増えていく。

import { createApp } from 'vue'

createApp({
  data() {
    return {
      count: 0
    }
  }
}).mount('#app')
<div id="app">
  <button @click="count++">
    Count is: {{ count }}
  </button>
</div>

この例から、Vueのコア機能ふたつがわかる。

  • 宣言的レンダリング: HTMLを拡張したテンプレート構文を使ってJavaScriptの状態をHTMLに反映させられる。
  • リアクティビティ: JavaScriptの状態を監視して変更があったら効率的にDOMを変更する。

静的HTMLに動きを加えたり、Web Componentsとして利用したり、SPA、SSR、SSGで利用したり、デスクトップやモバイルアプリ、WebGL、ターミナルでも使えるらしい。ReactでいうInkとかReact Nativeみたいなものがあるということだろうか?あまり聞かないけれども。

ビルドツールの使える環境では Single-File Components というファイルフォーマットで書くことが推奨されている。拡張子は .vue で、HTMLとCSSとJSを1ファイルにまとめて書く感じ。

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

Vueには2種類のAPIがある。

Options API はコンポーネントをオブジェクトとしてとらえ、data methods mounted といったプロパティを使ってロジックを記述していく。コンポーネントインスタンスを this で参照できる。

<script>
import { defineComponent } from "vue";

export default defineComponent({
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count++;
    },
  },
  mounted() {
    console.log(`The initial count is ${this.count}.`);
  },
});
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Composition APIvue からインポートした関数を setup 関数の中で使用することでロジックを記述していく。

<script>
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const count = ref(0);
    const increment = () => count.value++;
    onMounted(() => console.log(`The initial count is ${count.value}.`));

    return { count, increment };
  },
});
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

defineComponent~setup() { を書くのを省略するために script setup というSFCの記法が用意されている。

<script setup>
import { ref } from "vue";

const count = ref(0);
const increment = () => count.value++;
onMounted(() => console.log(`The initial count is ${count.value}.`));
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

The Options API is centered around the concept of a "component instance" (this as seen in the example), which typically aligns better with a class-based mental model for users coming from OOP language backgrounds. It is also more beginner-friendly by abstracting away the reactivity details and enforcing code organization via option groups.

コンポーネントインスタンス、代表的には this を中心に考えるのが Options API。this 以外にもコンポーネントインスタンスを参照する方法があるっぽい。オブジェクト指向プログラミングにおけるクラスの概念に慣れている人はメソッドとプロパティで構成されていて、またリアクティビティの詳細を抽象化しているるこっちのがとっつきやすい。

The Composition API is centered around declaring reactive state variables directly in a function scope, and composing state from multiple functions together to handle complexity. It is more free-form, and requires understanding of how reactivity works in Vue to be used effectively. In return, its flexibility enables more powerful patterns for organizing and reusing logic.

ref で定義した count のように、リアクティブな状態 (ステート) を関数内で直接宣言していろいろな関数から扱う (?) のが Composition API。より自由で、リアクティビティに関する知識がより求められる。柔軟なのでロジックをまとめたり (かな?) ロジックの再利用をしたりするためのパターンが使える。

In fact, the Options API is implemented on top of the Composition API!

ということらしい。

createApp でアプリケーションインスタンスを作成し、それをCSSセレクタ記法で指定したコンテナ要素にマウントすることで動作する。

import { createApp } from "vue";

const app = createApp({
  // ルートコンポーネント
});

// CSSのセレクタと同じ記法
app.mount("#app");

コンテナ要素はアプリの一部とはみなされない。

app.configapp.component といったプロパティやメソッドでアプリの設定を行う。

コンテナ要素の中にテンプレート記法を書ける。

<div id="app">
  <button @click="increment">Count is: {{ count }}</button>
</div>
import { createApp, ref } from 'vue'

const app = createApp({
  setup() {
    const count = ref(0);
    const increment = () => count.value++;
    onMounted(() => console.log(`The initial count is ${count.value}.`));

    return { count, increment };
  },
})

app.mount('#app')

最初に出てくるディレクティブが v-html なのおかしいだろ。内容が頭に入ってこない。

<script setup>
import { ref } from "vue";

const htmlContent = ref(`<a href="https://example.com">hogehoge</a>`);
<script>

<template>
  <span v-html="htmlContent"></span>
</template>

span のinnerHTMLを htmlContent にする。htmlContent が更新されたらちゃんと反映される。

文字列ベースのテンプレートエンジンではないので (?)、v-html に入れる文字列で {{ count }} をやっても効果がない。コンポーネントを使おう。

v-bind:属性名 で属性 (attribute) にリアクティブ値を埋め込める。

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

const colorMode = ref<"dark" | "light">("light");

const inverse = (mode: "dark" | "light") =>
  mode === "dark" ? "light" : "dark";

const toggle = () => (colorMode.value = inverse(colorMode.value));
</script>

<template>
  <button @click="toggle" v-bind:class="colorMode">Now: {{ colorMode }}</button>
</template>

<style>
button {
  border: none;
  padding: 8px;
  border-radius: 8px;
}

button.dark {
  background-color: #333;
  color: #ddd;
}
button.light {
  background-color: #ddd;
  color: #333;
}
</style>

v-bind:属性:属性 と略記できる。

  <button @click="toggle" :class="colorMode">Now: {{ colorMode }}</button>

HTML には <button disabled> のように、ついているか否か、というタイプの属性がある。Vueでは :disabled="foo" のように書くと、foo がtruthyなとき <button disabled> に、falsyなとき <button> になる。

以下のようなオブジェクトがあるとき、

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

v-bind= を使って展開できる。JSXでいう {...objectOfAttrs}

<div v-bind="objectOfAttrs"></div>
ログインするとコメントできます