此篇筆記是在 2021 年六月公司開新專案,導入 Vue 3 時所記錄下來的,主要參考 Kuro 大的 Vue 書籍,以及各式網路資源後所彙整。適合給過去曾寫過 Vue 2 或對 Vue 有基礎了解的人閱讀,且不會探討 Vue 底層核心運作模式。
筆記主要分成兩個章節,分別為與 Vue 2 差異,以及 Vue 3 新增的 Composition API。
與 Vue 2 差異
引入方式
使用 createApp
:
1 2 3 4 5 6 7 8 9 10 11 12
| import { createApp } from "vue"; import App from "./App.vue"; import router from "./router";
createApp(App).use(router).mount("#app");
const app = createApp(App); app.use(router); app.mount("#app");
|
Vue Router ( v4.x )
使用 creatRouter
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { createRouter, createWebHashHistory } from "vue-router"; import Home from "../views/Home.vue"; import About from "../views/About.vue";
const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, ];
const router = createRouter({ history: createWebHashHistory(), routes, })
|
VueX ( v4.x )
使用 creatStore
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { createStore } from 'vuex'
const store = createStore({ state () { return { count: 0 } }, mutations: { increment (state) { state.count++ } } })
|
不提供全域性註冊
1 2 3 4 5 6 7
| Vue.use(...); Vue.directive(...); Vue.component(...); Vue.mixins(...);
const vm = new Vue({ ... }).$mount('#app');
|
1 2 3 4 5 6 7
| const vm = Vue.createApp({ ... });
vm.use(...); vm.directive(...); vm.component(...); vm.mixins(...); vm.mount('#app');
|
新增語法
1. 子 component 中新增 emit
1 2 3 4 5 6 7 8 9 10
| app.component('the-button', { emits: ['update'], template: '<button @click="updateMessage">Click me</button>', methods: { updateMessage() { this.$emit('update'); }, }, });
|
除此之外,也可以在 emits
中增加 function,處理當 emit 傳回去父層的內容,常用於驗證或是非同步操作。詳細可參考 Vue 3 官網。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <template> <Custom :obj="obj" @changeName="handleName" /> </template>
script> import Custom from "@/components/Custom.vue"; import { reactive, ref } from "vue";
export default { name: "Home", components: { Custom, }, setup() { const obj = reactive({ name: "tom", age: 10, nickname: "apple", });
const handleName = (name) => { obj.nickname = name; };
return { title, obj, changeName, }; }, }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
<template> <div> <input v-model="nickname" /> <button @click="updateName(nickname)">update nickname</button> </div> </template>
<script> export default { name: "custom", emits: { changeName: (payload) => { if (!payload) { console.log("需填寫欄位內容"); return; } return true; }, }, setup(_, { emit }) { const updateName = (nickname) => { emit("changeName", nickname); };
let nickname = ref("");
return { nickname, props, updateName, }; }, }; </script>
|
2. teleport
透過 teleport
將類似 modal 視窗的 component 移動到外層的 DOM 節點上
視窗在有序 DOM Tree 中,應該是跟常用的 APP 平行,而不是被巢狀包裹在各個標籤中。
但如果透過 teleport
可以將該 component 移動到指定的父層節點下。詳情可參考 官網。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <teleport to="body"> <div v-if="modalOpen" class="modal"> <div> I'm a teleported modal! (My parent is "body") <button @click="modalOpen = false"> Close </button> </div> </div> </teleport> </template>
|
3. fragment
template 再也不需要根節點
在 Vue2 中,每個 template 都必需要有個根節點,但因為 Vue3 內部會將節點放進 fragment 虛擬元素中,因此不需要根節點也可以成功建立 template。
1 2 3 4
| <template> <h2>aaaa</h2> <h2>aaaa</h2> </template>
|
當子 component 沒有根節點包住的時候,在父層元素引入 component 時,就要特別避免以下使用:
更改語法
1. v-enter / v-leave
vue 中有一些封裝好的 component 可使用,其中 transition 的 v-enter
改成 v-enter-from
,v-leave
改成 v-leave-from
。
1 2 3 4 5 6 7 8 9
| .v-enter-from, .v-leave-to { opacity: 0; }
.v-leave-from, .v-enter-to { opacity: 1; }
|
2. data 必須使用函式並回傳
1 2 3 4 5
| data() { return { message: 'This works in Vue 2!' }; },
|
3. lifecycle hooks
推薦直接參考 Kuro 大的 文章
4. 選取 DOM 元素
Vue 2 會透過 ref
,以及 this.$refs.x
存取 DOM 元素。
在 Vue 3 中因為有 composition API 的 ref
,因此有一點不一樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <h5 ref="dom">被選取的 DOM 元素</h5> </template>
<script> import { ref } from "@vue/reactivity";
export default { name: "custom", setup() { const dom = ref(null); return { dom, }; }, }; </script>
|
移除語法
1. filters
原先的 option API 中有 filters 的語法,但因為與 methods 的重複性很高,在 Vue3 就被移除了
2. $on、$off、$once
因為 Vue3 不推薦使用 eventbus,因此移除 eventbus 中的 $on、$off、$once。
其他特性
- 支援 JSX
- 支援 TypeScript
注意事項
composition API
引入方式:
1
| import { ref, reactive, watch, computed } from 'vue';
|
setup
特性:
setup
相當於 beforecreate
跟 created
這兩個 lifecycle hooks。
setup
接受兩個參數,第一個是 props,第二個是 context,context 是一個 object,包含 attrs, slots, emit。因此也可以對 context 進行解構。
- 當今天不需要 props 只需要 emit 時,可以將第一個參數變成
_
,vue 會自動忽略第一個參數。
- 如果今天有傳 props 時,推薦在
setup()
中把 props 當參數引入再 return 出去,這樣在 template 中引用時就可以透過 props.x
操作。未來在看 template 時可以清楚知道哪些是 props
哪些是 state
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| setup(props, context) { console.log(context.attrs); console.log(context.slots);
return { props, } }
setup(props, { emit }) {}
setup(_, { emit }) {}
|
以下講解的 ref
、reactive
、watch
、watchEffect
、computed
都會放在 setup
裡面。
ref
資料綁定。ref
習慣會綁定的資料是原始型別,綁定後要存取資料時會透過 .value
進行存取。
reactive
資料綁定。reactive
習慣會綁定的資料是物件型別,綁定後 不須 透過 .value
進行存取。
watch
1
| watch(監聽資料, callback fn(newValue, oldValue));
|
資料監聽。第一個參數是要監聽的資料,第二個參數是 callback function。
特別注意的是:
- 如果要監聽的是
ref
的值,可直接將值放入。如果是要監聽 reactive
中某個 value,需要透過 function return reactive 的值回去。
- 第二個參數的 callback function 中,第一個參數是改動後的新值,第二個參數是舊值。
- 一次監聽多個值時,第一個參數改成 array,並放入要監控的值。
範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| setup() { const text = ref(''); const goals = reactive({ a: 1, b: 2, });
watch(text, (newValue, oldValue) => { console.log(newValue, oldValue) }) watch(goals, (newValue, oldValue) => { console.log(newValue, oldValue) })
watch(() => goals.a, (newValue, oldValue) => { console.log(newValue, oldValue) })
watch([text, () => goals.a], (newValue, oldValue) => { console.log(newValue, oldValue) })
return { text, goals, } }
|
watchEffect
實際上很少用到,跟前面提到的 watch()
有點像,不同的是不需要加入參數,而是直接監聽 watchEffect()
中有存取到的值,有更新時會執行當中的扣。
一般值在初始化的時候會執行一次,更新時會再執行一次。
1 2
| watchEffect(() => console.log(text))
|
computed
跟原本的 vue 2 概念相同。
- 傳入一個 function 的時候表示會回傳一個 readonly 的
ref
值。
1
| const computedValue = computed(fn);
|
- 傳入一個 object 的時候,可設定 getter 跟 setter。
1 2 3 4 5 6 7 8
| const computedValue = computed({ get() { ... }, set() { ... }, })
|
範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| setup() { const goals = reactive([]);
const computedValu = computed(() => { return goals.filter( (goal) => !goal.text.includes("Angular") && !goal.text.includes("React") ); }); onMounted(() => console.log(computedValue.value));
return { goals, computedValue, } }
|
custom hooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import { ref, onMounted, onUnmounted } from "vue";
export default function () { let x = ref(0); let y = ref(0);
const handleMouse = (e) => { x.value = e.pageX; y.value = e.pageY; };
onMounted(() => { window.addEventListener("mousemove", handleMouse); });
onUnmounted(() => { window.removeEventListener("mousemove", handleMouse); });
return { x, y, }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div> <div>x: {{ x }}</div> <div>y: {{ y }}</div> </div> </template>
<script> import useMouse from "@/hooks/useMouse";
export default { name: "custom", setup() { const { x, y } = useMouse(); return { x, y, }; }, }; </script>
|
toRefs
這邊要特別注意,以上的範例是使用 ref
,但是當如果使用 reactive
的話,狀況會有點不太一樣,會需要多使用 toRefs
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
import { reactive, toRefs, onMounted, onUnmounted } from "vue";
export default function () { let location = reactive({ x: 0, y: 0, });
const handleMouse = (e) => { location.x = e.pageX; location.y = e.pageY; };
onMounted(() => { window.addEventListener("mousemove", handleMouse); });
onUnmounted(() => { window.removeEventListener("mousemove", handleMouse); });
return { ...toRefs(location), }; }
|
接收 toRefs(xxx)
的值時,記得也要透過 .value
存取。
改寫 VueX
使用 Composition API 後,因為資料有共通性,因此有些人推薦以 Composition API 來取代繁雜的 VueX。
Kuro 大 就曾經示範。在 Vite-demo 分支中,是使用 Composition API 共享資料,在 master 分支則是採用原 VueX 寫法。
評論