學習資源
- 影片:
- 參考資料:
升級後的重大變革
新增功能
1. Composition API
- 相關邏輯可以集中
- 減少 this 指向的問題
- Vue3 自定義Hook
- 使用 Composition API 寫法取代舊版 mixins
- hook是一個函式
- 將程式碼拆解,能夠讓各個頁面引用
- 可以當作公用的函式
2. useCssVars
- 可以在組件加上自訂的 CSS 變數 var()
3. Teleport
- Teleport 可以將定義的內容轉移到目標元素。
4. Fragments
- 模板可以省略根元素,避免多出許多不必要的元素
5. Emits Component Option
- Vue 3 提供 emits 的聲明,用法就像 props 一樣。
6. Suspense & Error Boundary
- Suspense 是非同步載入的組件,在資料讀取完成以及渲染完成之前,會先顯示 #fallback 的內容。
- 可以不用特地加一個 loading 變數來處理加載資料時的判斷
7. ::v-slotted & ::v-global
棄用的功能
1. KeyCode
2. $on
, $off
& $once
- 移除
$on
,$off
&$once
也代表不能再使用EventBus
3. Inline Template Attribute
<!-- before -->
<my-comp inline-template :msg="parentMsg">
{{ msg }} {{ childState }}
</my-comp>
<!-- after -->
<my-comp v-slot="{ childState }">
{{ parentMsg }} {{ childState }}
</my-comp>
<!-- my-comp -->
<slot :childState="childState" />
4. Filters
5. $listeners
- 在 Vue3 中 $listeners 已經整合到 $attrs
Vue2:
<input type="text" v-bind="$attrs" v-on="$listeners" />
Vue3:
<input type="text" v-bind="$attrs" />
Composition API 簡介
Composition API 是什麼
Composition API 其實就是要取代 Vue2時,元件與元件間的程式碼與邏輯結構分散,難以重複使用的問題。 也可看作Function-based APIs,就是以函式作為邏輯的中心,將「該放一起的東西放在一起」。
Vue2 的 Options API 出了什麼問題?
原先,Vue2是將不同功能拆開來使用,但是隨著元件的增多。同一個邏輯操作的程式碼卻散佈在各個地方,不但不利於維護,也難以重複使用。
功能與邏輯的重複使用
-
Mixins 不再建議繼續使用
-
自定義指令 透過app.directive() 來建立一個客製化的指令,假設叫它'img-fallback'
當圖片載入錯誤時,用預設圖取代
//建立新元件 const app = vue.createApp({}); //在 app 註冊自定義指令 app.directive('img-fallback', { //mounted hook mounted(el){ //當el觸發error事件的時候,把src換成預設圖 el.add.EventListener('error',() => { el.src = 'default.png' }) } })
此時便可在所有想設定預設圖的
<img>
標籤加上v-img-fallback的自訂指令<div-id="app"> <img v-img-fallback src="noimage.png" </div>
此時,當瀏覽器試圖取得不存在的圖檔(noimage.png)時,就會觸發error事件,並且將圖檔置換成預設圖片。
除了 mounted鉤子外,還有其他的鉤子可以使用:
- created
- beforeMounted
- mounted
- beforeUpdate
- updated
- beforeUnmount
- unmomunted
這裡的鉤子函式是「指令」所使用的鉤子函式,與生命週期鉤子沒有直接關係。
- Composition API 將跨元件共用的屬性(如data、computed、methods等)包裝起來,然後再將要使用的元件引入進去。 與mixins最大的差別在於,被import到元件內的屬性,必須要以「物件」或「函式」的形式來將它們引入到元件中。
Vue3 自定義Hook
- 能夠將程式碼拆解,成為能夠引用的公用函式
範例程式碼:./mouse
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { x, y };
}
在頁面或組件中引用的方式
// 引用整個函式
import { useMousePosition } from "./mouse";
export default {
setup() {
// 定義並return 函式所return的東西即可
const { x, y } = useMousePosition();
return { x, y };
},
};
Composition API 核心
Compostion API 與 Options API 最大的差別:
- 元件的實體物件內不會再有 data、computed、methods 與生命週期 Hooks 等屬性 (禁止再將上述屬性放到實體物件中)
- 取代原先分散式的結構
- 解決以 this 取得屬性所產生的問題
- 以新的setup()函式取代
setup: 啟動元件的位置
- 增加可讀性
- 要將模板內會用到的部分 return 出去
setup(){
// 將程式邏輯定義在todoList()內
// 增加可讀性
const {todo, items, add , remove } = todoList();
// 將模板會用到的部分 return 出去
return {
todo,
items,
add,
remove
};
}
props 與 context
當setup()啟動時,會帶入兩個參數:props與context 由於已經不再使用 this 來存取屬性,所以可以透過 setup 函式提供的 props 物件來取得 props 的內容
- 取代原先以 this 存取屬性的 props
- context名稱可自訂
export default {
props {
defaultNum: {
type: Number,
defautl: 0
}
},
//注意要加上props
setup(props,context) {
// 透過prop物件取的對應的屬性
console.log(props.defaultNum);
}
}
context物件
- 提供了 attrs, slots, emit 三種屬性,分別對應到元件的實體物件上
- 可以透過 context.attrs、context.slots、context.emit 來使用
<template>
<div @click="clickInfo">
子组件 == {{name1}}, {{title1}}
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'child1',
props: {
name: String,
title: String
},
setup(props, context){
const name1 = reactive(props.name)
const title1 = reactive(props.title)
// 子组件点击事件
const clickInfo = ()=>{
// 抛出事件
context.emit('itemclick', {name: 'emitClick'})
}
return { name1, title1, clickInfo };
}
}
</script>
ref 與 reactive : 使值動態響應
在Compotision API 中,可以使用ref()來將數值進行包裝,並且回傳一個響應式的「物件」。 這個ref()函式:
- 可以包裝原始型別(primitive),也可以包裝物件或陣列。
- 必須後綴.value來讀取數值
在這個物件內,會提供一個value的屬性,可以透過這個value來更新或讀取狀態內的數值:
const count = ref(0);
console.log(count.value); //0
count.valut++;
console.log(count.value); //1
範例:count.vue
<template>
<h1>{{ count }}</h1>
<button @click="plus">Plus</button>
</template>
<script>
// 透過import 引入 ref
import { ref } from 'vue';
export default {
setup(){
const count = ref(0)
}
return {
// 透過 ref()包裝的數值可保有響應性
count,
plus,
// 純數值在模板中同樣可以渲染,但不會有響應性追蹤
nonReactiveCount: 0
}
}
</script>
ref 與 Dom 節點:替代$ref
由於setup()中已無this.$ref可用,要存取DOM中的元素,可利用先前提到的ref()。
<template>
<!-- 模板內要加上 ref 屬性 -->
<div ref="root"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup(){
// 這裡的 root 與 <div ref="root"> 配對
const root = ref(null);
//可以透過 root.value 取得實際的 DOM 元素
onMounted(()=>{
console.log(root.value);
})
return {
root,
}
}
}
</script>
v-for 與 ref 複數動態節點 : v-bind:ref
當 DOM 節點是透過 v-for 產生,同時又需要 ref 綁定模板時, 可以透過 v-bind:ref 綁定到某個陣列:
<div
v-for="(item, i) in list"
:ref=" el => { divs[i] = el }"
>
{{ item }}
</div>
如此便能指定divs[0]、divs[1]、divs[2]所對應的<div>
節點:
export default {
setup(){
const list = reactive([1,2,3]);
const divs = ref ([]);
//確保在每次更新前重置divs
onBeforeUpdate(()=> {
divs.value = [];;lll
})
return {
list,
divs,
}
}
}
reactive() : 包裝響應式物件的另一種函式
除了ref(),也可以使用reactive()來包裝響應式物件。 兩者最大的不同是:
- ref()可以包裝原始型別(primitive),也可以包裝物件或陣列。
- reactive內的參數只能是「物件」
- reactive()存取內部屬性時,不需後綴.value
<template>
<h1>{{ data.count }}</h1>
<button @click="data.plus">plus</button>
</template>
<script>
import { reactive } from "vue"
export default {
setup(){
//reactive包裝物件
const data = reactive({
count:0,
plus() => data.count++
});
return {
data
};
}
};
</scrpit>
- reactive()回傳一個被ES6 Proxy代理過的物件,才能做到響應式的更新。
toRefs 與 reactive:解決ES6解構語法造成的不響應問題
使用ES6的展開運算子「...」時,會將物件從響應式狀態抽離,變成普通物件。 因此需要改用 toRefs()
<template>
<h1>{{ data.count }}</h1>
<button @click="data.plus">plus</button>
</template>
<script>
import { toRefs } from "vue"
import counter from "./counter"
export default {
setup(){
//reactive包裝物件
const count = counter();
return {
...toRefs(count)
};
}
};
</scrpit>
computed
Composition API的computed,要改為函數式的寫法:
- 要後綴 .value 來讀取值
- computed 的參數為一個 getter 函式,並回傳一個 ref 物件
<template>
<h1>{{ data.count }}</h1>
<h1>{{ doubleCount }}</h1>
<h1>{{ quadrupleCount }}</h1>
<button @click="plus">
Plus
</button>
</template>
<script>
import { ref, computed } from "vue"
import counter from "./counter"
export default {
setup(){
const count = ref(0);
// computed 的參數為一個 getter 函式,並回傳一個 ref 物件
const doubleCount = computed(()=> count.value * 2);
//使用 computed回傳的內容要後綴.value
const quadrupleCount = computed(()=> doubleCount.value * 2);
const plus = () => count.value++;
return {
count,
doubleCount,
quadrupleCount,
plus
};
}
};
</scrpit>
computed 的 get 與 set
- computed 可以傳入 getter
- computed 可以傳入 get 與 set
const count = ref(0)
const plusOne = computed({
get:() => count.value + 1,
set:(val) => {
count.value = val - 1
}
})
readonly
將物件傳到readonly()中,會回傳一個被代理過的「唯獨」物件:
setup() {
const original = reactive({ count: 0 });
const copy = readonly(orignal);
const plus = () => original.count++;
return {
original,
copy,
plus,
}
}
執行plus()後,original.count 會從 0 變成 1 而透過readonly()包裝後回傳的copy物件,內部的.count 也會變成 1
methods
- Composition API 不再提供methods
- 直接將函式透過setup() return出去即可
watch & watchEffect
- 在 Composition API中,要改為函數式語法
- 可以觀察單一的 ref 物件
- 也可以觀察響應的 reactive 物件
觀察單一 ref 物件
watch(count, (val, oldVal) => {
console.log(`new count is $(val), preCount is $(oldVal)`)
})
觀察 reactive 物件
- 將傳入 watch()的參數count 改為 ()=>data.count
watch(
()=>data.count,
(val, oldVal) => { console.log(`new count is $(val), preCount is $(oldVal)`)}
)
透過陣列同時 watch 多個屬性
- callback 是共用的
watch(
[() => data.count,() => data.doubleCount],
([newCount, newDoubleCount], [PreCount, PreDoubleCound]) => {
console.log(`new count is $(newCount), prevCount is ${prevCount}` )
console.log(`new doubleCount is $(newDoubleCount), prevDoubleCount is ${prevDoubleCount}` )
}
)
對不同屬性更新想執行不同的動作
- 可以分別寫兩個 watch
觀察陣列或物件
- 需要在watch()中加入第三個參數, {deep: true}
watchEffect
- 當.value更新時,會呼叫callback
- 與 watch的差別:
- watchEffect會在 setup 剛建立時就執行一次
- 不需要像watch一樣指定觀察的目標
- 內部的callback函式對應數值更新後自動執行( 類似coumputed )
- watchEffect 無法取得更新前的數值
watchEffect(()=> {
console.log("watchEffect", count.value);
})
解除 watchEffect 觀察
- 個別的watchEffect() 被呼叫後會回傳一個屬於它的停止函式。
- 想要解除觀察時,可以呼叫該watchEffect() 所回傳的函式來停止觀察。
- 只能在現存元件中使用,如果元件被銷毀,provide 與 inject 的連結就會失效
const stop1 = watchEffect(() => {
console.log("watchEffect", count.value)
})
相依性注入 (Dependency Injection)
- Composition API 版的 provide 與 inject store.js
export {
todoList: symbol()
};
- 在提供狀態的元件加上provide(),並將內容指定到對應的物件上: provide.vue
//提供者元件
import { ref, provide } from "vue";
import store from "./store";
export defautl {
setup(){
const todoList = ref([]);
// 將 todoList 透過 provide 指定到 store.todoList
provide(store.todoList, todoList);
return {
todoList,
};
},
};
- 在取用的元件,透過inject取出: inject.vue
//取用者元件
import { ref, inject } from "vue";
import store from "./store";
export default {
setup(){
// 透過inject 取出 store.todoList
const todoList = inject (store.todoList);
return {
todoList,
}
}
}
Composition API 的生命週期鉤子
- 在 Composition API 中,生命鉤子改為函數式的語法:
//使用時先import
import { onMounted, onUpdated, onUnmounted } from 'vue';
const MyComponent = {
setup() {
onMounted(()=> {
console.log('mounted');
});
onUpdated(()=> {
console.log('updated!');
})
onUnmounted(()=> {
console.log('unmounted!')
})
}
}