寫在前面
在分享 vue-next
各個子模塊的實現之前,我覺的有必要比較全面的整理下 vue-next
中提出的函數式 api
,瞭解這些的話,無論是對於源碼的閱讀,還是當正式版發佈時開始學習,應該都會有起到一定的輔助作用。
類似的東西在網上有很多,只是會比較零碎,同時有些也相對過時了,當然當前整理的這些也有可能會過時,畢竟代碼還處於 pre-alpha
的階段,但其中的設計思想應該是不會改變了。
往期文章
setup
setup
會作爲編寫組件業務邏輯的主戰場,各種 hook
類型的方法均需要在 setup
這個作用域下調用,直接來看 RFC
中的例子:
<template>
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count \* 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>
其代碼結構看起來和 vue2
基本保持一致,不過有以下幾點需要注意:
-
setup
本身的調用時機,從目前的源碼來看,介於beforeCreate
和created
這兩個生命週期之間,言外之意,就是你無法在這裏使用this
指向當前組件實例 - 對於
state
的聲明,我們使用reactive
這個api
來構造,對於computed state
的聲明,使用computed
,兩者本質上均是Proxy
對象 - 對於組件方法的聲明,我們直接通過聲明普通函數的方式進行聲明即可,對於
state
的變更,直接通過閉包使用reactive
返回的Proxy
對象即可 -
setup
的返回值被稱作render context
,顧名思義,就是模板中可以訪問到的各種數據和方法的上下文對象,我在之前的文章中曾提及,模板在解析數據時,會優先考慮從data
對象取值,之後就是這個render context
了 - 除了返回
render context
,還可以返回模板渲染函數,對,就是那個h(...)
,這種形式對應的情況是,當我們沒有聲明template
屬性時
在 vue-next 中,我們直接從 vue
導入 reactive
、computed
以及其他的 api
即可,如果在 vue2
版本上,我們還可以通過使用 composition-api 這個 plugin
來使用這些 api
。
state
聲明 state 主要有以下幾種形式。
基礎類型
基礎類型可以通過 ref
這個 api
來聲明,如下:
import { ref } from "vue";
export default {
setup() {
const count = ref(0);
function inc() {
count.value++;
}
return { count, inc };
}
};
之所以要通過 ref
,是因爲 js
中的基礎類型傳值不是引用傳值,因此 vue-next
內部會自動將它封裝爲一個 ref
對象,其值指向原始值。當然,ref
指向引用類型也是沒有問題的,其 value
指向引用類型變量的引用。
引用類型
引用類型除了可以使用 ref
來聲明,也可以直接使用 reactive
,如下:
import { reactive } from "vue";
export default {
setup() {
const state = reactive({
count: 0
});
function inc() {
state.count++;
}
return { count: state.count, inc };
// 或者通過 toRefs 方法
// return { ...toRefs(state), inc };
}
};
props
對於 prop
可以通過如下代碼聲明及使用:
export default {
props: {
count: Number
},
setup(props) {
watch(() => {
console.log(\`count is: \` + props.count)
})
}
}
這裏可以發現其實和 vue2
中聲明的方式基本一樣,但值得注意的是,在 vue-next
中,props
的類型聲明不是必須的,如果你使用 typescript
,完全可以改寫爲如下的版本:
interface PropTypesI {
count: number
}
export default {
setup(props: PropTypesI) {
watch(() => {
console.log(\`count is: \` + props.count)
})
}
}
除此之外,還可以直接通過 watch
方法來觀察某個 prop
的變動,這是爲什麼呢?答案非常簡單,就是 props
本身在源碼中,也是一個被 reactive
包裹後的對象,因此它具有響應性,所以在 watch
方法中的回調函數會自動收集依賴,之後當 count
變動時,會自動調用這些回調邏輯。
context
在 setup
那一小節中,我們知道,setup
在調用時,組件實例還未創建,那意味着我們無法使用 this
訪問當前實例,那我想通過 this
在 vue2
中訪問一些內置屬性,怎麼辦?比如 attrs
或者 emit
。我們可以通過 setup
的第二個參數:
setup(props, context) {
do anything...
}
這個 context
對象也是一個 Proxy
對象,當我們通過 context.attrs
訪問其屬性時,本質上代理對象會將訪問指向組件的內部實例(即之間文章中提及的 componentInternalInstance
)。
生命週期
每一個 vue2
中的組件生命週期函數,當前都對應一個生命週期 hook
,比如:
import { onMounted, onUpdated, onUnmounted } from "vue";
setup() {
onMounted(() => { ... });
onUpdated(() => { ... });
onUnmounted(() => { ... });
}
這裏值得注意的一點在於,對於 beforeCreate
和 created
生命週期,雖然有響應的 hook
,但是我覺的沒有必要單獨使用了,因爲這些邏輯代碼大部分是一些初始化邏輯的代碼,直接寫在 setup
方法中即可。
如何複用代碼
在這個基礎上,複用代碼的方式也不再像 vue2
中的那樣,通過 mixin
或者 HOC
來達到複用代碼的目的,這裏稍微說一下,這些複用代碼方式中比較顯著的缺點有:
- 隱藏了數據來源,主要體現在
mixin
中 - 會犧牲一些性能,主要體現在
HOC
中 - 可能會遇到命名衝突問題,主要體現在
mixin
中
在 vue-next
中,複用代碼的邏輯本質上是利用這些 hook
來拆分業務邏輯與狀態,如果你比較熟悉 react hooks
的話,應該很快就能明白我指的是什麼意思。如果我們將邏輯都寫在 setup
方法中,很快代碼就會變得難以維護,在這方面,我們可以將一些耦合在一起的代碼抽離出來,同時以 use
前綴命名,聲明一個自定義的 hook
,如下:
export default {
setup() {
const data = useSearch()
const { sortedData, sort } = useSorting(data)
return { data, sortedData, sort }
}
}
function useSearch() {
...fetch data
}
function useSort(data) {
...sort data
}
除了以 inline
的方式來聲明,還可以將這些自定義的 hook
聲明在單獨的文件中,直接通過 import
導入即可,比如:
import useSearch from '@hooks/search'
import useSort from '@hooks/sort'
與 react hooks
對比
vue-next
在這方面借鑑了 react hooks
的設計思想,但是從實現層來講,它們是不一樣的,主要有以下幾點:
-
vue-next
不依賴於其調用順序,而react
依賴 -
vue-next
提供了生命週期方法,而react
刻意模糊生命週期的概念 -
vue-next
基於響應式系統實現,意味它的依賴不需要顯示聲明(而且是自動的),而react
需要手動聲明依賴數組
關注公衆號 全棧_101,只談技術,不談人生