Vue 3的組合式API以及ref語法糖學習

組合式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步:

  1. 賦值在頂層:必須在setup頂層執行賦值:
const instance = getCurrentInstance();
  1. 使用在周期鉤子里:必須在周期鉤子里使用,不得在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()。

向父組件派發事件

  1. setup(props, context) {}常規寫法的話,setup的第二個參數就是干這個的:
setup(props, context) {
  context.emit('ooxx')
}

或者解構:

setup(props, { emit }) {
  emit('ooxx')
}
  1. 語法糖寫法的話,先const instance = getCurrentInstance();,然后在生命周期鉤子或者用戶觸發事件里寫上instance.emit("xxx");即可。

如何導出解構的Proxy

如果要解構Proxy,分2種情況討論:

  1. 如果property是基本類型數據,會丟失響應,這時候應當先const ref = toRefs(Proxy),然后在return里解構ref變量。

  2. 如果你確定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里用不到,此時的最佳實踐:

  1. 盡量避免定義這種變量

  2. 給這種變量的變量名前面寫上下劃線,表明是私有變量

<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的鍋,因為任何編程方式都有最佳實踐一說:

  1. data方面

忘記古老的var時代的最佳實踐。在古代,var聲明的變量存在變量提升現象,為了防止愚蠢的程序員出了bug還搞不清為什么出bug,所以最佳實踐建議var聲明變量永遠寫在作用域最頂部?,F在是let時代,并不需要這種最佳實踐,所以應該就近聲明變量。這樣也就不會有所謂“data區域”了。

計算變量也是同樣道理。

  1. 周期鉤子方面

因為現在可以疊加設置周期鉤子,相信很少有蠢人會只定義一個鉤子。

鉤子應緊隨業務,不要在本業務的鉤子里寫其他業務的內容。

  1. methods方面

methods的本質就是函數,函數聲明也應做到就近聲明,就近使用,千萬不要把函數故意提升到作用域頂部。

  1. 公用代碼方面

應當放在頂部,這是起碼的最佳實踐。

  1. 拆分組件

你一個組件5千行,你還不拆分,你要作死啊?

  1. 多用混入等其他手段

就不多說了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • setup setup函數是一個新的組件選項。作為在組件內使用 Composition API 的入口點。 調用時...
    裘馬輕狂大帥閱讀 1,675評論 0 0
  • 因為這個月的月初給自己定了個小目標,學完Vue3的基本使用,并使用Vue3親手做一個小項目(稍微透露一下,我制作的...
    1kesou閱讀 4,821評論 0 8
  • Vue3官網文檔[https://vue3js.cn/docs/zh/guide/introduction.htm...
    一蘇沨來閱讀 3,353評論 0 7
  • Vue講解 Vue 是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層...
    艾曼大山閱讀 11,889評論 7 109
  • 重構了虛擬DOM setup是干啥的?setup實際上是一個組件的入口,它運行在組件被實例化時候,props 屬性...
    微芒不朽閱讀 746評論 0 0