第十六期:Vue 3.0 前瞻,体验 Vue Function API
概述
Vue 2.x 及以前的高階組件的組織形式或多或少都會面臨一些問題,特別是在需要處理重復邏輯的項目中,一旦開發者組織項目結構組織得不好,組件代碼極有可能被人詬病為“膠水代碼”。而在 Vue 2.x 及之前的版本,解決此類問題的辦法大致是下面的方案:
- mixin
- 函數式組件
- slots
筆者維護的項目也需要處理大量復用邏輯,在這之前,筆者一直嘗試使用mixin的方式來實現組件的復用。有些問題也一直會對開發者和維護者造成困惑,如一個組件同時mixin多個組件,很難分清對應的屬性或方法寫在哪個mixin里。其次,mixin的命名空間沖突也可能造成問題。難以保證不同的mixin不用到同一個屬性名。為此,官方團隊提出函數式寫法的意見征求稿,也就是RFC:Function-based component API。使用函數式的寫法,可以做到更靈活地復用組件,開發者在組織高階組件時,不必在組件組織上考慮復用,可以更好地把精力集中在功能本身的開發上。
注:本文只是筆者使用vue-function-api提前體驗 Vue Function API ,而這個 API 只是 Vue 3.0 的 RFC,而并非與最終 Vue 3.x API 一致。發布后可能有不一致的地方。
在 Vue 2.x 中使用
要想提前在Vue 2.x中體驗 Vue Function API ,需要引入vue-function-api,基本引入方式如下:
import Vue from 'vue'; import { plugin as VueFunctionApiPlugin } from 'vue-function-api';Vue.use(VueFunctionApiPlugin);基本組件示例
先來看一個基本的例子:
<template><div><span>count is {{ count }}</span><span>plusOne is {{ plusOne }}</span><button @click="increment">count++</button></div> </template><script> import Vue from 'vue'; import { value, computed, watch, onMounted } from 'vue-function-api';export default {setup(props, context) {// reactive stateconst count = value(0);// computed stateconst plusOne = computed(() => count.value + 1);// methodconst increment = () => {count.value++;};// watchwatch(() => count.value * 2,val => {console.log(`count * 2 is ${val}`);});// lifecycleonMounted(() => {console.log(`mounted`);});// expose bindings on render contextreturn {count,plusOne,increment,};}, }; </script>詳解
setup
setup函數是Vue Function API 構建的函數式寫法的主邏輯,當組件被創建時,就會被調用,函數接受兩個參數,分別是父級組件傳入的props和當前組件的上下文context。看下面這個例子,可以知道在context中可以獲取到下列屬性值
const MyComponent = {props: {name: String},setup(props, context) {console.log(props.name);// context.attrs// context.slots// context.refs// context.emit// context.parent// context.root} }value & state
value函數創建一個包裝對象,它包含一個響應式屬性value:
那么為何要使用value呢,因為在JavaScript中,基本類型并沒有引用,為了保證屬性是響應式的,只能借助包裝對象來實現,這樣做的好處是組件狀態會以引用的方式保存下來,從而可以被在setup中調用的不同的模塊的函數以參數的形式傳遞,既能復用邏輯,又能方便地實現響應式。
直接獲取包裝對象的值必須使用.value,但是,如果包裝對象作為另一個響應式對象的屬性,Vue內部會通過proxy來自動展開包裝對象。同時,在模板渲染的上下文中,也會被自動展開。
import { state, value } from 'vue-function-api'; const MyComponent = {setup() {const count = value(0);const obj = state({count,});console.log(obj.count) // 作為另一個響應式對象的屬性,會被自動展開obj.count++ // 作為另一個響應式對象的屬性,會被自動展開count.value++ // 直接獲取響應式對象,必須使用.valuereturn {count,};},template: `<button @click="count++">{{ count }}</button>`, };如果某一個狀態不需要在不同函數中被響應式修改,可以通過state創建響應式對象,這個state創建的響應式對象并不是包裝對象,不需要使用.value來取值。
watch & computed
watch和computed的基本概念與 Vue 2.x 的watch和computed一致,watch可以用于追蹤狀態變化來執行一些后續操作,computed用于計算屬性,用于依賴屬性發生變化進行重新計算。
computed返回一個只讀的包裝對象,和普通包裝對象一樣可以被setup函數返回,這樣就可以在模板上下文中使用computed屬性。可以接受兩個參數,第一個參數返回當前的計算屬性值,當傳遞第二個參數時,computed是可寫的。
import { value, computed } from 'vue-function-api';const count = value(0); const countPlusOne = computed(() => count.value + 1);console.log(countPlusOne.value); // 1count.value++; console.log(countPlusOne.value); // 2// 可寫的計算屬性值 const writableComputed = computed(// read() => count.value + 1,// writeval => {count.value = val - 1;}, );watch第一個參數和computed類似,返回被監聽的包裝對象屬性值,不過另外需要傳遞兩個參數:第二個參數是回調函數,當數據源發生變化時觸發回調函數,第三個參數是options。其默認行為與 Vue 2.x 有所不同:
- lazy:是否會在組件創建時就調用一次回調函數,與 Vue 2.x 相反,lazy默認是false,默認會在組件創建時調用一次。
- deep:與 Vue 2.x 的 deep 一致
- flush:有三個可選值,分別為 'post'(在渲染后,即nextTick后才調用回調函數),'pre'(在渲染前,即nextTick前調用回調函數),'sync'(同步觸發)。默認值為'post'。
當watch多個被包裝對象屬性時,參數均可以通過數組的方式進行傳遞,同時,與 Vue 2.x 的vm.$watch一樣,watch返回取消監聽的函數:
const stop = watch([valueA, () => valueB.value],([a, b], [prevA, prevB]) => {console.log(`a is: ${a}`);console.log(`b is: ${b}`);} );stop();注意:在RFC:Function-based component API初稿中,有提到effect-cleanup,是用于清理一些特殊情況的副作用的,目前已經在提案中被取消了。
生命周期
所有現有的生命周期都有對應的鉤子函數,通過onXXX的形式創建,但有一點不同的是,destoryed鉤子函數需要使用unmounted代替:
import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';const MyComponent = {setup() {onMounted(() => {console.log('mounted!');});onUpdated(() => {console.log('updated!');});// destroyed 調整為 unmountedonUnmounted(() => {console.log('unmounted!');});}, };一些思考
上面的詳解部分,主要抽取的是 Vue Function API 的常見部分,并非RFC:Function-based component API的全部,例如其中的依賴注入,TypeScript類型推導等優勢,在這里,由于篇幅有限,想要了解更多的朋友,可以點開RFC:Function-based component API查看。個人也在Function-based component API討論區看到了更多地一些意見:
-
由于底層設計,在setup取不到組件實例this的問題,這個問題在筆者嘗試體驗時也遇到了,期待正式發布的 Vue 3.x 能夠改進這個問題。
-
對于基本類型的值必須使用包裝對象的問題:在 RFC 討論區,為了同時保證TypeScript類型推導、復用性和保留Vue的數據監聽,包裝屬性必須使用.value來取值是討論最激烈的
-
關于包裝對象value和state方法命名不清晰可能導致開發者誤導等問題,已經在Amendment proposal to Function-based Component API這個提議中展開了討論:
- setup() {const state = reactive({count: 0,});const double = computed(() => state.count * 2);function increment() {state.count++;}return {...toBindings(state), // retains reactivity on mutations made to `state`double,increment,};
}
?
- 引入reactive API 和 binding API,其中reactive API 類似于 state API , binding API 類似于 value API。
- 之前使用的方法名state在 Vue 2.x 中可能被用作組件狀態對象,導致變量命名空間的沖突問題,團隊認為將state API 更名為 reactive 更為優雅。開發者能夠寫出const state = ... ,然后通過state.xxxx這種方式來獲取組件狀態,這樣也相對而言自然一些。
- value方法用于封裝基本類型時,確實會出現不夠優雅的.value的情況,開發者可能會在直接對包裝對象取值時忘記使用.value,修正方案提出的 reactive API,其含義是創建響應式對象,初始化狀態state就使用reactive創建,可保留每項屬性的getter和setter,這么做既滿足類型推導,也可以保留響應式引用,從而可在不同模塊中共享狀態值的引用。
- 但reactive可能導致下面的問題,需要引入binding API。 解決,如使用reactive創建的響應式對象,對其使用拓展運算符...時,則會丟失對象的getter和setter,提供toBindings方法能夠保留狀態的響應式。
當然,目前 Vue Function API 還處在討論階段,Vue 3.0 還處在開發階段,還是期待下半年 Vue 3.0 的初版問世吧,希望能給我們帶來更多的驚喜。
閱讀目錄(置頂)(長期更新計算機領域知識)https://blog.csdn.net/weixin_43392489/article/details/102380691
閱讀目錄(置頂)(長期更新計算機領域知識)https://blog.csdn.net/weixin_43392489/article/details/102380882
總結
以上是生活随笔為你收集整理的第十六期:Vue 3.0 前瞻,体验 Vue Function API的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 诸暨机器人餐厅价格_现场 | 一家尝出
- 下一篇: click vue 重复调用_VUE防止