Vue3 の一般的な API#
reactive# Vue ガイド#
データ属性 Data#
コンポーネントの data
オプションは関数です。Vue は新しいコンポーネントインスタンスを作成する過程でこの関数を呼び出します。この関数はオブジェクトを返す必要があり、その後 Vue は応答性システムを介してそれをラップし、コンポーネントインスタンス内で $data
の形式で保存します。便宜上、そのオブジェクトの任意のトップレベルプロパティもコンポーネントインスタンスを通じて直接公開されます:
- 本質的に、コンポーネントの
data
オプションは単なる関数です - この関数は Vue がコンポーネントインスタンスを作成する際に呼び出されます
- この関数はオブジェクトを返します
- 返されたこのオブジェクトは Vue によって応答性データとしてラップされます
- ラップされた応答性データは、コンポーネントインスタンス内で
$data
の形式で保存されます - 便宜上、
data
が返すオブジェクトの任意のトップレベルプロパティは、コンポーネントインスタンスを通じて直接取得できます。ただし、それらがすべてdata
関数が返すオブジェクト内にあることを確認する必要があります - コンポーネントインスタンスに新しいプロパティを直接追加すると、成功することはできますが、応答性オブジェクト
$data
に含まれていないため、そのプロパティは応答性を持ちません data
が返すオブジェクトのプロパティでは、$
および_
で始まる名前の使用を避けるべきです
原理分析#
なぜ
data
属性はオブジェクトではなく関数なのか?
- 結論:
data
は関数としてもオブジェクトとしても定義できますが、ルートコンポーネントのインスタンス化時にはオブジェクトまたは関数であり、コンポーネントのインスタンス化は関数のみです - 原理:ルートインスタンスはシングルトンであり、データ汚染を引き起こしません;コンポーネントインスタンスはシングルトンではなく、複数のコンポーネントインスタンス間で同じ
data
を共有することによるデータ汚染を防ぐために、関数形式を採用し、data
を初期化する際にはそれをファクトリ関数として扱い、常に新しいdata
オブジェクトを返します
計算プロパティ Computed#
アプリケーションのシナリオ:
- テンプレート内で長い式はテンプレートを複雑にし、メンテナンスが難しくなります。この問題を解決するために
computed
が生まれました - キャッシュ効果があり、パフォーマンスを向上させます。依存データが変化しない場合、キャッシュされたデータが呼び出されます
応答性データを含む 複雑なロジック には、計算プロパティ を使用するべきです
計算プロパティと関数メソッド#
計算プロパティは、それらの反応依存関係に基づいてキャッシュされます。計算プロパティは、関連する応答性依存関係が変更されたときにのみ再評価されます。
対照的に、再レンダリングがトリガーされるたびに、メソッドを呼び出すと関数が常に再実行されます。
なぜキャッシュが必要なのか?
性能コストが高い計算プロパティ list
があり、それは巨大な配列を遍歴し、大量の計算を行う必要があると仮定します。その後、他の計算プロパティが list
に依存している可能性があります。キャッシュがなければ、list
の getter
を何度も実行することになります!キャッシュを望まない場合は、method
を代わりに使用してください。
リスナー Watch#
Options API による Watch の実装#
通常の watch#
export default {
props:['dataList'],
data(){
return {
sourceData:[],
total:0
}
},
// propsのdataListが変化したとき、dataの値も再計算されて代入されます
watch:{
dataList(newVal,oldVal){
this.total = newVal.length
this.sourceData = newVal
}
}
}
オブジェクトの watch#
export default {
data(){
return {
firstName:'Li',
lastName:'Huan'
}
},
// propsのdataListが変化したとき、dataの値も再計算されて代入されます
watch:{
dataList:{
// getter
get:function(){
return this.firstName + this.lastName
},
set:function(val){
// this.dataList = 'Wang Feng'
const [ firstName,lastName ] = val.split(' ')
this.firstName = firstName
this.lastName = lastName
},
{
// オブジェクトと配列は参照型であり、参照型変数はアドレスを保持します。アドレスが変わらないため
// watchはトリガーされません。この場合、深い監視を行う必要があり、属性 deep: true を追加する必要があります
deep:true,
// watchには特徴があり、値が最初にバインドされたときにはリスナ関数は実行されず、
// 値が変化したときにのみ実行されます。最初にバインドされた値のときにも関数を実行したい場合は、immediate属性が必要です
immediate:true
}
}
}
}
Reactive API による watch の実装#
watch 関数は特定のデータソースを監視し、コールバック関数内で副作用を実行します。デフォルトでは 遅延 であり、つまり監視しているソースデータが変更されたときにのみコールバックが実行されます。
watch(source, callback, [options]);
パラメータの説明:
source
:監視する応答性変数を指定するために、String、Object、Function、および Array をサポートしますcallback
:実行されるコールバック関数options
:deep
、immediate
、およびflush
オプションをサポートします
reactive を監視する#
import { defineComponent,reactive,watch } from 'vue'
export default defineComponent({
setup(){
const state = reactive({
firstName:'Li',
lastName:'Wang'
})
// firstNameを変更するとwatchのコールバック関数が自動的にトリガーされます
watch(
()=>state.firstName,
(newVal,oldVal)=>{
console.log(`新しい値:${newVal}`,`古い値:${oldVal}`)
},
{deep:true})
}
})
複数のデータを監視する#
const stop = watch([() => state.firstName, () => state.lastName], ([curAge, newVal], [preAge, oldVal]) => {
console.log('新しい値:', curAge, '古い値:', preAge);
console.log('新しい値:', newVal, '古い値:', oldVal);
});
unmounted(()=>{
// 監視を停止
stop()
})
watchEffect#
watchEffect
は watch
のように依存関係を事前に渡す必要がなく、watchEffect
は自動的に依存関係を収集します。コールバック関数を指定するだけです。コンポーネントの初期化時に、依存関係を収集するために一度実行され、その後収集された依存関係のデータが変化すると、再度コールバック関数が実行されます。
watchEffect(()=>{
// 自動的に依存関係を収集し、初期化時に一度実行
console.log(firstName);
console.log(lastName)
})
スタイル設定#
オブジェクト構文#
<template>
<div class='static' :class="{active:isActive,danger:hasError}"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
}
}
</script>
<!-- レンダリング後 -->
<div class="static active danger"></div>
オブジェクトを直接渡す#
<template>
<div class='static' :class="classObj"></div>
</template>
<script>
export default {
data(){
return {
classObj:{
active:true,
hasError:true
}
}
}
}
</script>
computed を渡す#
<template>
<div class="static" :class="clsObject"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
},
computed:{
classObj(){
return {
active:this.isActive,
hasError:this.hasError
}
}
}
}
</script>
条件レンダリング#
<template>
<div v-if='show'></div>
<div v-else></div>
<div v-show='show'></div>
</template>
<script>
export default {
data(){
return {
show:true
}
}
}
</script>
v-if
vs v-show
:
v-show
は単に要素のcss
プロパティを変更するだけで、要素のdisplay
をnone
に設定しますv-if
は要素をコンポーネントツリーから削除します。要素がコンポーネントである場合、show
の値を変更すると、コンポーネント要素のライフサイクル関数がトリガーされます
内蔵コンポーネント#
component#
動的コンポーネント
// 動的コンポーネント
<template>
<!-- currentTabが変わるとコンポーネントも変わる -->
<component :is="tabs[currentTab]"></component>
</template>
上記の例では、:is
に渡される値は以下のいずれかです:
- 登録されたコンポーネント名
- インポートされたコンポーネントオブジェクト
transition#
アニメーションコンポーネント
<!-- テンプレート -->
<template>
<Transition name="slide-fade">
</Transition>
</template>
/*
入場と退出のアニメーションは異なる
継続時間と速度曲線を使用できます。
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
- トランジションフック関数
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
</Transition>
</template>
// 要素がDOMに挿入される前に呼び出されます
// これを使用して要素の "enter-from" 状態を設定します
function onBeforeEnter(el) {}
// 要素がDOMに挿入された後の次のフレームで呼び出されます
// これを使用して入場アニメーションを開始します
function onEnter(el, done) {
// コールバック関数doneを呼び出すことでトランジションが終了したことを示します
// CSSと組み合わせて使用する場合、このコールバックはオプションの引数です
done()
}
// 入場トランジションが完了したときに呼び出されます。
function onAfterEnter(el) {}
function onEnterCancelled(el) {}
// leaveフックの前に呼び出されます
// 大多数の場合、leaveフックだけを使用します
function onBeforeLeave(el) {}
// 退出トランジションが開始すると呼び出されます
// これを使用して退出アニメーションを開始します
function onLeave(el, done) {
// コールバック関数doneを呼び出すことでトランジションが終了したことを示します
// CSSと組み合わせて使用する場合、このコールバックはオプションの引数です
done()
}
// 退出トランジションが完了し、
// 要素がDOMから削除されたときに呼び出されます
function onAfterLeave(el) {}
// v-showトランジションでのみ使用可能
function onLeaveCancelled(el) {}
keep-alive#
コンポーネントの状態をキャッシュし、コンポーネントの再レンダリングを避けるために使用されます
keep-alive
には以下の 3 つの属性があります
include
:文字列または正規表現で、名前が一致するコンポーネントのみがキャッシュされます。コンポーネントの name;exclude
:文字列または正規表現で、名前が一致するコンポーネントはキャッシュされません;max
:数字で、最大でいくつのコンポーネントインスタンスをキャッシュできるかを示します。
slot#
デフォルトスロット
<template>
<!-- 親コンポーネント -->
<Child>
<div>デフォルトスロット</div>
</Child>
<!-- 子コンポーネント -->
<template>
<slot>
<p>スロットのバックアップコンテンツ</p>
</slot>
</template>
</template>
名前付きスロット#
<!-- コンポーネント内部 -->
<div class="container">
<header>
<!-- ヘッダーをここに置きたい -->
</header>
<main>
<!-- メインコンテンツをここに置きたい -->
</main>
<footer>
<!-- フッターをここに置きたい -->
</footer>
</div>
<!-- コンポーネント内部 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
スコープ付きスロット#
親コンポーネントが子コンポーネント内のスロットコンテンツをカスタマイズする際に、子コンポーネント内のデータを取得する必要があります
<template>
<!-- Child -->
<ul>
<li v-for="(item, index) in items">
<slot :item="item"></slot>
</li>
</ul>
<!-- Parent -->
<todo-list>
<template #default="{slotProps}">
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
</template>
Teleport#
teleport は Vue3.x で新たに導入された機能で、HTML が DOM のどの親ノードの下にレンダリングされるかを制御するクリーンな方法を提供します
<template>
<Teleport to="body">
<div> bodyにテレポート </div>
</Teleport>
</template>
import { reactive } from 'vue';
export default {
// `setup` はコンポジションAPI専用の特別なフックです
setup() {
const state = reactive({ count: 0 }); // stateをテンプレートに公開
return { state };
},
};
ref#
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
computed#
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 計算プロパティ ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'はい' : 'いいえ'
})
</script>
<template>
<p>出版された本がありますか:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
watch#
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('質問には通常、疑問符が含まれます。;-)')
// refを直接監視できます
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = '考え中...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'エラー!APIに到達できませんでした。 ' + error
}
}
})
</script>
<template>
<p>
はい/いいえの質問をしてください:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
watchEffect#
watchEffect () はコールバック関数を即座に一度実行します。この時、関数が副作用を生じた場合、
Vue は自動的に副作用の依存関係を追跡し、応答ソースを自動的に分析します。
watchEffect(async () => {
const response = await fetch(url.value);
data.value = await response.json();
});
watch vs. watchEffect#
watch
とwatchEffect
はどちらも応答性のある副作用のあるコールバックを実行できます。主な違いは、応答性の依存関係を追跡する方法です:watch
は明示的に監視されているソースのみを追跡します。コールバック内でアクセスされるものは追跡しません。また、応答ソースが実際に変更された場合にのみコールバックがトリガーされます。watch
は副作用が発生したときに依存関係を追跡しないため、コールバック関数のトリガータイミングをより正確に制御できます。
一方、watchEffect
は副作用が発生している間に依存関係を追跡します。同期実行中に、アクセス可能なすべての応答性プロパティを自動的に追跡します。これは便利で、コードがしばしばより簡潔になりますが、その応答性の依存関係はそれほど明確ではありません。
状態管理 pinia#
store#
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 };
},
// これは次のように定義することもできます
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
},
getters(){
finishedTodos(store){
return store.count++
}
}
});
action#
import { useCounterStore } from '@/stores/counter';
export default {
setup() {
const counter = useCounterStore();
counter.count++;
// 自動補完付き ✨
counter.$patch({ count: counter.count + 1 });
// またはアクションを使用する代わりに
counter.increment();
},
};
vue-router ルーティング#
router.js
const Home = { template: '<div>ホーム</div>' };
const About = { template: '<div>アバウト</div>' };
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
});
const app = Vue.createApp({});
app.use(router);
app.mount('#app');
レンダリング
<template>
<router-view />
</template>
setup 構文#
defineProps defineEmits#
Ts 構文サポート
const props = defineProps<{
foo: string
bar?: number
}>()
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
defineExpose#
setup コンポーネントはデフォルトでオフになっています
<script setup>
import {ref} from 'vue' const a = 1 const b = ref(2) defineExpose({(a, b)})
</script>