組合式API與配置項式API混合使用注意事項
混合使用的話,
setup(props, context) {}
應作為一個配置項API的一個配置項,然后將它視為Vue 2的beforeCreate周期,在注冊components和props之后、在其他配置項加載之前執行。有人說
setup(props, context) {}
也可以視為created周期,你最好別這么視為,這會引起很多誤解。setup(props, context) {}
所return的數據和方法如果與其他配置項的數據和方法重名,以setup() {}為準。無論書寫順序,其他配置項都無法覆蓋setup(props, context) {}
的數據和方法。混合使用的話,不可以使用ref語法糖。
import { reactive, ref, watch } from "vue"
里面的這些方法可以在配置項API里使用,但是通常意義不大,因為配置項API基本沿用Vue 2的寫法(有些區別,見官方文檔遷移辦法)。
setup(props, context) {}里為何不可用this關鍵字?
官方說的很清楚:
在setup(props, context) {}內部,this不會是該活躍實例的引用,因為setup()是在解析其它組件選項之前被調用的,所以setup()內部的this的行為與其它選項中的this完全不同。
setup(props, context) {}的this指向window,composition的文檔中也沒有提到怎么獲取組件實例,其實方法是:咱們可以通過getCurrentInstance()這個接口獲取Vue實例。
如何取得Vue實例?
用getCurrentInstance API,使用方法分2步:
- 賦值在頂層:必須在setup頂層執行賦值:
const instance = getCurrentInstance();
- 使用在周期鉤子里:必須在周期鉤子里使用,不得在setup里使用:
雖然const instance = getCurrentInstance()
必須在頂層,但是此時獲取不到上下文,必須寫到onBeforeMount(() => {})
或其他周期函數里才能獲取上下文。
setup(props, context) {
const instance = getCurrentInstance();
onBeforeMount(() => {
console.log(instance.data);
});
}
如何訪問this、配置項的data和methods
訪問組件實例this、配置項data、配置項methods的辦法:
使用組件實例this,就用
instance.proxy
。訪問配置項的data,就用
instance.data
,它具備響應式。執行配置項的methods,就用
instance.ctx.foo()
。
向父組件派發事件
-
setup(props, context) {}
常規寫法的話,setup的第二個參數就是干這個的:
setup(props, context) {
context.emit('ooxx')
}
或者解構:
setup(props, { emit }) {
emit('ooxx')
}
- 語法糖寫法的話,先
const instance = getCurrentInstance();
,然后在生命周期鉤子或者用戶觸發事件里寫上instance.emit("xxx");
即可。
如何導出解構的Proxy
如果要解構Proxy,分2種情況討論:
如果property是基本類型數據,會丟失響應,這時候應當先
const ref = toRefs(Proxy)
,然后在return里解構ref變量。如果你確定Proxy所有一級property的值都是引用類型,可以放心解構Proxy。
生命周期鉤子可以寫到函數里
函數里可以寫生命周期鉤子,只要函數比周期執行的早就可以:
function fun1() {
// 這里可以用onMounted執行代碼
onMounted(() => {})
}
Ref語法糖
實現的前提是:首先給script標簽加上setup標記,也就是<script setup>,然后,不允許再使用配置項式API。
不再需要寫export default{}
這很棒。
原本是:
<script>
import {onBeforeMount, onMounted} from 'vue'
export default {
name: 'App',
setup() {
onBeforeMount(() => {
// 在掛載前執行某些代碼
});
onMounted(() => {
// 在掛載后執行某些代碼
});
return {};
}
}
</script>
現在是:
<script setup>
import { reactive, onBeforeMount, onMounted } from "vue";
const a = reactive({ x: 1 });
onBeforeMount(() => {
a.x = 2;
});
onMounted(() => {
a.x = 3;
});
</script>
直接向<template>暴露頂層變量
編譯器會自動收集頂層變量,然后return,所以你不需要寫return
,如果寫了反而報錯。這很棒。
有些變量在template里用不到,此時的最佳實踐:
盡量避免定義這種變量
給這種變量的變量名前面寫上下劃線,表明是私有變量
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
</div>
</template>
<script setup>
import { reactive } from "vue";
let r = reactive({ a: 1, b: { c: 2 } });
</script>
ref標簽
ref: t = 10;
約等價于let t = ref(10)
,依然有些區別,先看例子:
ref: t = 10;
console.log(t); // 10
console.log(t + 1); // 11
console.log(t.value); // undefined
console.log($t); // ref對象
console.log($t + 1); // [object Object]1
console.log($t.value + 1); // 11
也就是說,聲明ref: t = 10
之后:
- t返回ref對象的內部值,而不是ref對象本身
- t.value返回undefined
- t前加$符號返回ref對象
- $t.value返回內部值
- t對于template來講是ref對象
這個ref:
標簽是社區最大的爭議點,它的好處是你可以在js里把t當做內部值來隨意運算,而template也自動把它當做ref對象。當你需要在js里調用ref對象的時候,你可以用$t
,雖然加$
也是一種心智負擔,但是這種需求概率太低了,因為你極少需要獲取ref對象。這就是Vue創始人推薦使用ref:
標簽的理由。
我建議盡量使用ref:
標簽,除非你真的很討厭這種語法糖。
使用<script setup>標簽,如何獲取props和context?
沒使用<script setup>標簽的時候,setup(props, context) {}
自帶參數用來獲取props和context,現在setup(props, context) {}
已經省略了,如何獲取props和context呢?
1. 使用useContext
獲取context
useContext()返回的內容跟不用<script setup>標簽時候setup(props, context) {}
的context的返回內容完全一致。
console.log(useContext());
2. 使用defineProps
接收props
defineProps({
msg: String
})
或:
defineProps({
msg: {
type: String,
default: ''
})
3. 借助useContext
使用props
const ctx = useContext();
console.log(ctx.props);
4. 允許直接修改props
Vue 2中不允許直接修改props,得用this.$emit,但是Vue 3允許直接修改,只不過修改的是組件內props的值,并不會影響父組件的傳入值。
有時候直接修改props很有用,比如父組件將ajax的結果數據給子組件,子組件修改數據并不需要通知父組件,父組件也沒興趣知道修改成什么,這時候子組件直接修改數據即可。
5. 不要解構props
最好不要解構props,也就是說最好分開傳入props,如果props的property是基本類型數據,會導致響應式丟失,對它的修改會反映不到DOM上。
在setup中調用store
在Vue2中使用 Vuex,我們都是通過this.$store
來與獲取到Vuex實例,但上一部分說了原本Vue2中的this
的獲取方式不一樣了,并且我們在Vue3的getCurrentInstance().proxy
中也沒有發現$store
這個屬性,那么如何獲取到Vuex實例呢?這就要通過vuex
中的一個方法了,即useStore
。
// store 文件夾下的 index.js
import Vuex from 'vuex'
const store = Vuex.createStore({
state: {
name: '王XX',
age: 18
},
mutations: {
// ……
},
// ……
})
// example.vue
<script>
// 從 vuex 中導入 useStore 方法
import {useStore} from 'vuex'
export default {
setup() {
// 獲取 vuex 實例
const store = useStore()
console.log(store)
}
}
</script>
然后接下來就可以像之前一樣正常使用vuex
了。
如何避免用配置項思維編寫組合式代碼?
有人說,很多人編寫組合式API依然習慣用配置項思維,最終還是會形成data區域、computed區域、周期鉤子區域、methods區域等。如果趕上“隨緣”性格的程序員,他會連區域都不分,只會東一榔頭西一棒槌式的隨意編寫,使用組合式API反而更亂。
其實,這不是組合式API的鍋,因為任何編程方式都有最佳實踐一說:
- data方面
忘記古老的var
時代的最佳實踐。在古代,var
聲明的變量存在變量提升現象,為了防止愚蠢的程序員出了bug還搞不清為什么出bug,所以最佳實踐建議var
聲明變量永遠寫在作用域最頂部?,F在是let
時代,并不需要這種最佳實踐,所以應該就近聲明變量。這樣也就不會有所謂“data區域”了。
計算變量也是同樣道理。
- 周期鉤子方面
因為現在可以疊加設置周期鉤子,相信很少有蠢人會只定義一個鉤子。
鉤子應緊隨業務,不要在本業務的鉤子里寫其他業務的內容。
- methods方面
methods的本質就是函數,函數聲明也應做到就近聲明,就近使用,千萬不要把函數故意提升到作用域頂部。
- 公用代碼方面
應當放在頂部,這是起碼的最佳實踐。
- 拆分組件
你一個組件5千行,你還不拆分,你要作死啊?
- 多用混入等其他手段
就不多說了。