Vue Composition API(VCA) 在實現上也其實只是把 Vue 自己就有的響應式系統更顯式地暴露出來而已。
這不是函數式,只是 API 暴露爲函數。
3.0 Template 編譯出來的性能會比手寫 jsx 快好幾倍。
——尤雨溪javascript
Vue2 傳統的 data,computed,watch,methods 寫法,咱們稱之爲「選項式api(Options API )」
Vue3 使用 Composition API (VCA)能夠根據邏輯功能來組織代碼,一個功能相關的 api 會放在一塊兒。html
到目前爲止,前端
Vue:Mixins(混入)、HOC(高階組件)、做用域插槽、Vue Composition API(VCA/組合式API)。vue
React:Mixins、HOC、Render Props、Hook。java
咱們能夠看到都是一段愈來愈好的成長史,這裏就再也不舉例贅述,本文重心在 VCA,VCA 更偏向於「組合」的概念。react
在 Vue 中,有了抽象封裝組件的概念,解決了在頁面上模塊越多,越顯臃腫的問題。但即便進行組件封裝,在應用愈來愈大的時候,會發現頁面的邏輯功能點愈來愈多,
data/computed/watch/methods
中會被不斷塞入邏輯功能,因此要將邏輯再進行抽離組合、複用,這就是 VCA。webpack
舉個簡單的例子:es6
咱們要實現 3 個邏輯web
爲了閱讀質量,省略了部分代碼,但不影響咱們瞭解 VCAapi
// 邏輯功能(1)
const getTableDataApi = id => {
const mockData = {
1: [
{ id: 11, name: '張三1' },
{ id: 12, name: '李四1' },
{ id: 13, name: '王五1' }
],
2: [
{ id: 21, name: '張三2' },
{ id: 22, name: '李四2' },
{ id: 23, name: '王五2' }
]
};
return new Promise(resolve => {
setTimeout(() => {
resolve(mockData[id] || []);
}, 1000);
});
};
export default {
name: 'VCADemo',
components: { Modal },
data() {
return {
// 邏輯功能(1)
id: 1,
table: [],
// 邏輯功能(2)
search: '',
// 邏輯功能(3)
modalShow: false,
form: {
id: '',
name: ''
}
};
},
computed: {
// 邏輯功能(2)
getTableDataBySearch() {
return this.table.filter(item => item.name.indexOf(this.search) !== -1);
}
},
watch: {
// 邏輯功能(1)
id: 'getTableData'
},
mounted() {
// 邏輯功能(1)
this.getTableData();
},
methods: {
// 邏輯功能(1)
async getTableData() {
const res = await getTableDataApi(this.id);
this.table = res;
},
// 邏輯功能(3)
handleAdd() {
this.modalShow = true;
},
// 邏輯功能(3)
handlePost() {
const { id, name } = this.form;
this.table.push({ id, name });
this.modalShow = false;
}
}
};
複製代碼
這裏只是舉例簡單的邏輯。若是項目複雜了,邏輯增多了。涉及到一個邏輯的改動,咱們就可能須要修改分佈在不一樣位置的相同功能點,提高了維護成本。
讓咱們來關注邏輯,抽離邏輯,先看主體的代碼結構
import useTable from './composables/useTable';
import useSearch from './composables/useSearch';
import useAdd from './composables/useAdd';
export default defineComponent({
name: 'VCADemo',
components: { Modal },
setup() {
// 邏輯功能(1)
const { id, table, getTable } = useTable(id);
// 邏輯功能(2)
const { search, getTableBySearch } = useSearch(table);
// 邏輯功能(3)
const { modalShow, form, handleAdd, handlePost } = useAdd(table);
return {
id,
table,
getTable,
search,
getTableBySearch,
modalShow,
form,
handleAdd,
handlePost
};
}
});
複製代碼
setup 接收兩個參數:props,context。能夠返回一個對象,對象的各個屬性都是被 proxy
的,進行監聽追蹤,將在模板上進行響應式渲染。
咱們來關注其中一個邏輯,useTable
,通常來講咱們會用 use
開頭進行命名,有那味了~
// VCADemo/composables/useTable.ts
// 邏輯功能(1)相關
import { ref, onMounted, watch, Ref } from 'vue';
import { ITable } from '../index.type';
const getTableApi = (id: number): Promise<ITable[]> => {
const mockData: { [key: number]: ITable[] } = {
1: [
{ id: '11', name: '張三1' },
{ id: '12', name: '李四1' },
{ id: '13', name: '王五1' }
],
2: [
{ id: '21', name: '張三2' },
{ id: '22', name: '李四2' },
{ id: '23', name: '王五2' }
]
};
return new Promise(resolve => {
setTimeout(() => {
resolve(mockData[id] || []);
}, 1000);
});
};
export default function useTable() {
const id = ref<number>(1);
const table = ref<ITable[]>([]);
const getTable = async () => {
table.value = await getTableApi(id.value);
};
onMounted(getTable);
watch(id, getTable);
return {
id,
table,
getTable
};
}
複製代碼
咱們把相關邏輯獨立抽離,並「組合」在一塊兒了,能夠看到在 vue 包暴露不少獨立函數提供咱們使用,已經再也不 OO 了,嗅到了一股 FP 的氣息~
上面這個例子先說明了 VCA 的帶來的好處,Vue3 的核心固然是 VCA,Vue3 不只僅是 VCA,讓咱們帶着好奇往下看~
選項式 API(Vue2) | Hook inside setup(Vue3) |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
Hook inside setup,顧名思義,VCA 建議在 setup
這個大方法裏面寫咱們的各類邏輯功能點。
傳送,將組件的 DOM 元素掛載在任意指定的一個 DOM 元素,與 React Portals 的概念是一致的。
一個典型的例子,咱們在組件調用了 Modal 彈框組件,咱們但願的彈框是這樣子的,絕對居中,層級最高,如:
組件的結構是這樣子的
<Home>
<Modal />
</Home>
複製代碼
可是若是在父組件 Home 有相似這樣的樣式,如 transform
:
就會影響到 Modal 的位置,即便 Modal 用了 position:fixed
來定位,如:
這就是爲何咱們須要用 Teleport 組件來幫助咱們 「跳出」 容器,避免受到父組件的一些約束控制,把組件的 DOM 元素掛載到 body 下,如:
<Teleport to="body">
<div v-if="show">
...Modal 組件的 DOM 結構...
</div>
</Teleport>
複製代碼
注意:即便 Modal 跳出了容器,也保持 「父子組件關係」,只是 DOM 元素的位置被移動了而已 。
咱們都知道在 Vue2 也有異步組件的概念,但總體上來講不算完整~,Vue3 提供了 defineAsyncComponent
方法與 Suspense
內置組件,咱們能夠用它們來作一個優雅的異步組件加載方案。
直接看代碼:
HOCLazy/index.tsx
import { defineAsyncComponent, defineComponent } from 'vue';
import MySuspense from './MySuspense.vue';
export default function HOCLazy(chunk: any, isComponent: boolean = false) {
const wrappedComponent = defineAsyncComponent(chunk);
return defineComponent({
name: 'HOCLazy',
setup() {
const props = { isComponent, wrappedComponent };
return () => <MySuspense {...props} />;
}
});
}
複製代碼
解釋:HOCLazy 接收了兩個參數,chunk
就是咱們常常採用的組件異步加載方式如:chunk=()=>import(xxx.vue)
,isComponent
表示當前的「組件」是一個 組件級 or 頁面級,經過判斷 isComponent
來分別對應不一樣的 「loading」 操做。
HOCLazy/MySuspense.vue
<template>
<Suspense>
<template #default>
<component :is="wrappedComponent" v-bind="$attrs" />
</template>
<template #fallback>
<div>
<Teleport to="body" :disabled="isComponent">
<div v-if="delayShow" class="loading" :class="{component:isComponent}">
<!-- 組件和頁面有兩種不同的loading方式,這裏再也不詳細封裝 -->
<div> {{isComponent?'組件級':'頁面級'}}Loading ...</div>
</div>
</Teleport>
</div>
</template>
</Suspense>
</template>
<script lang="ts"> import { defineComponent, defineAsyncComponent, ref, onMounted } from 'vue'; export default defineComponent({ name: 'HOCLazy', props: ['isComponent', 'wrappedComponent'], setup(props) { const delayShow = ref<boolean>(false); onMounted(() => { setTimeout(() => { delayShow.value = true; // delay 本身拿捏,也能夠以 props 的方式傳入 }, 300); }); return { ...props, delayShow }; } }); </script>
<style lang="less" scoped>
.loading {
// 組件級樣式
&.component {
}
// 頁面級樣式
}
</style>
複製代碼
解釋:
fallback
咱們這裏能夠理解成一個 loading 的佔位符,在異步組件還沒顯示以前的後備內容。v-bind="$attrs"
來保證咱們傳遞給目標組件的 props 不會消失。Teleport :disabled="isComponent"
來控制是否跳出。delayShow
,若是咱們沒有這個延遲,在網絡環境良好的狀況下,loading 每次都會一閃而過,會有一種「反優化」的感受。調用 HOCLazy:
爲了更好的看出效果,咱們封裝了 slow 方法來延遲組件加載:
utils/slow.ts
const slow = (comp: any, delay: number = 1000): Promise<any> => {
return new Promise(resolve => {
setTimeout(() => resolve(comp), delay);
});
};
export default slow;
複製代碼
調用(組件級)
<template>
<LazyComp1 str="hello~" />
</template>
const LazyComp1 = HOCLazy(
() => slow(import('@/components/LazyComp1.vue'), 1000),
true
);
// ...
components: {
LazyComp1
},
// ...
複製代碼
看個效果:
其實這與 React 中的
React.lazy + React.Suspense
的概念是一致的,以前寫過的一篇文章 《React丨用戶體驗丨hook版 lazy loading》,小夥伴能夠看看作下對比~
ref 和 reactive 的存在都是了追蹤值變化(響應式),ref 有個「包裝」的概念,它用來包裝原始值類型,如 string 和 number ,咱們都知道不是引用類型是沒法追蹤後續的變化的。ref 返回的是一個包含 .value
屬性的對象。
setup(props, context) {
const count = ref<number>(1);
// 賦值
count.value = 2;
// 讀取
console.log('count.value :>> ', count.value);
return { count };
}
複製代碼
在 template 中 ref 包裝對象會被自動展開(Ref Unwrapping),也就是咱們在模板裏不用再 .value
<template>
{{count}}
</template>
複製代碼
與 Vue2 中的 Vue.observable()
是一個概念。
用來返回一個響應式對象,如:
const obj = reactive({
count: 0
})
// 改變
obj.count++
複製代碼
注意:它用來返回一個響應式對象,自己就是對象,因此不須要包裝。咱們使用它的屬性,不須要加 .value
來獲取。
官網:由於 props 是響應式的,你不能使用 ES6 解構,由於它會消除 prop 的響應性。
讓咱們關注 setup
方法的 props 的相關操做:
<template>
{{name}}
<button @click="handleClick">點我</button>
</template>
// ...
props: {
name: { type: String, default: ' ' }
},
setup(props) {
const { name } = props;
const handleClick = () => {
console.log('name :>> ', name);
};
return { handleClick };
}
// ...
複製代碼
注意:props 無需經過 setup 函數 return,也能夠在 template 進行綁定對應的值
咱們都知道解構是 es6 一種便捷的手段,編譯成 es5 ,如:
// es6 syntax
const { name } = props;
// to es5 syntax
var name = props.name;
複製代碼
假設父組件更改了 props.name 值,當咱們再點擊了 button 輸出的 name 就仍是以前的值,不會跟着變化,這實際上是一個基礎的 js 的知識點。
爲了方便咱們對它進行包裝,toRefs
能夠理解成批量包裝 props 對象,如:
const { name } = toRefs(props);
const handleClick = () => {
// 由於是包裝對象,因此讀取的時候要用.value
console.log('name :>> ', name.value);
};
複製代碼
能夠理解這一切都是由於咱們要用解構,toRefs
所採起的解決方案。
toRef 的用法,就是多了一個參數,容許咱們針對一個 key 進行包裝,如:
const name = toRef(props,'name');
console.log('name :>> ', name.value);
複製代碼
Vue3 的 watch 方法與 Vue2 的概念相似,watchEffect 會讓咱們有些疑惑。其實 watchEffect 與 watch 大致相似,區別在於:
watch 能夠作到的
對於 Vue2 的 watch 方法,Vue3 的 "watch" 多了一個「清除反作用」 的概念,咱們着重關注這點。
這裏拿 watchEffect
來舉例:
watchEffect:它當即執行傳入的一個函數,同時響應式追蹤其依賴,並在其依賴變動時從新運行該函數。
watchEffect 方法簡單結構
watchEffect(onInvalidate => {
// 執行反作用
// do something...
onInvalidate(() => {
// 執行/清理失效回調
// do something...
})
})
複製代碼
執行失效回調,有兩個時機
一個例子:咱們要經過 id 發起請求獲取「水果」的詳情,咱們監聽 id,當 id 切換過於頻繁(還沒等上個異步數據返回成功)。可能會致使最後 id=1
的數據覆蓋了id=2
的數據,這並非咱們但願的。
咱們來模擬並解決這個場景:
模擬接口 getFruitsById
interface IFruit {
id: number;
name: string;
imgs: string;
}
const list: { [key: number]: IFruit } = {
1: { id: 1, name: '蘋果', imgs: 'https://xxx.apple.jpg' },
2: { id: 2, name: '香蕉', imgs: 'https://xxx.banana.jpg' }
};
const getFruitsById = (
id: number,
delay: number = 3000
): [Promise<IFruit>, () => void] => {
let _reject: (reason?: any) => void;
const _promise: Promise<IFruit> = new Promise((resolve, reject) => {
_reject = reject;
setTimeout(() => {
resolve(list[id]);
}, delay);
});
return [
_promise,
() =>
_reject({
message: 'abort~'
})
];
};
複製代碼
這裏封裝了「取消請求」的方法,利用 reject 來完成這一動做。
在 setup 方法
setup() {
const id = ref<number>(1);
const detail = ref<IFruit | {}>({});
watchEffect(async onInvalidate => {
onInvalidate(() => {
cancel && cancel();
});
// 模擬id=2的時候請求時間 1s,id=1的時候請求時間 2s
const [p, cancel] = getFruitsById(id.value, id.value === 2 ? 1000 : 2000);
const res = await p;
detail.value = res;
});
// 模擬頻繁切換id,獲取香蕉的時候,獲取蘋果的結果尚未回來,取消蘋果的請求,保證數據不會被覆蓋
id.value = 2;
// 最後 detail 值爲 { "id": 2, "name": "香蕉", "imgs": "https://xxx.banana.jpg" }
}
複製代碼
若是沒有執行 cancel()
,那麼 detail 的數據將會是 { "id": 1, "name": "蘋果", "imgs": "https://xxx.apple.jpg" }
,由於 id=1 數據比較「晚接收到」。
這就是在異步場景下常見的例子,清理失效的回調,保證當前反作用有效,不會被覆蓋。感興趣的小夥伴能夠繼續深究。
咱們都知道在封裝組件的時候,只能有一個 root 。在 Vue3 容許咱們有多個 root ,也就是片斷,可是在一些操做值得咱們注意。
當 inheritAttrs=true[默認]
時,組件會自動在 root 繼承合併 class ,如:
子組件
<template>
<div class="fragment">
<div>div1</div>
<div>div2</div>
</div>
</template>
複製代碼
父組件調用,新增了一個 class
<MyFragment class="extend-class" />
複製代碼
子組件會被渲染成
<div class="fragment extend-class">
<div> div1 </div>
<div> div2 </div>
</div>
複製代碼
若是咱們使用了 片斷 ,就須要顯式的去指定綁定 attrs ,如子組件:
<template>
<div v-bind="$attrs">div1</div>
<div>div2</div>
</template>
複製代碼
在 Vue2 咱們會對 props 裏的數據進行規定類型,默認值,非空等一些驗證,能夠理解 emits 作了相似的事情,把 emit 規範起來,如:
// 也能夠直接用數組,不作驗證
// emits: ['on-update', 'on-other'],
emits: {
// 賦值 null 不驗證
'on-other': null,
// 驗證
'on-update'(val: number) {
if (val === 1) {
return true;
}
// 自定義報錯
console.error('val must be 1');
return false;
}
},
setup(props, ctx) {
const handleEmitUpdate = () => {
// 驗證 val 不爲 1,控制檯報錯
ctx.emit('on-update', 2);
};
const handleEmitOther = () => {
ctx.emit('on-other');
};
return { handleEmitUpdate, handleEmitOther };
}
複製代碼
在 setup 中,emit 已經再也不用 this.$emit
了,而是 setup 的第二個參數 context
上下文來獲取 emit 。
我的仍是挺喜歡 v-model 的更新的,能夠提高封裝組件的體驗感~
在Vue2,假設我須要封裝一個彈框組件 Modal,用
show
變量來控制彈框的顯示隱藏,這確定是一個父子組件都要維護的值。由於單向數據流,因此須要在 Modal 組件 emit 一個事件,父組件監聽事件接收並修改這個show
值。
爲了方便咱們會有一些語法糖,如 v-model,可是在 Vue2 一個組件上只能有一個 v-model ,由於語法糖的背後是value
和@input
的組成, 若是還有多個相似這樣的 「雙向修改數據」,咱們就須要用語法糖.sync
同步修飾符。
Vue3 把這兩個語法糖統一了,因此咱們如今能夠在一個組件上使用 多個 v-model 語法糖,舉個例子:
先從父組件看
<VModel v-model="show" v-model:model1="check" v-model:model2.hello="textVal" />
複製代碼
hello爲自定義修飾符
咱們在一個組件上用了 3 個 v-model 語法糖,分別是
v-model 語法糖 | 對應的 prop | 對應的 event | 自定義修飾符對應的 prop |
---|---|---|---|
v-model(default) | modelValue | update:modelValue | 無 |
v-model:model1 | model1 | update:model1 | 無 |
v-model:model2 | model2 | update:model2 | model2Modifiers |
這樣子咱們就更清晰的在子組件咱們要進行一些什麼封裝了,如:
VModel.vue
// ...
props: {
modelValue: { type: Boolean, default: false },
model1: { type: Boolean, default: false },
model2: { type: String, default: '' },
model2Modifiers: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue', 'update:model1', 'update:model2'],
// ...
複製代碼
<template>
<input type="text" placeholder="請輸入帳號" v-if="show" />
<input type="text" placeholder="請輸入郵箱" v-else />
<button @click="show=!show">Toggle</button>
</template>
複製代碼
相似這樣的 v-if/v-else,在 Vue2 中,會盡量高效地渲染元素,一般會複用已有元素而不是從頭開始渲染,因此當咱們在第一個 input 中輸入,而後切換第二個 input 。第一個 input 的值將會被保留複用。
有些場景下咱們不要複用它們,須要添加一個惟一的 key ,如:
<template>
<input type="text" placeholder="請輸入帳號" v-if="show" key="account" />
<input type="text" placeholder="請輸入郵箱" v-else key="email" />
<button @click="show=!show">Toggle</button>
</template>
複製代碼
可是在 Vue3 咱們不用顯式的去添加 key ,這兩個 input 元素也是徹底獨立的,由於 Vue3 會對 v-if/v-else 自動生成惟一的 key。
在 Vue2 咱們對於一些全局的配置多是這樣子的,例如咱們使用了一個插件
Vue.use({
/* ... */
});
const app1 = new Vue({ el: '#app-1' });
const app2 = new Vue({ el: '#app-2' });
複製代碼
可是這樣子這會影響兩個根實例,也就是說,會變得不可控。
在 Vue3 引入一個新的 API createApp
方法,返回一個實例:
import { createApp } from 'vue';
const app = createApp({ /* ... */ });
複製代碼
而後咱們就能夠在這個實例上掛載全局相關方法,並只對當前實例生效,如:
app
.component(/* ... */)
.directive(/* ... */ )
.mixin(/* ... */ )
.use(/* ... */ )
.mount('#app');
複製代碼
須要注意的是,在 Vue2 咱們用了 Vue.prototype.$http=()=>{}
這樣的寫法,來對 「根Vue」 的 prototype 進行掛載方法,使得咱們在子組件,能夠經過原型鏈的方式找到 $http
方法,即 this.$http
。
而在 Vue3 咱們相似這樣的掛載須要用一個新的屬性 globalProperties
:
app.config.globalProperties.$http = () => {}
複製代碼
在 setup 內部使用 $http
:
setup() {
const {
appContext: {
config: {
globalProperties: { $http }
}
}
} = getCurrentInstance()
}
複製代碼
Vue2 響應式的基本原理,就是經過 Object.defineProperty
,但這個方式存在缺陷。使得 Vue 不得不經過一些手段來 hack,如:
push
,pop
等方法。而在 Vue3 中優先使用了 Proxy 來處理,它代理的是整個對象而不是對象的屬性,可對於整個對象進行操做。不只提高了性能,也沒有上面所說的缺陷。
簡單舉兩個例子:
const targetObj = { id: '1', name: 'zhagnsan' };
const proxyObj = new Proxy(targetObj, {
get: function (target, propKey, receiver) {
console.log(`getting key:${propKey}`);
return Reflect.get(...arguments);
},
set: function (target, propKey, value, receiver) {
console.log(`setting key:${propKey},value:${value}`);
return Reflect.set(...arguments);
}
});
proxyObj.age = 18;
// setting key:age,value:18
複製代碼
如上,用 Proxy
咱們對 proxyObj
對象動態添加的屬性也會被攔截到。
Reflect
對象是ES6 爲了操做對象而提供的新 API。它有幾個內置的方法,就如上面的 get
/ set
,這裏能夠理解成咱們用 Reflect
更加方便,不然咱們須要如:
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return target[propKey];
},
複製代碼
const targetArr = [1, 2];
const proxyArr = new Proxy(targetArr, {
set: function (target, propKey, value, receiver) {
console.log(`setting key:${propKey},value:${value}`);
return Reflect.set(...arguments);
}
});
proxyArr.push('3');
// setting key:2,value:3
// setting key:length,value:3
複製代碼
咱們都知道 Vue 有虛擬dom的概念,它能爲咱們在數據改變時高效的渲染頁面。
Vue3 優化了 vdom 的更新性能,簡單舉個例子
Template
<div class="div">
<div>content</div>
<div>{{message}}</div>
</div>
複製代碼
Compiler 後,沒有靜態提高
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", { class: "div" }, [
_createVNode("div", null, "content"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
]))
}
複製代碼
Compiler 後,有靜態提高
const _hoisted_1 = { class: "div" }
const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "content", -1 /* HOISTED */)
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_hoisted_2,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
]))
}
複製代碼
靜態提高包含「靜態節點」和「靜態屬性」的提高,也就是說,咱們把一些靜態的不會變的節點用變量緩存起來,提供下次 re-render 直接調用。
若是沒有作這個動做,當 render
從新執行時,即便標籤是靜態的,也會被從新建立,這就會產生性能消耗。
3.0 的一個主要設計目標是加強對 TypeScript 的支持。本來咱們指望經過 Class API 來達成這個目標,可是通過討論和原型開發,咱們認爲 Class 並非解決這個問題的正確路線,基於 Class 的 API 依然存在類型問題。——尤雨溪
基於函數的 API 自然 與 TS 完美結合。
在 TS 下,咱們須要用 Vue 暴露的方法 defineComponent,它單純爲了類型推導而存在的。
import { defineComponent } from 'vue';
export default defineComponent({
props: {
val1: String,
val2: { type: String, default: '' },
},
setup(props, context) {
props.val1;
}
})
複製代碼
當咱們在 setup 方法訪問 props 時候,咱們能夠看到被推導後的類型,
string | undefined
string
,如圖:咱們關注一下 props 定義的類型,若是是一個複雜對象,咱們就要用 PropType 來進行強轉聲明,如:
interface IObj {
id: number;
name: string;
}
obj: {
type: Object as PropType<IObj>,
default: (): IObj => ({ id: 1, name: '張三' })
},
複製代碼
或 聯合類型
type: {
type: String as PropType<'success' | 'error' | 'warning'>,
default: 'warning'
},
複製代碼
tree-sharking 即在構建工具構建後消除程序中無用的代碼,來減小包的體積。
基於函數的 API 每個函數均可以用 import { method1,method2 } from "xxx";
,這就對 tree-sharking 很是友好,並且函數名同變量名均可以被壓縮,對象去不能夠。舉個例子,咱們封裝了一個工具,工具提供了兩個方法,用 method1
,method2
來代替。
咱們把它們封裝成一個對象,而且暴露出去,如:
// utils
const obj = {
method1() {},
method2() {}
};
export default obj;
複製代碼
// 調用
import util from '@/utils';
util.method1();
複製代碼
通過webpack打包壓縮以後爲:
a={method1:function(){},method2:function(){}};a.method1();
複製代碼
咱們不用對象的形式,而用函數的形式來看看:
// utils
export function method1() {}
export function method2() {}
複製代碼
// 調用
import { method1 } from '@/utils';
method1();
複製代碼
通過webpack打包壓縮以後爲:
function a(){}a();
複製代碼
用這個例子咱們就能夠了解 Vue3 爲何能更好的 tree-sharking ,由於它用的是基於函數形式的API,如:
import {
defineComponent,
reactive,
ref,
watchEffect,
watch,
onMounted,
toRefs,
toRef
} from 'vue';
複製代碼
咱們上面的代碼都是在 setup 內部實現,可是目前 Vue3 還保留了 Vue2 的 options api 寫法,就是能夠「並存」,如:
// ...
setup() {
const val = ref<string>('');
const fn = () => {};
return {
val,
fn
};
},
mounted() {
// 在 mounted 生命週期能夠訪問到 setup return 出來的對象
console.log(this.val);
this.fn();
},
// ...
複製代碼
結合 react ,咱們知道 「函數式」,hook 是將來的一個趨勢。
因此我的建議仍是採用都在 setup
內部寫邏輯的方式,由於 Vue3 能夠徹底提供 Vue2 的所有能力。
我的以爲無論是 React Hook 仍是 Vue3 的 VCA,咱們均可以看到如今的前端框架趨勢,「更函數式」,讓邏輯複用更靈活。hook 的模式新增了 React / Vue 的抽象層級,「組件級 + 函數級」,可讓咱們處理邏輯時分的更細,更好維護。
Vue3 One Piece,nice !
最後,前端精本精祝您聖誕快樂🎄~ (據說公衆號關注「前端精」會更快樂哦~