Vue3のドキュメントで理解したこと~Vueを知らないエンジニアがドキュメントで理解出来ること

Vue3のドキュメントでEssentialsについて理解したことを紹介します。こちらはSFC形式で私が理解した内容です。

公式ドキュメント

公式ドキュメントのイントロダクションは良く出来てるなあと思います。英語ですがよく出る単語さえ把握しておけばどこもだいたい同じ感じで読み進められます。
英語の他に、ソースが記載されています。プログラマであればソースは普段書いたり読んだりしているので理解が早いと思います。
Single-File Components(SFC)とOptions APIの2通りのスタイルがあります。
Intoroductionにどちらを選択すべきかは詳しく載っています。要約するとどっちを使っても良く、初めてなら理解しやすい方を選んでと書いてあるようです。なので、私はSFCを使ってEssentialsの章を読み進めました。
ドキュメントのトップにあるApi ReferenceでCompositionを選択すると、ドキュメント内のソースがSFC形式で表示されます。Optionsを選択するとOptions APIの形式で表示されます。

ローカル環境で動作確認

自分で手を動かしてみると理解が早いです。ドキュメントではPlaygroundのリンクが時折あり、そちらでソースを書き換えたりして動きを確認することができます。
しかし、自分で変更したソースを残したいので、ローカル環境にVueをインストールして動作を確認しました。
Quick Startにインストール手順がありますので実施してみましょう。npmを普段使っている人は問題なくインストールが出来ると思います。
私はドキュメントに書いてある Visual Studio Code + Volar extensionの環境で構築してみました。
npmでインストールして質問される項目はすべてNoでインストールしました。まずはシンプルにインストールした方が他の要素で惑うことが少ないと思います。

> npm init vue@latest
# 聞かれた質問はすべてNo(Enterのみ)
> cd <your-project-name>
> npm install
> npm run dev

参考までに、私がEssentialsを一通り自分で試したソースはコチラです。

使ってみた印象

過去にReactを同じような感じで勉強していたので比較的理解が早かったように思います。画面の部品としてコンポーネントを実装して組み合わせる感じです。部品を複数のメンバーで同時並行的に共同開発し易いと思いました。コンポーネント間のデータの渡し方もバケツリレーのように子から親に渡すのはReactと同じようです。アプリケーション全体として状態を管理するにはPiniaというライブラリを使うようです。
アプリケーションが小さいなら使わなくてもバケツリレーで出来なくはなさそうです。
以降は各項目について私が理解した内容をコツコツ追記していきます。

Vue3のドキュメントで理解したこと

Vue3のドキュメントでEssentialsについて理解したことを紹介します。こちらはSFC形式で私が理解した内容です。

Creating an Application

The application instance

アプリケーションの根っこの部分、ここからアプリケーションのインスタンスを生成するようです。

import { createApp } from 'vue'
// import the root component App from a single-file component.
import App from './App.vue'

const app = createApp(App)

The Root Component

App.vueに部品をインポートして配置します。
多くのアプリケーションは木の構造のようになり、例えばTodoアプリケーションなら下記のようなコンポーネント構成になります。

App (root component)
├─ TodoList
│  └─ TodoItem
│     ├─ TodoDeleteButton
│     └─ TodoEditButton
└─ TodoFooter
   ├─ TodoClearButton
   └─ TodoStatistics

Mounting the App

アプリのインスタンスは.mount()が呼ばれるまで描画されません。

<div id="app"></div>
app.mount('#app')

Template Syntax

HTMLベースのテンプレートシンタックスを使います。内部的に良く最適化されたJavascriptコードをコンパイルしていて、変更があったときに更新が最小になるようにDOMを操作してくれるようです。

公式HPのプレイグラウンドを使えばブラウザだけで簡単に動作確認が出来るので試してみて下さい

Text Interpolation

最も基本的な形式は以下のように{{}}で括ります。

<span>Message: {{ msg }}</span>

msgのプロパティ値を置き換えて表示します。プロパティが変更されるたびに更新されます。

Raw HTML

通常のプロパティはプレーンテキストとして表示されます。プロパティにHTMLを指定してもそのまま表示されます。HTMLとして解釈して表示するにはv-htmlディレクティブを使います。

const rawHTML = ref('<span style="color:red">Hello World</span>')
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

1行目はrawHTMLの値をタグを含めてそのまま表示し、2行目はHTMLとして解釈されて赤文字で表示されます。

Attribute Bindings

v-bindディレクティブを使うとHTMLの属性に変数を割り当てられる。

<div v-bind:id="dynamicId"></div>

この例はID属性にdynamicIdとして宣言したプロパティを指定します。nullやundefinedならID属性が削除されて表示されます。

Shorthand

v-vindはよく使うので省略記法があります。

<div :id="dynamicId"></div>

:で始まる属性は通常のHTMLと少し違うように見えるますが正しくパースしてくれます。また、今後の説明では省略して記述します。

Boolean Attributes

ブール属性はtrue/falseを指定します。例えばdisabled属性です。

<button :disabled="isButtonDisabled">Button</button>

isButtonDisabled変数によってtrue/falseをコントロール出来ます。

Dynamically Binding Multiple Attributes

Javascripオブジェクトで複数の属性を持つ場合

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

1つの要素で複数の属性を指定出来ます。この場合、省略記法が使えません。

<div v-bind="objectOfAttrs"></div>

Using JavaScript Expressions

JavaScript式でもデータをバインディング出来ます。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

これらはJavaScript式として評価されます。

Expressions Only

それぞれのバインディングは1つの式だけです。次のような式は動きません。

<!-- this is a statement, not an expression: -->
{{ var a = 1 }}

<!-- flow control won't work either, use ternary expressions -->
{{ if (ok) { return message } }}

Calling Functions

コンポーネントで定義したメソッドを呼ぶことは可能です。

<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

Directives

ディレクティブは v- プレフィクスを持った特別な属性です。上で説明した v-htmlv-bind などです。
ディレクティブの仕事は式の値が変更されたときにDOMを更新することです。

<p v-if="seen">Now you see me</p>

これは v-if ディレクティブが <p> エレメントを seen 変数が true なら追加し、そうでないなら削除します。

Arguments

いくつかのディレクティブはディレクティブ名の後の : で示される引数をもちます。 v-bind はHTML属性を更新するのに使われます。

<a v-bind:href="url"> ... </a>

<!-- shorthand -->
<a :href="url"> ... </a>

href が引数です。 href 属性を url の値でバインドします。
もう一つの例は v-on ディレクティブでDOMイベントを検知します。

<a v-on:click="doSomething"> ... </a>

<!-- shorthand -->
<a @click="doSomething"> ... </a>

click 引数は検知するイベント名です。省略記法は @ です。

Dynamic Arguments

[] で括ったディレクティブの引数はJavaScript式を使うことが出来ます。

<!--
Note that there are some constraints to the argument expression,
as explained in the "Dynamic Argument Value Constraints" and "Dynamic Argument Syntax Constraints" sections below.
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- shorthand -->
<a :[attributeName]="url"> ... </a>

attributeName は動的にJavascript式として評価されます。例えば、コンポーネントのインスタンスが attributeName プロパティを持っていて、href であればそれは v-bind:href と同じ機能になります。
似たようにイベント名を操作して動的に引数を使うことが出来ます。

<a v-on:[eventName]="doSomething"> ... </a>

<!-- shorthand -->
<a @[eventName]="doSomething">

この場合 eventName の値が focus なら、 v-on:focus と同じ機能になります。

Dynamic Argument Value Constraints

動的引数は文字列を想定しています。 null ならバインディングが削除されます。文字列以外の値はワーニングを発生します。

Dynamic Argument Syntax Constraints

動的引数はいくつかのシンタックス制限があります。スペースやクオートはHTML属性名のなかでは妥当でありません。

<!-- This will trigger a compiler warning. -->
<a :['foo' + bar]="value"> ... </a>

もし複雑な動的引数を通したいなら、 computed property を使うのが良いです。
テンプレートを使うときアッパーケース文字と一緒にキーを命名するのを避けるべきです。

<a :[someAttr]="value"> ... </a>

上記は :[someattr] に変えましょう。もしコンポーネントで somattr の代わりに someAttr があれば動作しません。SFCテンプレート内であればこの制約の対象でないです。

Modifiers

ドットにより特別なプレフィクスがあります。例えば .preventv-on ディレクティブにイベントで event.preventDefault を呼びます。

<form @submit.prevent="onSubmit">...</form>

Reactivity Fundamentals

Declaring Reactive State

Reactiv State はコンポーネントで扱うオブジェクトや配列です。

<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 })

function increment() {
  state.count++
}
</script>

<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>

<script setup> 内でインポートと変数宣言をします。同じコンポーネント内のテンプレートで利用すること出来ます。

DOM Update Timing

Reactiv State は更新されたらDOMは自動的に更新されますが、注目すべきことはDOMの更新は同時でないのです。Vueはそれらを「next tick」までバッファしどれだけ多くのstateを変更しても1度だけで更新します。
stateが変更された後にDOMが更新されるまで待つために nextTick() global API があります。

import { reactive,nextTick  } from 'vue'
const state = reactive({ count: 0 })
function increment() {
  state.count++
  console.log({'before':document.getElementById('counter').textContent})

  nextTick(()=>{
    console.log({'after':document.getElementById('counter').textContent})
  })
}

Deep Reactivity

reactive はより複雑にしてネストされたオブジェクトや配列でさえ変更を検出してくれます。

import { reactive } from 'vue'

const obj = reactive({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // these will work as expected.
  obj.nested.count++
  obj.arr.push('baz')
}

Limitations of reactive()

reactive() APIは2つの制限があります。

  1. オブジェクトや配列、Map Set などのコレクションでだけ動作し、stringnumber のようなプリミティブ型は対象でない。
  2. リアクティブなオブジェクトはプロパティアクセスで動作するので同じリアクティブオブジェクトの参照を保持しなければなりません。リアクティブ接続が失われてしまうので置き換えることが出来ません。
    この意味するところは、リアクティブオブジェクトのプロパティをローカル変数に割り当てたりデストラクチャしたり、または関数を通してプロパティを渡すとき、リアクティブ接続が失われます。
const state = reactive({ count: 0 })

// n は state.count から接続が切れたローカル変数です。
let n = state.count
// もとの state に影響しません。
n++

// count も state.count から接続が切れたローカル変数です。
let { count } = state
// もとの state に影響しません。
count++

// 関数はプレーンな数値を受け取り、state.count への変更を追跡出来なるくなります。
callSomeFunction(state.count)

Reactive Variables with ref()

Vueにはリアクティブ変数に ref() 関数があります。どんなタイプの値にも対応しています。
ref() が受け取った引数はrefオブジェクト内にラップして .value プロパティで値を返します。

import { ref } from 'vue'

const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

Ref Unwrapping in Templates

ref() で作成した変数は <template> で参照するときに .value は必要ありません。

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }} <!-- no .value needed -->
  </button>
</template>

Computed Properties

template にロジックを多く記述するとメンテナンスがしづらくなります。 computed property を使うとロジックを切り出せます。
computed()computed ref を返します。 autheor.books が変更したときに publishedBooksMessage も変更されます。

const now = computed(() => Date.now())

Date.now() はreactiveに依存していないので更新されることはありません。
シンプルに同じ処理の関数と比較すると computed() の方は、reactiveが変更されなければlist などの重い処理で余計な描画をしないように出来ます。

<script setup lang="ts">
import { reactive, computed } from "vue"

const author = reactive({
  name: "John Doe",
  books: [
    "Vue 2 - Advanced Guide",
    "Vue 3 - Basic Guide",
    "Vue 4 - The Mystery",
  ],
})

// a computed ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? "Yes" : "No"
})
</script>

<template>
  <p>Has published books:</p>
  <span>computed:{{ publishedBooksMessage }}</span>
  <span>expression:{{ author.books.length > 0 ? "Yes" : "No" }}</span>
</template>

computed property は読み取り専用とし変更をしないことです。reactiveが変更されるたびに更新されるスナップショットのようなものです。

Class and Style Bindings

classstyle をデータに応じて動的に操作することが出来ます。 :classv-bind:class の省略形です。
:class は真偽値でtrueなら出力で、falseなら出力されません。

<script setup lang="ts">
import { ref } from "vue"
const items = ref([
  { id: 1, label: "apple", purchased: false, highPriority: true },
  { id: 2, label: "orrange", purchased: false, highPriority: false },
  { id: 2, label: "banana", purchased: true, highPriority: true },
])
</script>
<template>
  <ul>
    <li
      v-for="({ id, label, purchased, highPriority }, index) in items"
      :key="id"
      class="static-class"
      :class="{ strikeout: purchased, priority: highPriority }"
    >
      {{ label }}
    </li>
  </ul>
</template>
<style>
.strikeout {
  background-color: rgb(179, 179, 212);
}
.priority {
  color: red;
}
.static-class {
  font-size: large;
}
</style>

:style は JavaScript object valuesをバインドします。

<script>
const activeColor = ref('red')
const fontSize = ref(30)
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

先頭に戻る