vue2開發過專案的,想直接上手vue3很快,幾個API熟悉了就夠了,其它的特性在使用vue3的過程慢慢去了解。 任何在熟悉了使用之後再去研究一些API的原理,慢慢就能把vue3掌握。
而且vue3的使用結合ts,開發過程中ts的比重沒有那麼大,之前分享的ts那些基礎會了,完全就夠用來開發了。【相關推薦:】
全域性 API 和應用 API
vue3的一個新概念,返回一個提供應用上下文的應用範例,應用範例掛載的整個元件樹共用同一個上下文:
const app = createApp(App); app.use(store).use(router).mount("#app");
vue2:
new Vue({ router, store, render: (h) => h(App), }).$mount("#app");
然後以前用Vue.的那些API都變成用這個應用範例app.:
vue2 | vue3 |
---|---|
Vue.component | app.component |
app.config | app.config |
app.directive | app.directive |
app.mixin | app.mixin |
app.use | app.use |
其它API像nextTick、h等都是直接從vue結構出來使用:
import { createApp, h, nextTick } from 'vue'
composition API
這個可太重要了,vue3把這個函數當作了入口點。接收兩個引數props和context。 函數會在beforeCreate、created之前執行,可以說取代了beforeCreate、created成為新的生命週期。 新增的composition API都寫在setup函數中。
props就是vue2的props,context則提供attrs、slots、emit等。 通過return一個物件,把響應式資料暴露給template,相當於vue2的data,不同的是函數也是這樣暴露給template:
<template> <div class="hello"> <h1>{{ msg }}</h1> <h1 @click="test">{{ isRef }}</h1> </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ name: "HelloWorld", props: { msg: String, }, setup(props) { console.log(props.msg); let isRef = ref("is ref"); const test = () => { console.log(isRef.value); }; return { isRef, test, }; }, }); </script>
vue3還提供了單檔案元件(推薦),將setup加到script上,裡面的程式碼就會被編譯成setup函數。 比較重要的幾個點:
宣告的頂層的繫結 (包括變數,函數宣告,以及 import 引入的內容) 都能在模板中直接使用 響應式資料、元件等也可以直接使用:
<template> <div class="hello"> <h1>{{ msg }}</h1> <h1 @click="test">{{ isRef }}</h1> </div> <MyComponent /> </template> <script lang="ts" setup> import MyComponent from './MyComponent.vue' import { ref } from "vue"; const msg = "msg"; const isRef = ref(""); function test() { console.log(isRef.value); } </script>
使用單檔案元件,一些屬性的使用也要改變,當然也有其它替代的API:
屬性 | 對應 |
---|---|
props和emits | defineProps和defineEmits |
ref或者$parent | defineExpose |
slots和attrs | useSlots和useAttrs() |
接受一個內部值並返回一個響應式且可變的 ref 物件,在setup函數內部存取ref函數需要加.value, 如果要加型別,要用泛型,不加則會型別推論,也可以不給初始值,則是any,且是undefined:
const ref1 = ref(1); const ref2 = ref<number>(2); const ref3 = ref1.value;//不是響應式,相當於變數 const ref4 = ref();//refs.value是undefined ref2.value = 5;//賦值 讀取都要.value
一般來說,ref只會賦值基礎資料型別和陣列,也可以泛型聯合型別 如果將物件分配為ref值,則它將被reactive函數處理為深層的響應式物件:
//不建議,內部也是reactive處理 const ref1 = ref({ a: 10, }); //不確定型別 const ref3 = ref<string | number>(); ref3.value = 1; ref3.value = ""; //陣列物件,ts型別宣告,用泛型 type Obj1 = { c: string }; type Obj2 = { b: string; c: Obj1[]; }; const ref2 = ref<Obj2[]>([ { b: "", c: [{ c: "" }], }, ]);
用來宣告響應式的物件,型別通過泛型新增:
type Obj = { a: number; b: string; }; let obj = reactive<Obj>({ a: 10, b: "", }); let state = reactive({ a: 10, b: "", });
reactive將會解包所有深層的 refs,同時維持ref的響應性, 將ref分配給reactive的property時,ref也會被自動解包。 簡單說就是ref的值和reactive的值響應式了
const count = ref(1) const obj = reactive({ count }) // ref 會被解包 console.log(obj.count === count.value) // true // 它會更新 `obj.count` count.value++ console.log(count.value) // 2 console.log(obj.count) // 2 // 它也會更新 `count` ref obj.count++ console.log(obj.count) // 3 console.log(count.value) // 3 //跟上面一樣效果 const count = ref(1) const obj = reactive({}) obj.count = count console.log(obj.count) // 1 console.log(obj.count === count.value) // true
為源響應式物件上的某個property新建立一個ref,也就是reactive建立的才可以, 會保持對其源 property 的響應式連線:
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') fooRef.value++ console.log(state.foo) // 2 state.foo++ console.log(fooRef.value) // 3
將響應式物件轉換為普通物件,其中結果物件的每個property都是指向原始物件相應property的ref:
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) /* stateAsRefs 的型別: { foo: Ref<number>, bar: Ref<number> } */ // ref 和原始 property 已經「連結」起來了 state.foo++ console.log(stateAsRefs.foo.value) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3
toRef和toRefs在對一些響應式物件結構展開會很有用,可以有用的時候看看。
watch接收兩個引數,第一個引數可以是有return的函數,也可以是一個ref, 第二個引數則跟vue2相同的函數,並且watch可以很多個:
//一般ref不用這種方式 let count = ref(0); watch( () => count.value, (val, old) => { console.log(old, val); } ); //單個ref推薦 watch(count, (val, old) => { console.log(old, val); }); //監聽reactive物件 let state = reactive({ count: 0 }); //函數引數必須具體到某個值,如果是() => state無效,或者加上{ deep: true } watch( () => state.count, (val, old) => { console.log(old, val); } ); //加上{ deep: true } watch( () => state, (val, old) => { console.log(old, val); }, { deep: true } ); //監聽整個物件,新舊的值都相同,或者用lodash.cloneDeep進行深拷貝 //state改成() => _.cloneDeep(state) watch(state, (val, old) => { console.log(old.count, val.count); });
也可以同時監聽多個,兩個引數分別用陣列,個人還是建議單個:
const state = reactive({ count: 1 }); const count = ref(0); // 監聽一個陣列 watch( [() => state.count, count], ([newState, newCount], [oldState, oldCount]) => { console.log("new:", newState, newCount); console.log("old:", oldState, oldCount); } );
官網一直提到惰性,其實就是是否加immediate: true,加了初始化就會執行。
它"立即執行"傳入的一個函數,同時響應式追蹤其依賴,並在其依賴變更時重新執行該函數:
const state = reactive({ count: 1 }); const count = ref(0); watchEffect(() => { if (state.count > 3) { count.value++; } }); watchEffect(() => console.log(count.value));
至於watch和watchEffect共用停止偵聽,清除副作用 (相應地 onInvalidate 會作為回撥的第三個引數傳入)、 副作用重新整理時機和偵聽器偵錯行為後面要仔細研究一下。
從vue2開始,就很多人分不清什麼時候用computed什麼時候用watch,computed主要是用來宣告 有兩個及以上的依賴的資料,也就是說一個變數是根據多個資料進行判斷的,用computed,單個的用watch。 至於語法的不同就不多說了,vue3的computed常用語法是一個帶有return的函數,也可以同時存在多個:
let count = ref(0); let page = ref(0); let pg = computed(() => { return count.value + page.value; });
要注意,computed宣告的變數(pg)是不能直接去修改的(唯讀),跟vue2一樣也有get、set函數(可讀可寫)。
在單檔案元件中必須使用defineProps和defineEmits API來宣告props和emits,可以算是語法糖。 父元件還是跟原來一樣傳值,子元件接收:
//父元件 <template> <div class="home"> <input v-model="msg" /> <HelloWorld :msg="msg" @change="change" /> </div> </template> <script lang="ts" setup> import { ref } from "vue"; import HelloWorld from "@/components/HelloWorld.vue"; let msg = ref("is parent"); const change = (val: string) => { msg.value = val; }; </script> //子元件 <template> <div>{{ msg }}</div> <button @click="change">emit</button> </template> <script lang="ts" setup> import { defineProps, defineEmits } from "vue"; const props = defineProps({ msg: String, }); console.log(props.msg); const emit = defineEmits(["change"]); const change = () => { emit("change", "is son"); }; </script> //設定預設值 defineProps({ msg: { type: Number, default: 100, }, });
template可以直接使用msg,要呼叫的話就得props.msg。
defineProps只能限制型別,沒有提供預設值(這邊應該是限定用ts的介面才沒有預設值)。為了解決這個問題,提供了 withDefaults 編譯器宏:
type Porps = { msg: string; }; const props = withDefaults(defineProps<Porps>(), { msg: "default", });
withDefaults接收兩個引數,第一個引數是defineProps加props欄位的泛型,第二個欄位是預設值,也可以不設定。
也可以監聽props:
watch( () => props.msg, (val) => { console.log(val); } );
vue2很多情況還是需要用到這個api,vue3中比較特別, 為了獲得對模板內元素或元件範例的參照,我們可以像往常一樣宣告ref, 在渲染上下文中暴露root,並通過ref="root",將其繫結到 div 作為其 ref。 在虛擬DOM修補程式演演算法中,如果 VNode 的 ref 鍵對應於渲染上下文中的 ref, 則VNode的相應元素或元件範例將被分配給該ref的值。 這是在虛擬 DOM 掛載/打修補程式過程中執行的,因此模板參照只會在初始渲染之後獲得賦值。
<button ref="testRef">testRef</button> let testRef = ref(null); onMounted(() => { // DOM 元素將在初始渲染後分配給 ref console.log(testRef.value); // <button>testRef</button> });
跟vue2一樣的使用:
nextTick(() => { console.log(testRef.value); });
也可以跟官網提供的一樣用async的函數await:
let testRef = ref(null); const nextTickFn = async () => { await nextTick(); console.log(testRef.value); }; nextTickFn();
vue2有時候會用this.$refs呼叫子元件的函數或者變數, 單檔案元件是預設關閉的,所以單檔案元件要用defineExpose編譯器宏暴露出去:
//父元件 <template> <div class="home"> <HelloWorld ref="sonRef" /> </div> </template> <script lang="ts" setup> import { nextTick, ref } from "vue"; import HelloWorld from "@/components/HelloWorld.vue"; let sonRef = ref(); nextTick(() => { sonRef.value.sonFn(); console.log(sonRef.value.sonRef); }); </script> //子元件 let sonRef = ref("is son"); const sonFn = () => { console.log("is son fn"); }; defineExpose({ sonFn, sonRef });
vue2的this.$parent在單檔案元件中,自己是實現了,但是官方沒有直接給出程式碼:
//父元件 const parentRef = ref("is parent ref"); const parentFn = () => { console.log("is parent fn"); }; defineExpose({ parentRef, parentFn, }); //子元件 let parent = getCurrentInstance(); console.log(parent?.parent?.exposed?.parentRef.value); parent?.parent?.exposed?.parentFn();
新增元件
官網介紹了很多,這個元件最大的作用就是可以讓元件脫離固定的元件位置,可以掛載在邏輯上最優的位置, 其它使用都跟元件一樣,只有位置改變:
<teleport to="#teleportDiv"> <HelloWorld /> </teleport> <teleport to="body"> <HelloWorld /> </teleport>
掛載的元素會從上向下解析,第一個to的元素,標籤、class、id等。一般使用也不會隨便,都會用id元素或者body。
Suspense是一個試驗性的新特性,官方也說生產環境請勿使用。 主要是允許將元件非同步處理等待過程提升到元件樹中處理。
單檔案元件裡面頂層await裡面提到: async setup() 必須與 Suspense 組合使用,Suspense 目前還是處於實驗階段的特性。 我們打算在將來的某個釋出版本中開發完成並提供檔案。
所以這邊就不給出不是單檔案元件的程式碼實現了。
生命週期
生命週期還是跟原來的一樣,只是前面都加上了on,destroy都變成了unmount。 setup會在beforeCreate、created之前執行,取代了beforeCreate、created。
使用:
onMounted(() => { console.log("mounted"); });
試了一下,居然可以寫多個。
vue3新增或者刪除了哪些API,其實沒必要一下子全部去記住,當一些API使用的時候如果發現不生效了, 再去官網查一下,當然,基礎的這些API就得記下來。
還是那句話,先學會基礎,上手開發,然後慢慢去了解學習不常用的,甚至是原始碼。
(學習視訊分享:、)
以上就是如何快速上手vue3,學會這幾個API吧!的詳細內容,更多請關注TW511.COM其它相關文章!