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-html
や v-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
ドットにより特別なプレフィクスがあります。例えば .prevent
は v-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つの制限があります。
- オブジェクトや配列、
Map
Set
などのコレクションでだけ動作し、string
やnumber
のようなプリミティブ型は対象でない。 - リアクティブなオブジェクトはプロパティアクセスで動作するので同じリアクティブオブジェクトの参照を保持しなければなりません。リアクティブ接続が失われてしまうので置き換えることが出来ません。
この意味するところは、リアクティブオブジェクトのプロパティをローカル変数に割り当てたりデストラクチャしたり、または関数を通してプロパティを渡すとき、リアクティブ接続が失われます。
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
class
と style
をデータに応じて動的に操作することが出来ます。 :class
は v-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>