とほほのVue 3入門
- Vue 3とは
- インストール
- チュートリアル
- ディレクティブ
- グローバルAPI
- アプリケーションAPI
- createApp()
- createSSRApp()
- app.mount()
- app.unmount()
- app.onUnmount()
- app.component()
- app.directive()
- app.use()
- app.mixin()
- app.provide()
- app.runWithContext()
- app.version
- app.config.errorHandler
- app.config.warnHandler
- app.config.performance
- app.config.compilerOptions
- app.config.globalProperties
- app.config.optionMergeStrategies
- app.config.idPrefix
- app.config.throwUnhandledErrorInProduction
- 汎用API
- アプリケーションAPI
- Composition API
- ビルトイン
- 単一ファイルコンポーネント
- Options API
- スロット
- サーバーサイドレンダリング(SSR)
- 関連プロジェクトとエコシステム
- その他
- リンク
Vue 3とは
- 発音は view と同じで ヴュー です。
- React, Angular と並び SPA(Single Page Application) フレームワークとして利用されています。
- Google で AngularJS を開発していた 尤雨溪(Evan You) が開発し、2014年にリリースしました。
- MIT License で公開されており、個人・商用に限らず無償で利用することができます。
- 2016年10月1日に Vue 2 が、2020年9月18日に Vue 3 がリリースされました。
- Vue 2 は 2023年12月31日 にサポート終了となりました。
- 日本での Vue, React, Angular の人気度は Googleトレンド で確認できます。
- 本書では現時点の最新版 Vue 3.5.12 に関して説明します。
インストール
オンライン環境で試す
インストールすることなくオンラインで Vue を試す環境がいくつか用意されています。
CDNを使用する方法
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <div id="app"> {{ message }} </div> <script> const { createApp, ref } = Vue createApp({ setup() { const message = ref("Hello world!") return { message } } }).mount("#app") </script>
もしくは ES modules を用いて次のように書くこともできます。
<div id="app"> {{ message }} </div> <script type="module"> import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js' createApp({ setup() { const message = ref("Hello world!") return { message } } }).mount('#app') </script>
インポートマップを用いて 'https://unpkg...'
を 'vue'
とだけ記述できるようになります。
<script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module"> import { createApp, ref } from 'vue' :
npmを使用する方法
$ podman run -dit --name vue -p 80:80 -v `pwd`/vue:/opt/vue almalinux:9 $ podman exec -it --detach-keys=ctrl-\\ vue /bin/bash
$ dnf -y install git $ git clone https://github.com/creationix/nvm.git ~/.nvm $ echo "source ~/.nvm/nvm.sh" >> ~/.bashrc $ source ~/.bashrc $ nvm ls-remote $ nvm install v20.18.0
$ npm create vue@latest ※すべてデフォルト値を選択 $ cd vue-project $ npm install $ npm run dev
ホストアドレスやポート番号を変更するには vite.config.js ファイルに下記を追記します。
export default defineConfig({ : server: { host: "0.0.0.0", port: 80, } })
ビルドするには下記を実行します。ビルドした結果が dist フォルダに作成されます。
$ npm run build
Apache (httpd) から dist フォルダを参照します。
# dnf -y install httpd # sed -i 's/#ServerName/ServerName/' /etc/httpd/conf/httpd.conf # vi /etc/httpd/conf.d/vue.conf # /usr/sbin/httpd -DFORGROUND
<VirtualHost *:80> ServerName www.example.com DocumentRoot /opt/vue/vue-project/dist <Directory "/opt/vue/vue-project/dist"> Require all granted </Directory> </VirtualHost>
チュートリアル
main.js
src/main.js を下記の様に書き換えます。App.vue を id="app" の要素に割り当てます。
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app')
App.vue
*.vue ファイルは 単一ファイルコンポーネント (SFC: Single File Component) と呼ばれるもので、JavaScript (または TypeScript) を記述する script ブロック、HTML を記述する template ブロック、CSS を記述する style ブロックから構成されます。src/App.vue を下記の様に書き換えてみましょう。
<script setup> import { ref } from 'vue' const message = ref("Hello world!") </script> <template> <h1>{{ message }}</h1> </template> <style scoped> h1 { font-size: 40pt; } </style>
npm run dev
でサーバーを起動し、ブラウザからアクセスし、Hello world!
が表示されれば成功です。
テキスト展開({{...}})
下記の様に値 "Hello world!"
を持つリアクティブなオブジェクト(ref)を定義します。
const message = ref("Hello world!")
テンプレートで {{ ... }}
を記述することで ref の値を表示します。
<h1>{{ message }}</h1>
下記の様に宣言しても {{ ... }}
で表示することはできますが、message
の値を変更してもリアクティブに表示に反映することができません。
var message = "Hello world!"
ref の値を変更するには message
を直接変更するのではなく、message.value
の値を変更します。.value
の setter
が値の変更を感知し、変更を表示に反映してくれます。
message.value = "Bye!"
{{ ... }} の中では JavaScript を記述することができます。
<h1>{{ message.toUpperCase() + "!" }}</h1>
イベントハンドラー(@click)
@eventName でイベントハンドラーを呼び出すことができます。Up ボタンを押すたびにカウントアップしていきます。
<script setup> import { ref } from 'vue' const count = ref(0) function countUp() { count.value++ } </script> <template> {{ count }} <button @click="countUp">Up</button> </template>
子コンポーネント
子コンポーネントを呼び出す
まず、子コンポーネント (例: Child.vue) を用意します。
<template> <div>THIS IS CHILD</div> </template> <style scoped> div { color: #f00 } </style>
親コンポーネントからこれを読み込みます。
<script setup> import Child from './Child.vue' </script> <template> <Child /> </template>
子コンポーネントに値を渡す
親コンポーネントから子コンポーネントに属性を渡すには次のようにします。
<script setup> import Child from './Child.vue' </script> <template> <Child message="MY MESSAGE" /> </template>
<script setup> const props = defineProps(['message']) </script> <template> <div>{{ props.message }}</div> </template>
子コンポーネントにコンテンツを渡す
スロット を用いることで子コンポーネントにコンテンツを渡すことができます。
<script setup> import Child from './Child.vue' </script> <template> <Child>Hello!!</Child> </template>
<template>
<slot></slot> // Hello!!
</template>
子コンポーネントからイベントを受け取る
下記の様にして子コンポーネントからカスタムイベントを受け取ることができます。
<template> <button @click="$emit('my-click')">OK</button> </template>
<script setup> import { ref } from 'vue' import Child from './Child.vue' const count = ref(0) </script> <template> <Child @my-click="count++" /> {{ count }} </template>
汎用コンポーネント
下記の様にコンポーネントを登録しておくことで、アプリケーション内部のどの場所でも、登録したコンポーネントを import
無しに利用することができます。
-- main.js --
import { createApp } from 'vue'
import App from './App.vue'
import MyButton from './MyButton.vue'
const app = createApp(App)
app.component("MyButton", MyButton)
app.mount('#app')
ディレクティブ
テキストを表示する(v-text, {{...}})
指定したプロパティの値を表示します。v-text="..." は {{ ... }} と同じ意味を持ちます。
-- script -- const message = ref("Hello world!") -- template -- <div v-text="message"></div> <div>{{ message }}</div>
HTMLを表示する(v-html)
&, <, > をサニタイジングせず生の HTML として表示します。利用者が入力した文字列を HTML として表示してしまうとクロスサイトスクリプト問題(XSS) が発生してしまうため、使用には十分注意してください。
-- script -- const message = ref("<b>Hello world!</b>") -- template -- <div v-html="message"></div>
表示/非表示を切り替える(v-show)
真偽値によって表示する・しないを切り替えます。
-- script -- const flag = ref(true) function toggle() { flag.value = !flag.value } -- template -- <button @click="toggle">Toggle</button> <div v-show="flag">Hello world!</div>
条件により表示を切り替える(v-if/v-else-if/v-else)
if / else if / else の条件判断により表示を制御します。
-- script -- const num = ref(5) const ans = ref(5) -- template -- <div v-if="num < ans">Too small.</div> <div v-else-if="num > ans">Too big.</div> <div v-else>Correct!</div>
v-show
と v-if
は見た目上同じような動作になりますが、v-show
は条件の真偽に関わらずレンダリングを行いその表示・非表示を CSS で切り替えるのに対し、v-if
は偽の時はレンダリング自体をスキップし、真になった時点でレンダリングを行います。
<div v-show="flag">...</div> <div v-if="flag">...</div>
繰り返し(v-for)
リストや反復可能オブジェクトに対して繰り返し処理を行います。
-- script -- const users = ref([ { name: "Yamada", age: 36 }, { name: "Suzuki", age: 42 }, { name: "Tanaka", age: 53 }, ]) -- template -- <div v-for="user in users"> {{ user.name }} {{ user.age }} </div>
下記の様にして「10回繰り返す」を実装することもできます。
<div v-for="n in 10">...</div>
オブジェクトに対して key, value を列挙することもできます。
-- script -- const user = ref({ name: "Yamada", age: 36 }) -- template -- <div v-for="(value, key) in user"> {{ key }}: {{ value }} </div>
下記の様にループのインデックス番号(index)を参照することができます。
<div v-for="(user, index) in users"> {{ index }}: {{ user.name }}({{ user.age }}) </div>
<div v-for="(value, key, index) in user"> {{ index }}: {{ key }}: {{ value }} </div>
イベントをハンドリングする(v-on, @)
イベントハンドラーを呼び出します。@ は v-on: の省略形です。
-- script -- const count = ref(0) function onClick() { count.value++ } -- template -- <button v-on:click="onClick">Up</button> <button @click="onClick">Up</button> <div>{{ count }}</div>
インラインハンドラーとメソッドハンドラー
イベントハンドラーには メソッドハンドラー と インラインハンドラー 形式があります。メソッドハンドラー形式はメソッド名のみを記述します。ハンドラーは暗黙の引数 event を受け取ります。
-- script -- function onClick(event) { console.log(event.target); count.value++ } -- template -- <button @click="onClick">Click</button>
インラインハンドラー形式は JavaScript 構文を記述します。下記の様にメソッドに引数を渡すことができます。event を引数として渡したい時は $event と記述します。
<button @click="onClick(123)">Click</button> <button @click="onClick(123, $event)">Click</button>
インラインハンドラーの中で直接 ref を書き換えることもできます。この場合 .value
は不要です。
<button @click="count++">Up</button>
イベント修飾子
下記の様にイベント名の後に .once や .prevent などの修飾子を加えることができます。
<button @click.once="onClick">Click</button>
- .stop : event.stopPropagation() を呼び出し、イベントが親要素に伝播するのを抑止します。
- .prevent : event.preventDefault() を呼び出し、デフォルトの動作を抑止します。
- .self : イベントがこの要素からディスパッチされた場合にのみハンドラーを呼び出します。
- .capture : イベントリスナーをキャプチャーモードで追加します。
- .once : ハンドラーを一度だけ呼び出します。
- .passive : DOM イベントを { passive: true } で追加します。
キー修飾子
@keyup などのキーイベントに対してはキーを指定することができます。キー名は KeyboardEvent.key(↗) 値 (例: PageDown) をケバブケース (例: page-down) に置換したもの、または省略形 (.enter, .tab, .delete, .esc, .space, .up, .down, .left, .right, .ctrl, .alt, .shift, .meta) を指定します。
<input @keyup.page-down="onPageDown"> <input @keyup.alt.enter="onAltEnter">
.exact は Ctrl, Alt, Shift, Meta キーの押下を厳密に判断します。
<!-- Ctrl, Alt, Shift, Meta キーが押されていても発火 --> <button @click.ctrl="onClick">OK</button> <!-- Ctrl, Alt, Shift, Meta キーが押されてないときだけ発火 --> <button @click.exact="onClick">OK</button> <!-- Ctrl キーのみが押されている場合に発火 --> <button @click.ctrl.exact="onCtrlClick">OK</button>
マウスボタン修飾子
@click は通常マウスの左ボタンクリックに反応しますが、右ボタンや中央ボタンに反応させることができます。
<button @click="onLeftButtonClick">OK</button> <button @click.left="onLeftButtonClick">OK</button> <button @click.right="onRightButtonClick">OK</button> <button @click.middle="onMiddleButtonClick">OK</button>
動的イベント名
@[eventName] でイベント名を変数で指定することができます。下記のサンプルでは最初は @click に反応しますが、一度クリックすると @dblclick に反応するようになります。
-- script -- const eventName = ref("click") function onEvent() { console.log(eventName.value); eventName.value = "dblclick" } -- template -- <button @[eventName]="onEvent">{{ eventName }}</button>
属性バインド(v-bind)
属性値のバインド(:attr)
v-bind:attrName は属性値をバインドします。省略形は :attrName です。
-- script -- const url = ref("https://www.example.com") -- template -- <a v-bind:href="url">Link</a> <a :href="url">Link</a>
イメージ名のバインド(:src)
画像は /public に配置する方法、/assets に配置する方法があります。
<img src="/public/img/sample.png"> <img src="./assets/img/sample.png"> <img src="@/assets/img/sample.png"> <img src="~@/assets/img/sample.png">
:src でバインドするには下記の様にします。古い書き方で require() を用いる例も紹介されていますが、require() はブラウザ上の JavaScript ではサポートされておらず、Native ES Modules を使用する Vite では使用することができません。
-- script -- import sampleImg1 from "/public/img/sample.png" import sampleImg2 from "@/assets/img/sample.png" -- template -- <img :src="sampleImg1"> <img :src="sampleImg2">
クラス名のバインド(:class)
下記は v-bind を使用せずに普通にクラス名を割り当てる例です。
-- template -- <div class="red">Hello</div> -- style -- .red { color: red }
v-bind:class (省略形 :class) を用いると次のようになります。colorClass
の値が変わるとリアクティブに反映されます。
-- script -- const colorClass = ref("red") -- template -- <div :class="colorClass">Hello</div>
isError が true の時だけ red クラスを割り当てるには次のようにします。
<div :class="{'red': isError}">Hello</div> <div :class="{'red': isError == true}">Hello</div>
複数のクラスを指定する場合は [...] を用います。
<div :class="['red', 'bold']">Hello</div> <div :class="[colorClass, boldClass]">Hello</div>
[...] 内では三項演算子を使用することもできます。
<div :class="[isError ? 'red' : 'normal']">Hello</div> <div :class="[isError ? redClass : normalClass]">Hello</div>
下記の様にして適用するクラスを動的に変更することができます。
-- script -- import { ref, computed } from 'vue' const textClass = ref({'red':false, 'bold':false}) function onRed() { textClass.value.red = !textClass.value.red } function onBold() { textClass.value.bold = !textClass.value.bold } -- template -- <div :class="textClass">Hello</div> <button @click="onRed">Red</button> <button @click="onBold">Bold</button> -- style -- .red { color: red; } .bold { font-weight: bold; }
スタイルのバインド(:style)
スタイルを指定する際は次のように指定します。ハイフンを含む CSS プロパティ名はキャメルケースにするか、'...' で囲みます。
<div :style="{color: 'red', fontWeight: 'bold'}">Hello</div> <div :style="{color: 'red', 'font-weight': 'bold'}">Hello</div>
属性値をリアクティブに変更することもできます。
-- script -- const textColor = ref("red") -- template -- <div :style="{color: textColor}">Hello</div>
下記の様に指定することもできます。
-- script -- const textStyle = ref({color: 'red', fontWeight: 'bold'}) -- template -- <div :style="textStyle">Hello</div>
[...] で複数のスタイルオブジェクトを指定することもできます。
-- script -- const redStyle = ref({color: 'red'}) const boldStyle = ref({fontWeight: 'bold'}) -- template -- <div :style="[redStyle, boldStyle]">Hello</div>
プロパティ名が -webkit- などのベンダプレフィックスを持つ場合、これを省略することができます。下記の例で line-clamp
は '-webkit-line-clamp'
と記述すべきですが、省略しても Vue が適切に保管してくれます。
const textStyle = ref({ display: '-webkit-box', width: '10rem', lineClamp: 3, boxOrient: 'vertical', overflow: 'hidden', })
値に関しては補完してくれませんが、'box'
がサポートされていなければ '-webkit-box'
としたい場合、次の様に記述することができます。
const textStyle = ref({ display: ['box', '-webkit-box'], :
複数属性のバインド
下記の様にして複数の属性をまとめてバインドすることができます。
-- script -- const attrs = { id: "div1", class: "classA", style: "color:red" } -- template -- <div v-bind="attrs">...</div>
バンドル修飾子(.camel, ...)
.camel は属性名をキャメルケースに変換します。下記の例は viewBox="0 0 100 100"
に変換します。
<svg :view-box.camel="'0 0 100 100'"></svg>
モデル(v-model)
v-model は JavaScript から HTML、HTML から JavaScript への相互変換を行います。JavaScript の値が変わると画面上の表示が変わり、画面上の表示を編集すると JavaScript の値に反映されます。<input>
, <select>
, <textarea>
で利用できます。
-- script -- const message = ref("Hello!") -- template -- <div><input type="text" v-model="message"></div> <div>{{ message }}</div>
チェックボックスや複数選択(select multiple)の場合、下記の様に配列に割り当てることができます。
-- script -- const animals = ref([]) -- template -- <label><input type="checkbox" value="Cat" v-model="animals">Cat</label> <label><input type="checkbox" value="Dog" v-model="animals">Dog</label> <label><input type="checkbox" value="Fox" v-model="animals">Fox</label> <div>{{ animals }}</div>
ラジオボタンの場合は文字列に割り当てます。
-- script -- const animal = ref("") -- template -- <label><input type="radio" value="Cat" v-model="animal">Cat</label> <label><input type="radio" value="Dog" v-model="animal">Dog</label> <label><input type="radio" value="Fox" v-model="animal">Fox</label> <div>{{ animal }}</div>
レイジー修飾子(.lazy)
.lazy 修飾子をつけると @input の代わりに @change イベント発行の際に値を更新します。キー入力の際ではなく、フォーカスが他の部品に移動した際などに値が変更されるようになります。
<div><input v-model.lazy="message"></div>
ナンバー修飾子(.number)
.number 修飾子をつけると JavaScript 値を更新する際に数値として更新します。
-- script -- const count = ref(123) -- template -- <div><input v-model.number="count"></div> <div>{{ typeof count }} {{ count }}</div>
トリム修飾子(.trim)
.trim 修飾子をつけると JavaScript 値を更新する際に前後の空白とトリムします。
-- script -- const message = ref("Hello!") -- template -- <div><input v-model.trim="message"></div> <pre>[{{ message }}]</pre>
スロット(v-slot)
スロットを定義します。詳細は スロット を参照してください。
子要素のコンパイルをスキップ(v-pre)
子要素の {{...}}
などの記述のコンパイルをスキップします。
<div>{{ message }}</div> ... messageの値を表示 <div v-pre>{{ message }}</div> ... {{ message }} という文字列を表示
一度だけレンダリング(v-once)
一度だけレンダリングを行い、その後は更新を行いません。下記の例では count の値をカウントアップすると、1行目はカウントアップしますが、2行目はカウントアップしません。無駄なリアクティブ計算を抑制することで高速化に役立ちます。
<div>{{ count }}</div> ... カウントアップする <div v-once>{{ count }}</div> ... カウントアップしない
メモ化(v-memo)
v-memo=[value1, value2, ...]
のように記述し、前回のループの時と value1, value2, ...
の値に変動があったアイテムのみを再描画します。下記の例では、item.count
の値はすべてのアイテムでカウントアップしますが、item.selected
の値が変動したアイテムのみ再描画し、他のアイテムの再描画をスキップします。行数の多いループを高速化するのに有効です。
<script setup> import { ref } from 'vue' const items = ref([]) for (let i = 0; i < 10; i++) { items.value.push({ id: i, count: 0, selected: "" }) } items.value[5].selected = "←" function update() { for (let i = 0; i < 10; i++) { items.value[i].count++ items.value[i].selected = "" } items.value[Math.floor(Math.random(10) * 10)].selected = "←" } </script> <template> <ul v-for="item in items" v-memo="[item.selected]"> <li>{{ item.id }}: {{ item.count }} {{ item.selected }}</li> </ul> <button @click="update">Update</button> </template>
準備が整うまで非表示(v-cloak)
v-cloak はコンパイルの準備が整うまで要素を非表示にするために用いられます。ビルドを行う場合には必要ありません。
-- template -- <div v-cloak> {{ message }} </div> -- style -- [v-cloak] { display: none; }
グローバルAPI
アプリケーションAPI
createApp()
アプリケーションインスタンスを作成します。
import { createApp } from 'vue' const app = createApp({ ... })
createSSRApp()
SSR hydration モードのアプリケーションインスタンスを作成します。
import { createSSRApp } from 'vue' const app = createSSRApp({ ... })
app.mount()
DOM要素にアプリケーションインスタンスをマウントします。
const app = createApp(...) app.mount("#app")
app.unmount()
DOM要素からアプリケーションインスタンスをアンマウントします。
app.unmount()
app.onUnmount()
DOM要素からアプリケーションインスタンスがアンマウントされた時に呼び出すコールバックを定義します。
app.onUnmount(() => { ... })
app.component()
app.component(key, value) は key に value を割り当てます。app.component(key) は key に割り当てた value を取り出します。
app.component("mykey", "myvalue")
const value = app.component("mykey") // "myvalue"
app.directive()
カスタムディレクティブを定義します。下記の例では v-demo
を作成しています。
app.directive("demo", { created(el, binding, vnode) { ... }, beforeMount(el, binding, vnode) { ... }, mounted(el, binding, vnode) { ... }, beforeUpdate(el, binding, vnode, prevVnode) { ... }, updated(el, binding, vnode, prevVnode) { ... }, beforeUnmount(el, binding, vnode) { ... }, unmounted(el, binding, vnode) { ... }, })
- el : 対象要素。
- binding : バインド情報。
- value : ディレクティブの属性値。v-demo="'red'" を指定した場合 'red'。文字列を指定する場合は "..." の中に '...' を記述する必要があります。
- oldValue : 変更前のディレクティブの属性値。
- arg : ディレクティブの引数。v-demo:abc を指定した場合 "abc"。
- modifires : 修飾子。v-demo:abc.x.y を指定した場合 { x: true, y: true }。
- instance : ディレクティブが使用されるコンポーネントインスタンス。
- dir : ディレクティブ定義オブジェクト。
- vnode : 変更前の VNode。
- prevVnode : 変更後の VNode。
mounted
と updated
の場合に同じ動作を指定するだけでよい場合は、下記の様に簡略に記述することも可能です。
-- main.js -- app.directive("demo", (el, binding) => { el.style.color = binding.value }) -- App.vue -- <div v-color="'red'">Hello</div>
app.use()
プラグインを読み込みます。
import MyPlugin from './plugins/MyPlugin' app.use(MyPlugin)
app.mixin()
ミックスインは非推奨となりました。
app.provide()
アプリケーションから inject()
で参照可能な key-value を定義します。provide()
も参照してください。
-- main.js -- app.provide("my-key", "my-value") -- App.vue -- import { inject } from 'vue' const myValue = inject("my-key") // "my-value"
app.runWithContext()
アプリケーションインスタンスの様なコンテキストでコールバック関数を即座に実行します。
import { inject } from 'vue' app.provide("message", "Hello!!") const message = app.runWithContext(() => { return inject("message") }) console.log(message)
app.version
Vue のバージョンを返します。
console.log(app.version)
app.config.errorHandler
アプリケーションで発生するエラーをハンドリングします。テンプレートの記述誤りなどのエラーは拾ってくれないようです。
-- main.js -- app.config.errorHandler = (err, instance, info) => { console.log(err) } -- App.vue -- function func() { throw new Error("ERROR!!!!") }
app.config.warnHandler
アプリケーションで発生する警告をハンドリングします。開発モードでのみ動作し、プロダクションモードでは無視されます。
-- main.js -- app.config.warnHandler = (msg, instance, trace) => { console.log(msg) } -- App.vue -- {{ notExistingValue }}
app.config.performance
ブラウザの開発ツール(DevTools)によるパフォーマンストレースを有効にします。開発モードでのみ動作し、プロダクションモードでは無視されます。
app.config.performance = true
app.config.compilerOptions
実行時コンパイルのオプションを指定します。
app.config.compilerOptions.isCustomElment = tag => tag.startWith("ion-") // ion-* をカスタムエレメントと見なす app.config.compilerOptions.whitespace = "condense" // "condense" または "preserve" app.config.compilerOptions.delimiters = ["{{", "}}"] // {{...}} のデリミタを指定 app.config.compilerOptions.comments = true // コンパイル時にコメントを残す
app.config.globalProperties
アプリケーションから参照可能なプロパティを定義します。定義したプロパティはコンポーネントインスタンスで this.propertyName
で参照できます。
-- main.js -- app.config.globalProperties.myMessage = "Hello!" -- App.vue -- <script> export default { data() { return { myMessage: this.myMessage } } } </script> <template> {{ myMessage }} </template>
app.config.optionMergeStrategies
カスタムコンポーネントのオプションマージ戦略を定義します。詳細は app.config.optionMergeStrategies↗ を参照してください。
app.config.idPrefix
useId()
で生成される ID のプレフィックスを指定します。
-- main.js -- app.config.idPrefix = "my-app" -- App.vue -- console.log(useId()) // my-app-0 console.log(useId()) // my-app-1 console.log(useId()) // my-app-2
app.config.throwUnhandledErrorInProduction
開発モードでは開発者がエラーに気づくように未処理の例外がスローされますが、プロダクションモードでは利用者への影響を減らすためにログ出力のみとなります。この値を true
とすることでプロダクションモードでも未処理例外をスローするようになります。
app.config.throwUnhandledErrorInProduction = true
汎用API
version
Vue のバージョン情報を返します。
import { version } from 'vue' console.log(version)
nextTick()
リアクティブな値が変化した時、それが DOM に描画されるのを待ちます。下記の例では1つ目の console.log() ではカウントアップ前の値、2つ目の console.log() でカウントアップ後の値が表示されます。
-- script -- import { ref, nextTick } from 'vue' const count = ref(0) async function increment() { count.value++ console.log(document.getElementById('counter').textContent) await nextTick() console.log(document.getElementById('counter').textContent) } -- template -- <button id="counter" @click="increment">{{ count }}</button>
defineComponent()
<script setup>
ではなく <script>
を用いるケースで JavaScript (または TypeScript で)コンポーネントを定義します。本書では説明を省略します。
import { defineComponent } from 'vue' export default defineComponent({ props: { ... }, setup(props) { ... }, })
defineAsyncComponent()
コンポーネントを非同期に読み込みます。デフォルトでは読み込まれず、必要になった時に読み込まれるコンポーネントを定義することができます。
import { defineAsyncComponent } from 'vue' const AsyncChild = defineAsyncComponent(() => import('./AsyncChild.vue') )
Composition API
リアクティビティ:コアAPI
ref()
リアクティブな Ref オブジェクトを生成します。
import { ref } from 'vue' const message = ref("Hello world!")
reactive()
リアクティブなオブジェクトを生成します。
-- script -- import { reactive } from 'vue' const user = reactive({ name: "Yamada", age: 36 }) function increment() { user.age++ } -- template -- {{ user.name }}({{ user.age }}) // Yamada(36) <button @click="increment">Increment</button>
ref() と同じように利用できますが下記などの違いがあります。最近では reactive()
より ref()
を使用する方が推奨されているようです。
user = reactive("Yamada")
の様な数値や文字列などのプリミティブな値を持つことはできません。.value
を指定しなくてもuser.age++
のように値を変更することができます。user = new_user
のように値全体を書き換えることはできません。let {name} = user
のように分割代入を行うとリアクティブ性が失われます。let {name} = toRefs(user)
とすれば大丈夫。
computed()
JavaScript を用いて新しい Ref オブジェクトを生成します。{{ ... }}
の中にも JavaScript を記述することができますが、複雑になってきた場合は computed()
でロジックを script ブロックに移します。
-- script -- import { ref, computed } from 'vue' const num = ref(0) const doubleNum = computed(() => num.value * 2) -- template -- <button @click="num++">Up</button> {{ num }} / {{ doubleNum }}
下記の様に get()
と set()
を実装することで、num
が変動した時に doubleNum
を計算し、逆に、doubleNum
が変動した時に num
を逆計算することができます。
-- script -- import { ref, computed } from 'vue' const num = ref(0) const doubleNum = computed({ get() { return num.value * 2 }, set(newValue) { num.value = newValue / 2 } }) -- template -- <button @click="num++">Up</button> <button @click="doubleNum = doubleNum + 2">Up2</button> {{ num }} / {{ doubleNum }}
computed()
の第一引数には前回の値が渡されます。
computed((prev) => { ... })
readonly()
読み取り専用の ref を生成します。リファレンスなので元の値が変わると読み取り専用オブジェクトの値も変わります。読み取り専用オブジェクトの値を変更しようとすると変更は失敗し、警告が発生します。
-- script -- import { ref, readonly } from 'vue' const count = ref(123) const readOnlyCount = readonly(count) function countUp1() { read.value++ } // 両方の値がカウントアップする function countUp2() { readOnlyCount.value++ } // 警告が発生しカウントアップできない -- template -- <button @click="countUp1">countUp1</button> <button @click="countUp2">countUp2</button>
watch()
リアクティブオブジェクトの値が変更されたときに呼ばれるハンドラーを設定します。下記の例では、リアクティブオブジェクト count
の値が変更された時に、変更前の値と変更後の値をコンソールに書き出します。
-- script -- import { ref, watch } from 'vue' const count = ref(0) watch(count, (newValue, oldValue) => { console.log(`${oldValue} -> ${newValue}`) }) function countUp() { count.value++ } -- template -- {{ count }} <button @click="countUp">countUp</button>
複数の値を監視する場合は第一引数を配列にします。
watch([var1, var2], ...)
戻り値として監視を停止、一時停止、再開させるための関数を返却します。
const { stop, pause, resume } = watch(count, (newValue, oldValue) => { ... }) pause() // 監視を一時停止する resume() // 監視を再開する stop() // 監視を停止する
第3引数にはオプションを指定します。flush:'post'
はコンポーネントのレンダリングの後でウォッチャーが呼ばれます。
watch(count, (newValue, oldValue) => { ... }, { flush:"post" })
オプションには次のようなものがあります。
flush: post -- コンポーネントをレンダリングした後でウォッチャーを呼び出す flush: sync -- オブジェクトが変動すると直ちにウォッチャーを呼び出す immediate: true -- オブジェクトの変動に関わらずwatch()呼び出し時にもウォッチャーを呼び出す onec: true -- 最初の1回だけウォッチャーを呼び出す
watchEffect()
watch()
と同様リアクティブオブジェクトの値が変更されたときに呼ばれるハンドラーを設定します。watch()
は第1引数で監視するオブジェクトを明示的に指定するのに対して、watchEffect()
はハンドラの中で使用するオブジェクトが自動的に監視対象となります。wach()
は変更前の値を受け取れるのに対して watchEffect()
は受け取ることができません。停止、一時停止、再開関数やオプションは watch()
と同様です。
import { ref, watchEffect } from 'vue' const count = ref(0) watchEffect(() => { console.log(count.value) })
watchPostEffect()
watchEffect() に { flush:'post' } オプションをつけて呼び出すのと同等です。
watchPostEffect(() => { ... })
watchSyncEffect()
watchEffect() に { flush:'sync' } オプションをつけて呼び出すのと同等です。
watchSyncEffect(() => { ... })
onWatcherCleanup()
ウオッチャー内で fetch()
などの非同期関数を呼び出していると、非同期関数が終了するよりも前に再度ウォッチャーが呼び出されることがあります。その際に、前回のウォッチャーの非同期呼び出しをキャンセルしたい場合のハンドラーを設定します。下記の例では値が変動して1秒後にその値をロギングしますが、[Up] ボタンを何度も押した場合は setTimeout()
の非同期関数がキャンセルされて、最後の値のみが書き出されます。
import { ref, watch, onWatcherCleanup } from 'vue' const count = ref(0) watch(count, (newValue, oldValue) => { const timeoutId = setTimeout(() => { console.log(`${oldValue} -> ${newValue}`) }, 1000) onWatcherCleanup(() => { clearTimeout(timeoutId) }) }) function countup() { count.value++ }
リアクティビティ:ユーティリティAPI
isRef()
引数が Ref オブジェクトか否かをチェックします。TypeScript の場合は 型述語 型が返却されます。
const msg = ref("Hello!") console.log(isRef(msg))
unref()
引数が Ref オブジェクトであれば .value
の値を、さもなくば値を返します。下記の2行は同じ意味を持ちます。
console.log(unref(msg)) console.log(isRef(msg) ? msg.value : msg)
toRefs()
reactive()
で生成したリアクティブオブジェクトを Ref オブジェクトを要素に持つオブジェクトに変換します。元のオブジェクトの値を変更すると、toRefs()
で生成したオブジェクトの値も変わります。
-- script -- const user = reactive({name: "Yamada", age: 36}) const userRefs = toRefs(user) -- template -- {{ userRefs.name }} : {{ userRefs.age }}
toRef()
toRefs()
はリアクティブオブジェクトのすべての値を Ref 化しますが、toRef()
は単一の値のみを Ref 化します。上記の toRefs()
は次の様に記述できます。
const userRefs = { name: toRef(user, "name"), age: toRef(user, "age") }
toValue()
Ref をデリファレンスして値を取り出します。
const count = ref(123)
let countValue = toValue(count) // 123
isProxy()
引数が reactive()
, readonly()
, shallowReactive()
または shallowReadonly()
によって生成されたプロキシオブジェクトか否かをチェックします。
console.log(isProxy(value))
isReactive()
引数が reactive()
または shallowReactive()
によって生成されたオブジェクトか否かをチェックします。
console.log(isReactive(value))
isReadonly()
引数が readonly()
または shallowReadonly()
によって生成された読込専用オブジェクトか否かをチェックします。
console.log(isReadonly(value))
リアクティビティ:上級編
shallowRef()
shallow は「浅い」を意味します。ref()
はオブジェクト自体や、その子プロパティなど子孫プロパティをすべてリアクティブにしますが、shallowRef()
の場合はオブジェクトのみをリアクティブ化します。複雑な構造を持つ巨大なオブジェクトをリアクティブ化する際の効率を高めます。
const state = shallowRef({ count: 1 }) state.value = { count: 2 } // 変更を反映する(リアクティブ) state.value.count = 3 // 変更を反映しない(非リアクティブ)
triggerRef()
shallowRef()
で作成した浅い ref の子孫プロパティを変動させた際、ref を強制的にトリガーします。
state.value.count = 3 triggerRef(state)
customRef()
リアクティブオブジェクトを参照・設定する際の動作をカスタマイズした ref を作成することができます。参照(get)処理では track()
、設定(set)処理では trigger()
を呼び出します。
<script setup> import { customRef } from 'vue' const MyCustomRef = (value) => { return customRef((track, trigger) => { return { get() { console.log(`Get ${value}`) track() return value }, set(newValue) { console.log(`Set ${value} -> ${newValue}`) value = newValue trigger() } } }) } const count = MyCustomRef(0) function countUp() { count.value++ } </script> <template> <button @click="countUp">Up</button> {{count}} </template>
shallowReactive()
ref()
の浅いバージョンが shallowRef()
で、reactive()
の浅い版が shallowReactive()
です。
shallowReadonly()
readonly()
の浅いバージョンです。
toRaw()
reactive()
, readonly()
, shallowReactive()
, shallowReadonly()
で作成したオブジェクトからリアクティブ化前のオブジェクトを取り出します。
const count = reactive({count: 0})
console.log(toRaw(count)) // {count: 0}
markRaw()
オブジェクトに対してリアクティブ化されないようにマークします。巨大なデータを扱う際にリアクティブ化されないデータを制御するのに有効です。
const foo = markRaw({ foo: "foo" }) const baa = { baa: "baa" } const data = ref({ foo: foo, baa: baa }) console.log(isReactive(data.value.foo)) // false console.log(isReactive(data.value.baa)) // true
effectScope()
エフェクトスコープを作成することで、watch()
などのウォッチャーや computed()
などのリアクティブエフェクトをまとめて破棄することができます。詳細は RFC↗ を参照してください。
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count: ', doubled.value))
})
scope.stop() // まとめて破棄
getCurrentScope()
現在のアクティブなエフェクトスコープがあればそれを返します。
const scope = getCurrentScope()
onScopeDispose()
現在のアクティブなエフェクトスコープが破棄された時のコールバック関数を登録します。
scope.run(() => { watch(count, () => console.log(count.value)) watchEffect(() => console.log(count.value)) onScopeDispose(() => console.log("DISPOSED!")) })
ライフサイクルフック
onXxxxx()
下記のフック定義関数がサポートされています。詳細は「ライフサイクルフック(↗)」を参照してください。
- onBeforeMount() ... コンポーネントがマウントされる前。
- onMounted() ... コンポーネントがマウントされた後。
- onBeforeUnmount() ... コンポーネントがアンマウントされる前。
- onUnmounted() ... コンポーネントがアンマウントされた後。
- onBeforeUpdate() ... リアクティブ状態変更によりDOMツリーが変更される前。
- onUpdated() ... リアクティブ状態変更によりDOMツリーが変更された後。
- onRenderTriggered() ... レンダリングがトリガーされたとき。開発モードのみ。
- onRenderTracked() ... リアクティブな依存関係が追跡されたとき。開発モードのみ。
- onErrorCaptured() ... エラーがキャプチャされたとき。
- onActivated() ... コンポーネントが
<KeepAlive>
によりキャッシュツリーに挿入された後。 - onDeactivated() ... コンポーネントが
<KeepAlive>
によるキャッシュツリーから削除された後。 - onServerPrefetch() ... コンポーネントインスタンスがサーバーでレンダリングされる前。SSRモードのみ。
import { onUpdated } from 'vue' onUpdated(() => { console.log("onUpdated") })
依存関係のインジェクション
provide()
key-value を記憶します。記憶した情報は自コンポーネントや、子孫コンポーネントから inject() で参照することができます。
import { provide } from 'vue' provide("my-key", "my-value")
inject()
app.provide() や provide() で提供された key-value を参照します。provide() は自分自身または祖先コンポーネントが提供した値のみ取り出すことができます。
import { provide } from 'vue' provide("my-key", "my-value")
hasInjectionContext()
inject() を呼び出してよいコンテキストであるか否かを true/false で返します。例えば setup の外部などでは false を返します。ライブラリが inject() を呼び出してよいか判断する場合などに利用されます。
import { hasInjectionContext } from 'vue' if (hasInjectionContext()) { ... }
ヘルパー
useAttrs()
スロット の親コンポーネントから class
, id
, style
を含む属性を受け取ります。
-- Parent.vue -- <Child class="..." id="..." style="..." /> -- Child.vue -- import { useAttrs } from 'vue' const attrs = useAttrs() // { "class": "...", "id": "...", "style": { ... } }
useSlots()
スロット の親コンポーネントからスロット情報を受け取ります。スロット情報にスロット名のメソッドを呼び出すと、親コンポーネントの要素リストを得ることができます。
-- Parent.vue -- <Child v-slot:foo> <div>AAA</div> <div>BBB</div> </Child> -- Child.vue -- import { useSlots } from 'vue' const slots = useSlots() slots.foo().forEach((e) => { console.log(e.children) }) // AAA, BBB
useModel()
スロット の親コンポーネントからモデル情報を受け取ります。setup
をつけない <script>
で使用されます。
-- Parent.vue -- <Child :count=123 /> -- Child.vue -- <script> import { useModel } from 'vue' export default { props: ['count'], setup(props) { const msg = useModel(props, 'count') console.log(msg.value) // 123 } } </script>
useTemplateRef()
ref=name
を持つ要素の ref を受け取ります。下記の例では ref="myDiv"
を指定した要素の ref を受け取り、マウント時にそのスタイルを赤色にしています。
<script setup> import { useTemplateRef, onMounted } from 'vue' const myDiv = useTemplateRef("myDiv") onMounted(() => { myDiv.value.style.color = "red" }) </script> <template> <div ref="myDiv">Hello</div> </template>
useId()
DOM 要素の id
属性に使用できるような一意の ID を払い出します。デフォルトは "v-0"
, "v-1"
, "v-2"
, ... ですがプレフィックスは app.config.idPrefix() で変更することができます。
import { useId } from 'vue' console.log(useId()) // "v-0" console.log(useId()) // "v-1" console.log(useId()) // "v-2"
ビルトイン
コンポーネント
<Transition>
基本的なサンプル
v-if
、v-show
、<component>
による表示の切り替え、key
属性の変更のタイミングでトランジションをかけることができます。下記の例では show
により表示・非表示を切り替える際に 1秒 で透明度を 0~100% に変動させます。.v-enter-active
, .v-leave-active
には表示・非表示時の transition
を、.v-enter-from
, .v-enter-to
, .v-leave-from
, .v-leave-to
にはそれぞれ、表示の始め/終わり、非表示の始め/終わりにおける CSS プロパティを指定します。
<script setup> import { ref } from 'vue' const show = ref(true) </script> <template> <button @click="show = !show">Toggle</button> <Transition> <div v-if="show">HELLO!</div> </Transition> </template> <style scoped> .v-enter-active, .v-leave-active { transition: 1s; } .v-enter-from, .v-leave-to { opacity: 0; } </style>
名前付きトランジション
トランジションには名前を付けることができます。名前付きトランジションの場合 CSS クラス名の v が指定した名前に代わります。
<Transition name="foo">...</Transition> .foo-enter-active, .foo-leave-active { ... } .foo-enter-from, .foo-leave-to { ... }
アニメーション
.v-enter-active
, .v-leave-active
に animation を指定することもできます。
.v-enter-active { animation: bounce-in 0.5s } .v-leave-active { animation: bounce-in 0.5s reverse } @keyframes bounce-in { 0% { transform: scale(0) } 50% { transform: scale(1.5) } 100% { transform: scale(1) } }
カスタムトランジションクラス
.v-enter-active
などのCSSクラス名は enter-active-class
, leave-active-class
, enter-from-class
, enter-to-class
, leave-from-class
, leave-to-class
属性で変更することができます。これは、Animate.css で定義された animated__bounce
などのアニメーションを適用するのに役立ちます。
<Transition enter-active-class="animate__animated animate__bounce" leave-active-class="animate__animated animate__swing"> <div v-if="show">HELLO!</div> </Transition>
JavaScriptフック
トランジションの開始時や終了時にフック関数を呼び出すことができます。
<Transition @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter" @enter-cancelled="onEnterCancelled" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" @leave-cancelled="onLeaveCancelled" >...</Transition> function onBeforeEnter(elm) { ... }
トランジションの再利用
スロット と組み合わせて、トランジションを再利用可能なコンポーネントとして定義することができます。
<template> <Transition name="MyTransition"> <slot></slot> </Transition> </template> <style> .MyTransition-enter-active, .MyTransition-leave-active { transition: 1s } .MyTransition-enter-from, .MyTransition-leave-to { opacity: 0 } </style>
<script setup> import { ref } from 'vue' import MyTransition from './MyTransition.vue' const show = ref(true) </script> <template> <button @click="show = !show">Toggle</button> <MyTransition> <div v-if="show">HELLO!</div> </MyTransition> </template>
<TransitionGroup>
リストの各要素にトランジションを適用する場合は <Transition>
の代わりに <TransitionGroup>
を使用します。tag
属性に ul
などの親要素を指定します。各要素には :key
属性に一意となるキーを設定する必要があります。
<script setup> import { ref } from 'vue' const items = ref([1, 2, 3, 4, 5]) function add() { items.value.push(items.value.length + 1) } function del() { items.value.pop() } </script> <template> <button @click="add">Add</button> <button @click="del">Del</button> <TransitionGroup name="list" tag="ul"> <li v-for="item in items" :key="item">{{ item }}</li> </TransitionGroup> </template> <style scoped> .list-enter-active, .list-leave-active { transition: all 0.5s ease } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px) } </style>
<KeepAlive>
<component>
でコンポーネントを切り替える際などに、非アクティブとなったコンポーネントをキャッシュして、再表示の際の効率を上げることができます。
<KeepAlive> <component :is="activeComponent"></component> </KeepAlive>
<Teleport>
要素を to
で指定した CSS セレクタの子要素として表示します。下記を開発ツールの [Elements] で確認すると、div2 は div1 の子要素として配置されるのに対し、div3 は body 要素の子要素として配置されていることがわかります。モーダルダイアログを親要素の影響を受けずに body 直下に表示したい時などに有効です。
<div id="div1"> <div id="div2">FOO</div> <Teleport to="body"> <div id="div3">BAA</div> </Teleport> </div>
<Suspense>
fetch()
などを利用した非同期のコンポーネントがある場合、それらがすべてローでイング完了状態になるまでスピナーなどのフォールバックコンテンツを表示することができます。詳細は Suspense↗ を参照してください。
<Suspense>
<Dashbord /> // ネストされた非同期コンポーネントを含むコンポーネント
<template #fallback>
Loading...
</template>
</Suspense>
特別要素
<component>
<component>
は、:is
で指定したコンポーネントに切り替わります。コンポーネントをリアクティブ化する際には ref()
ではなく shallowRef()
を用います。
<script setup> import { shallowRef } from 'vue' import Foo from './Foo.vue' import Baa from './Baa.vue' const activeComponent = shallowRef(Foo) </script> <template> <button @click="comp = Foo">Foo</button> <button @click="comp = Baa">Baa</button> <component :is="activeComponent"></component> </template>
<slot>
スロットを定義します。詳細は スロット を参照してください。
<template>
HTML の DOM に変換されることのない疑似的な空要素です。複数の要素に v-if
や v-for
を適用する場合や v-slot
で名前付きスロットを定義したい場合に使用します。
<template v-if="show"> <div>...</div> <div>...</div> </template>
<template v-for="item is items"> <div>...</div> <div>...</div> </template>
<MyComponent> <template v-slot:header>Header</template> </MyComponent>
特別属性
key
リストの子要素をリアクティブに扱う際、Vue が子要素の変動を感知できるよう、子要素に一意のキーを設定します。
<ul> <li v-for="item in items" :key="item.id">...</li> </ul>
ref
useTemplateRef()
で参照する要素に指定します。
<div ref="myDiv">Hello</div>
is
<component>
でコンポーネントを指定する属性として使用します。
<component :is="activeComponent"></component>
また、通常の is
属性は HTML 標準における カスタム要素 を指定する属性ですが、値に vue:
をつけると Vue コンポーネントとみなされます。
<div is="vue:MyComponent"></div>
単一ファイルコンポーネント
マクロ関数
<script setup>
内部のみで使用可能なマクロ関数です。インポートは不要で <script setup>
を処理する際に自動的にコンパイルされます。
defineProps()
親コンポーネントから属性を受け取ります。
<Child foo="FOO" baa=123 />
-- script -- const props = defineProps(['foo', 'baa']) -- template -- {{ props }} // { "foo": "FOO", "baa": "123" } {{ foo }} // FOO {{ baa }} // 123
下記の様に型を指定することもできます。文字列以外を渡す場合は親コンポーネントで baa=123
を :baa=123
と記述する必要があります。型には String
, Number
, Boolean
, Array
, Object
, Date
, Function
, Symbol
, Error
を指定できます。
defineProps({ foo: String, baa: Number })
TypeScript の場合はジェネリクスで表現することもできます。
defineProps<{ foo: String, baa: Number }>()
デフォルト値や必須属性を指定することもできます。
defineProps({ foo: { type: String, default: "Unknown" }, baa: { type: Number, required: true } })
必須だけれども null 値を許可する場合は下記の様に記述します。
defineProps({ foo: { type: [String, null], required: true } })
defineEmits()
子コンポーネントから親コンポーネントに渡すイベントを定義します。
-- script -- function onMyEvent(e) { console.log(e) } // { message: "Hello!" } -- template -- <Child @my-event="onMyEvent" />
-- script -- const emit = defineEmits(["my-event"]) function onClick() { emit("my-event", { message: "Hello!" }) } -- template -- <button @click="onClick">Send</button>
defineModel()
親コンポーネントから v-model
を受け取ります。
-- script -- import { ref } from 'vue' import Child from './Child.vue' const message = ref("Hello!") -- template -- <Child v-model="message" /> <div>{{ message }}</div>
-- script -- const model = defineModel() -- template -- <input type="text" v-model="model" />
defineExpose()
子コンポーネントからメソッドや関数を親に公開します。下記の例では子コンポーネントから公開された countUp
メソッドや count
オブジェクトを親コンポーネントから参照しています。
-- script -- import { ref } from 'vue' import Child from './Child.vue' const child = ref() -- template -- <Child ref="child" /> <div>Parent</div> <div v-if="child">{{ child.count }}</div> <button @click="child.countUp">countUp</button>
-- script -- import { ref } from 'vue' const count = ref(0) function countUp() { count.value++ } defineExpose({ count, countUp }) -- template -- <div>Child</div> {{ count }} <button @click="countUp">Up</button>
defineOptions()
name
や inheritAttrs
などのオプションを設定します。
defineOptions({ inheritAttrs: false }) // 親から id, class, style などの暗黙属性を受け取らなくなる
defineSlots()
TypeScript を用いた開発で slot と props の型チェック情報を IDE に渡すために使用します。詳細は defineSlots()↗ を参照してください。
<script setup lang="ts"> const slots = defineSlots<{ default(props: { msg: string }): any }>() </script>
Options API
Vue 2 では Options API と呼ばれる書き方が主流でした。
<script> export default { data() { return { count: 0 } }, methods: { countUp() { this.count++ } } } </script> <template> <div> <div class="count">{{ count }}</div> <button @click="countUp">Up</button> </div> </template> <style> .count { font-size: 200% } </style>
Vue 3 になって Composition API と呼ばれる API 群がサポートされました。
<script> import { defineComponent, ref } from 'vue' export default defineComponent({ name: "App", setup() { const count = ref(0) function countUp() { count.value++ } return { count, countUp } } }) </script> <template> <div class="count">{{ count }}</div> <button @click="countUp">Up</button> </template> <style scoped> .count { font-size: 200% } </style>
Vue 3.2 以降では <script>
に setup
パラメータがサポートされ、次の様に記述できるようになりました。
<script setup> import { ref } from 'vue' const count = ref(0) function countUp() { count.value++ } </script> <template> <div class="count">{{ count }}</div> <button @click="countUp">Up</button> </template> <style scoped> .count { font-size: 200% } </style>
今後は setup 付きの SFC + Composition API が主流になっていくと思われます。本書では Options API の説明は省略しています。
スロット
スロットコンテンツとスロットアウトレット
slot は「差し込む」という意味を持ちます。親コンポーネントから子コンポーネントに HTML 要素を差し込むことができます。子コンポーネントで <slot></slot>
を記述すると、<slot></slot>
が親コンポーネントの <Child>~</Child>
の間に記述した要素に置換されます。ここで、<h1>Hello!</h1>
を スロットコンテンツ、<slot></slot>
を スロットアウトレット と呼びます。
-- App.vue -- <script setup> import Child from './Child.vue' </script> <template> <Child> <h1>Hello!</h1> </Child> </template> -- Child.vue -- <template> <slot></slot> ... <h1>Hello!</h1> に置換される </template>
フォールバックコンテンツ
<slot>~</slot>
の間には、親コンポーネントからスロットコンテンツが渡されなかった場合のデフォルトコンテンツを記述することができます。
-- Child.vue --
<slot>No data</slot>
名前付きスロット
親コンポーネントは v-bind
で名前をつけることにより、複数の名前付きスロットコンテンツを子コンポーネントに渡すことができます。子コンポーネントではこれを name
属性で受け取ります。v-bind
を指定しないコンテンツは default
という名前のコンテンツとみなされます。
-- App.vue -- <Child> <template v-slot:header>Header</template> <template v-slot:body>Body</template> <template v-slot:footer>Footer</template> </Child> -- Child.vue -- <slot name="header"></slot> <hr> <slot name="body"></slot> <hr> <slot name="footer"></slot>
v-bind:name
は省略して #name
と記述することができます。
<template #header>Header</template>
条件付きスロット
スロットコンテンツが存在する場合のみ表示したい場合は $slots.name
で判断します。
-- Child.vue --
<div v-if="$slots.footer">
<hr>
<slot name="footer" />
</div>
動的スロット名
スロット名も動的に割り当てることができます。[slotName] とすることでスロット名をリアクティブに指定することができます。
-- App.vue --
const cardName = ref("CardC")
<template #[cardName]>My Card</template>
スコープ付きスロット
下記の様にして、子コンポーネントから親コンポーネントに値を渡すことができます。(※ Vue のガイドには :text="..."
と書かれているのですが、:text="'...'"
のようにシングルクォートで囲まないと文字列をうまく渡すことができませんでした)
-- App.vue -- <Child v-slot="slotProps"> {{ slotProps.text }} {{ slotProps.count }} </Child> -- Child.vue -- <slot :text="'hello!'" :count="1"></slot>
サーバーサイドレンダリング(SSR)
Vue3 ではサーバーサイドレンダリング(SSR)もサポートしています。クライアント側 JavaScript がサーバー側でレンダリングされた HTML をイベントハンドリング付きでマッピングするハイドレーション(Hydration)と呼びます。サーバー側とクライアント側で同一のコードが動作するアイソモーフィック(Isomorphic)を実現しています。詳細は サーバーサイドレンダリング(SSR)↗ を参照してください。
mkdir ssr-test cd ssr-test npm init -y npm install vue npm install express
ES modules を使用するため package.json
に "type": "module" を加えます。
{ "name": "ssr-test", "type": "module", :
下記のファイルを作成します。サーバーにアクセスすると server.js が client.js を含む HTML を返します。client.js は app.js を読み込み、これを #app にマウントします。
import { createSSRApp } from 'vue'; export function createApp() { return createSSRApp({ data: () => ({ count: 1 }), template: `<button @click="count++">{{ count }}</button>`, }); }
import express from 'express'; import { renderToString } from 'vue/server-renderer'; import { createApp } from './app.js'; const server = express(); server.get('/', (req, res) => { const app = createApp(); renderToString(app).then((html) => { res.send(` <!DOCTYPE html> <html> <head> <title>Vue SSR Example</title> <script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module" src="/client.js"></script> </head> <body> <div id="app">${html}</div> </body> </html> `); }); }); server.use(express.static('.')); server.listen(8080, () => { console.log('ready'); });
import { createApp } from './app.js'; createApp().mount('#app');
サーバーを起動して http://{サーバアドレス}:8080 にアクセスしてみます。
node ./server.js
関連プロジェクトとエコシステム
Vue は下記などの関連プロジェクトやエコシステムと連携することができます。
Vite
Vue.js の開発者である尤雨溪が開発した開発用Webサーバーです。Vue.js や React に組み込まれています。Vite(ヴィート/ヴィット)はフランス語で素早いという意味を持ち、起動が早いのが特徴。TypeScript, JSX, SSR などもサポートしています。
Nuxt.js
React ベースのフレームワークとして Next.js があるように、Vue.js ベースのフレームワークとして Nuxt.js が開発されています。クライアント上の SPA インタフェースと、サーバーサイドの SSR やロジックをシームレスに結合することができます。
コマンドラインツール(Vue CLI, create-vue)
コマンドラインから Vue に関する操作を行うツールです。Vue CLI は現在はすでにメンテナンスモードに入っており、代わりに create-vue が推奨されています。コマンドラインから プロジェクトスキャフォールディング(create)、ビルド(build)、Vite起動などの Vue に関する操作を行うことができます。
npm create vue@latest npm install npm run dev npm run build
Vue Router
どの URL にアクセスしたらどのページを表示するといった、URL と表示ページ(コンポーネント)の関係を管理します。詳細は「Vue Router」を参照してください。
Vue Routerの基本的な使い方
Vue にルーティング機能を追加する Vue の公式ライブラリです。URL のパス名とコンポーネントのマッピングを提供します。Vue Router 4 から Vue 3 に対応しています。
npm install vue-router@4 npm list | grep vue-router vue-router@4.4.5
import { createApp } from 'vue' import { createRouter, createMemoryHistory } from 'vue-router' import App from './App.vue' import Home from './Home.vue' import Page from './Page.vue' const router = createRouter({ history: createMemoryHistory(), routes: [ { path: "/", component: Home }, { path: "/page", component: Page }, ] }) createApp(App) .use(router) .mount('#app')
<template> <Router-View /> </template>
<template> <h1>Home</h1> <Router-Link to="/page">Page</Router-Link> </template>
<template> <h1>Page</h1> <Router-Link to="/">Home</Router-Link> </template>
パラメータの渡し方
上記のプログラムに下記を追記します。
{ path: "/page/:page_id", component: Page },
<Router-Link to="/page/p123">Page</Router-Link>
-- script -- import { useRoute } from 'vue-router' const route = useRoute() console.log(route.params.page_id) -- template -- <div>{{ $route.params.page_id }}</div>
状態管理ライブラリ(Vuex, Pinia)
状態管理ライブラリを用いることでコンポーネント間でストア(状態変数の集合)を共有することができます。以前は Vuex が使用されていました。Vuex 3 と Vuex 4 のメンテナンスは継続されていますが、新機能の開発は停止しています。現在では代わりに Pinia への移行が薦められています。
npm install pinia npm list | grep pinia pinia@2.2.4
import { createPinia } from 'pinia' createApp() .use(createPinia()) .mount("#app")
import { defineStore } from 'pinia' export const MyStore = defineStore("MyStore", { state: () => ({ count: 0 }), actions: { increment() { this.count++ } }, })
-- script -- import { MyStore } from './MyStore' const mystore = MyStore() -- template -- <div>{{ mystore.count }}</div> <button @click="mystore.increment">Increment</button>
Vue DevTools
Vue のデバッグなどを支援する開発者支援ツールです。Viteプラグイン、スタンドアロンアプリ、Chrome拡張機能(ベータ版)などとして機能します。
VS Code拡張機能(Vetur, Vue - Official拡張機能)
Vue 開発のための VS Code 拡張機能です。かつでは Vetur が使用されていましたが Vue 2 にしか対応していません。Vue 3 では Volar を使用していましたが、Volar は現在では Vue - Official拡張機能 の Vue Language Features に統合されました。
- https://marketplace.visualstudio.com/items?itemName=Vue.volar↗
- https://volarjs.dev/↗
- https://vuejs.github.io/vetur/↗
Vuetify
Vue.js をベースとしたフレームワークです。デザインスキルが無くても美しい画面を簡単に作成することができます。多くの全体レイアウト(ワイヤフレーム)、メニュー、ボタン、ダイアログ、ページネーション、ツールバーなどの UI コンポーネントが提供されています。
npm install vuetify npm list | grep vuetify vuetify@3.7.3
import { createVuetify } from 'vuetify' import * as components from 'vuetify/components' import * as directives from 'vuetify/directives' createApp(App) .use(createVuetify({components, directives})) .mount('#app')
<script setup> const users = [ { name: "Yamada", age: 36 }, { name: "Suzuki", age: 48 }, { name: "Tanaka", age: 52 }, ] </script> <template> <v-container> <v-table> <thead> <tr><th>Name</th><th>Age</th></tr> </thead> <tbody> <tr v-for="user in users" :key="user.name"> <td>{{ user.name }}</td><td>{{ user.age }}</td> </tr> </tbody> </v-table> </v-container> </template>
BootstrapVue
Vue と Bootstrap を組み合わせるためのコンポーネント群です。ただし現時点(2024年10月)時点では Vue.js v2.6, Bootstrap v4.6 にしか対応していません。
テストフレームワーク(Cypress, Nightwatch, Playwright)
JavaScript の E2E(End-to-End) テストフレームワークです。Cypress, Nightwatch, Playwright などが利用されます。
その他
Vue 2 から Vue 3 への移行
Vue 2 と Vue 3 は一部互換性が無いものがあります。Vue 2 から Vue 3 への移行ガイドが下記で紹介されています。
TypeScript を使用する
create-vue でプロジェクトを作成する際に下記で Yes を回答すると TypeScript を使用することができます。ただし、Vite を使用する場合、TypeScript のトランスパイルは行いますが、型チェックは行いません。型チェックは VS Code などの IDE に任せ、その分、サーバーの起動/再起動時間を高速化しています。
npm create vue@latest : Add TypeScript? … No / Yes
SFC の <script>
に lang="ts"
を加えてください。
<script setup lang="ts"> :
複数アプリケーション
Vue アプリケーションを画面内に複数割り当てることも可能です。
createApp({...}).mount("#app-1") createApp({...}).mount("#app-2")
*.vue ファイルから app にアクセスする
コンポーネントから app にアクセスしたい場合 app.provide() と inject() を用います。
-- main.js -- app.provide("app", app) -- App.vue -- import { inject } from 'vue' const app = inject("app")
フォールスルー属性
id
, class
, style
, v-on
属性などは、親コンポーネントから子コンポーネントに暗黙的に渡すことができます。
---- App.vue ---- <Child class="big" style="color:red" /> ---- Child.vue ---- <template> <div>Hello!</div> </template>
リンク
- https://vuejs.org/
- https://ja.vuejs.org/
- https://vueframework.com/docs/v3/ja/
- https://v3.router.vuejs.org/
- https://v3.router.vuejs.org/ja/