Vue 3.0 於 2020-09-18 釋出了,使用了 Typescript 進行了大規模的重構,帶來了 Composition API RFC 版本,類似 React Hook 一樣的寫 Vue,可以自定義自己的 hook ,讓使用者更加的靈活。
為什麼推出3.0?
- 對 TypeScript 支援不友好(所有屬性都放在了 this 物件上,難以推倒元件的資料型別)
- 大量的 API 掛載在 Vue 物件的原型上,難以實現 TreeShaking
- TreeShaking:當我引入一個模組的時候,我不引入這個模組的所有程式碼,我只引入我需要的程式碼
- 架構層面對跨平臺 dom 渲染開發支援不友好
- CompositionAPI。受 ReactHook 啟發
- 更方便的支援了 jsx
- Vue 3 的 Template 支援多個根標籤,Vue 2 不支援
- 對虛擬 DOM 進行了重寫、對模板的編譯進行了優化操作
一、setup
為我們使用 vue3 的 Composition API 新特性提供了統一的入口, 有些文章說setup 函式會在 beforeCreate 之後、created 之前執行但實踐證明這個說法不正確如下圖列印順序, vue3 也是取消了這兩個鉤子,統一用 setup 代替, 該函式相當於一個生命週期函式,vue 中過去的 data,methods,watch 等全部都用對應的新增 api 寫在 setup()函式中。
1、setup引數
- props: 元件傳入的屬性
- context:attrs,emit,slots
props
setup中接受的props
是響應式的, 當傳入新的props 時,會及時被更新。由於是響應式的, 所以不可以使用ES6解構,解構會消除它的響應式。需要結構可以用toRefs()
context
setup
中不能訪問Vue2中最常用的this
物件,所以context
中就提供了this
中最常用的三個屬性:attrs
、slot
和emit
,分別對應Vue2.x中的 $attr
屬性、slot
插槽 和$emit
發射事件。
簡單用法
2、reactive、ref與toRefs
在vue2.x中, 定義資料都是在data
中, 但是Vue3.x 可以使用reactive
和ref
來進行資料定義。
區別
reactive
用於處理物件的雙向繫結,ref
則處理js基礎型別的雙向繫結,reactive
不能代理基本型別,例如字串、數字、boolean等。
簡單用法
<template> <div> <span>{{ year }}</span ><span>{{ user.name }}</span ><span>{{ user.label }}</span> </div> </template> <script> import { reactive, ref } from "vue"; export default { props: { data: { type: Object, default: { id: 1, name: "匹配", }, }, }, components: {}, setup(props, con) { const year = ref(10); const user = reactive({ name: "夏利", label: "", });
//這裡ref取值需要加value if (year.value > 5) { user.label = "牛逼"; } else { user.label = "一般"; } return { year, user, }; }, }; </script>
不能直接對user
進行結構, 這樣會消除它的響應式, 這裡就和上面我們說props
不能使用ES6直接解構就呼應上了。那我們就想使用解構後的資料怎麼辦,解決辦法就是使用toRefs
。
return { year, // 使用reRefs ...toRefs(user) }
3、生命週期
我們可以看到beforeCreate
和created
被setup
替換了(但是Vue3中你仍然可以使用, 因為Vue3是向下相容的, 也就是你實際使用的是vue2的)。其次,鉤子命名都增加了on
; Vue3.x還新增用於除錯的鉤子函式onRenderTriggered
和onRenderTricked
<script> import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered, } from "vue"; export default defineComponent({ // beforeCreate和created是vue2的 beforeCreate() { console.log("------beforeCreate-----"); }, created() { console.log("------created-----"); }, setup() { console.log("------setup-----"); // vue3.x生命週期寫在setup中 onBeforeMount(() => { console.log("------onBeforeMount-----"); }); onMounted(() => { console.log("------onMounted-----"); }); // 除錯哪些資料發生了變化 onRenderTriggered((event) => { console.log("------onRenderTriggered-----", event); }); }, }); </script>
4、全域性定義屬性 globalProperties
在專案中往往會全域性定義公共屬性或方法,方便我們元件間呼叫。
import { createApp } from 'vue' import App from './App.vue' import utils from './utils' // vue2.0寫法 let vue=new Vue() vue.prototype.utils=utils // vue3.0寫法 const app=createApp(App) app.config.globalProperties.utils=utils
app.mount('#app')
vue3.0中使用全域性定義的屬性
<script>
import { getCurrentInstance } from "vue";
export default { components: {}, setup(props, con) {
const { ctx } = getCurrentInstance(); console.log(ctx.utils);
}, }; </script>
5、use與plugin
定義一個元件
通過use引入
6、readonly 只讀屬性
在使用readonly後重新進行復制是不允許修改的,這個api不常用
7、computed計算api
這裡引入了proxy內的類似寫法,有了set與get的內容寫法,該函式用來創造計算屬性,和過去一樣,它返回的值是一個 ref 物件。裡面可以傳方法,或者一個物件,物件中包含 set()、get()方法。
7.1 建立只讀的計算屬性
<template> <div> <p>refCount: {{refCount}}</p> <p>計算屬性的值computedCount : {{computedCount}}</p> <button @click="refCount++">refCount + 1</button> </div> </template> <script> import { computed, ref } from '@vue/composition-api' export default { setup() { const refCount = ref(1) // 只讀 let computedCount = computed(() => refCount.value + 1) //2 console.log(computedCount) return { refCount, computedCount } } }; </script>
7.2 通過 set()、get()方法建立一個可讀可寫的計算屬性
<template> <div> <p>refCount: {{refCount}}</p> <p>計算屬性的值computedCount : {{computedCount}}</p> <button @click="refCount++">refCount + 1</button> </div> </template> <script> import { computed, ref } from '@vue/composition-api' export default { setup() { const refCount = ref(1) // 可讀可寫 let computedCount = computed({ // 取值函式 get: () => refCount.value + 1, // 賦值函式 set: val => { refCount.value = refCount.value -5 } })
//觸發get函式 console.log(computedCount.value)
// 為計算屬性賦值的操作,會觸發 set 函式 computedCount.value = 10 console.log(computedCount.value) // 觸發 set 函式後,count 的值會被更新 console.log(refCount.value)
return { refCount, computedCount } } }; </script>
8、watch與watchEffect
8.1watch
watch 函式用來偵聽特定的資料來源,並在回撥函式中執行副作用。預設情況是惰性的,也就是說僅在偵聽的源資料變更時才執行回撥。
<script> import { ref, watch } from "vue"; export default { components: {}, setup(props, con) { const count = ref(0); const name = ref("iuuggu"); setTimeout(() => { (count.value = 1), (name.value = "這一行山"); }, 200); watch( () => { /* * 1、需要一個明確的資料資源 * 2、需要有一個回撥函式 * 3、等到改變才會執行函式 * */ return count.value; }, () => {} ); // 4、ref簡寫 ,reactive不可以 watch(count, () => {}); // 5、監聽多個 watch( () => { return [count, name]; }, ([newv, oldv], [newv, oldv]) => {} ); // 6、onTrack,onTrigger watch( () => { return [count, name]; }, ([newv, oldv], [newv, oldv]) => {}, { onTrack(e) {}, onTrigger(e) {}, } ); }, }; </script>
8.1.1 監聽資料來源
reactive
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) watch( () => state.age, (age, preAge) => { console.log(age); // 100 console.log(preAge); // 10 } ) // 修改age 時會觸發watch 的回撥, 列印變更前後的值 state.age = 100 return { ...toRefs(state) } }
ref
setup(props, context) { const age = ref<number>(10); watch(age, () => console.log(age.value)); // 100 // 修改age 時會觸發watch 的回撥, 列印變更後的值 age.value = 100 return { age } }
8.1.2監聽多個
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) watch( [
() => state.age,
() => state.name
], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 時會觸發watch 的回撥, 列印變更前後的值, 此時需要注意, 更改其中一個值, 都會執行watch的回撥 state.age = 100 state.name = 'vue3' return { ...toRefs(state) } }
8.1.3stop 停止監聽
在 setup() 函式內建立的 watch 監視,會在當前元件被銷燬的時候自動停止。如果想要明確地停止某個監視,可以呼叫 watch() 函式的返回值即可,語法如下:
setup(props, context) { const state = reactive<Person>({ name: 'vue', age: 10 }) const stop = watch( [() => state.age, () => state.name], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 時會觸發watch 的回撥, 列印變更前後的值, 此時需要注意, 更改其中一個值, 都會執行watch的回撥 state.age = 100 state.name = 'vue3' setTimeout(()=> { stop() // 此時修改時, 不會觸發watch 回撥 state.age = 1000 state.name = 'vue3-' }, 1000) // 1秒之後講取消watch的監聽 return { ...toRefs(state) } }
8.2 watchEffect
該函式有點像update函式,但他執行在update與beforeuodate之前。
* 1、首次載入會立即執行
* 2、響應的最終所有依賴監聽變化(資料改變)
* 3、在解除安裝onUnmounte時自動停止
* 4、執行stop就會停止監聽,否則一直監聽
* 5、非同步函式先執行再去監聽改變
<script> import { watchEffect, ref, onMounted } from "vue"; export default { components: {}, //con==context(attrs,emit,slots) setup(props, con) { const count = ref(0); setTimeout(() => { count.value = 1; }, 2000); const stop = watchEffect( () => { /* * 1、首次載入會立即執行 * 2、響應的最終所有依賴監聽變化(資料改變) * 3、在解除安裝onUnmounte時自動停止 * 4、執行stop就會停止監聽,否則一直監聽 * 5、非同步函式先執行再去監聽改變 */ }, { // 6、在update之後執行 flush: "post", // 同步執行 flush: "async", } ); setTimeout(() => { stop(); }, 4000); // 7、元件掛在ref const myRef = ref(null); // 避免監聽時先見聽到null 在監聽到h1 onMounted(() => { watchEffect(() => { console.log(myRef.value); }); }); // 8、debugging 開發模式使用 watchEffect( () => { console.log(count.value); }, { onTrack(e) { // 監聽到count和改變count }, onTrigger(e) { // count改變了會觸發 }, } ); return { myRef, count, }; }, }; </script>
9、ref,torefs,isref,unref
9.1ref
ref() 函式用來根據給定的值建立一個響應式的資料物件,ref() 函式呼叫的返回值是一個物件,這個物件上只包含一個 .value 屬性:
ref資料
<template> <div class="mine"> {{count}} // 10 </div> </template> <script > import {ref } from 'vue'; export default { setup() { const count = ref(10) // 在js 中獲取ref 中定義的值, 需要通過value屬性 console.log(count.value); return { count } } } </script>
ref 訪問dom
通過 refs 來回去真實 dom 元素, 這個和 react 的用法一樣,為了獲得對模板內元素或元件例項的引用,我們可以像往常一樣在 setup()中宣告一個 ref 並返回它
-
還是跟往常一樣,在 html 中寫入 ref 的名稱
-
在steup 中定義一個 ref
-
steup 中返回 ref的例項
-
onMounted 中可以得到 ref的RefImpl的物件, 通過.value 獲取真實dom
<template> <!--第一步:還是跟往常一樣,在 html 中寫入 ref 的名稱--> <div class="mine" ref="elmRefs"> <span>1111</span> </div> </template> <script > import { set } from 'lodash'; import { onMounted, ref } from 'vue'; export default{ setup(props, context) { // 獲取真實dom const elmRefs = ref(null); onMounted (() => { console.log(elmRefs.value); // 得到一個 RefImpl 的物件, 通過 .value 訪問到資料 }) return { elmRefs } } } </script>
在 reactive 物件中訪問 ref 建立的響應式資料
當把 ref() 建立出來的響應式資料物件,掛載到 reactive() 上時,會自動把響應式資料物件展開為原始的值,不需通過 .value 就可以直接被訪問,例如:
<template> <div class="mine"> {{count}} -{{t}} // 10 -100 </div> </template> <script > import reactive, ref, toRefs } from 'vue'; export default { setup() { const count = ref(10) const obj = reactive({ t: 100, count }) // 通過reactive 來獲取ref 的值時,不需要使用.value屬性 console.log(obj.count); return { ...toRefs(obj) } } } </script>
9.2 toRefs() 函式
toRefs() 函式可以將 reactive() 建立出來的響應式物件,轉換為普通的物件,只不過,這個物件上的每個屬性節點,都是 ref() 型別的響應式資料
<template> <div> <p>{{ count }} - {{ name }}</p> <button @click="count += 1">+1</button> <button @click="add">+1</button> </div> </template> <script> import { reactive, toRefs } from "@vue/composition-api"; export default { setup() { // 響應式資料 const state = reactive({ count: 0, name: "zs" }); // 方法 const add = () => { state.count += 1; }; return { // 非響應式資料 // ...state, // 響應式資料 ...toRefs(state), add }; } }; </script>
9.3 isref
isRef() 用來判斷某個值是否為 ref() 建立出來的物件
setup(props, context) { const name: string = 'vue' const age = ref(18) console.log(isRef(age)); // true console.log(isRef(name)); // false return { age, name } }
9.4 unref
unRef() 用來判斷某個值是否為 ref() 建立出來的物件有丟擲該物件
setup(props, context) { const name: string = 'vue' const age = ref(18) console.log(unRef(age)); // 18 console.log(unRef(name)); // vue return { age, name } }