Composition API(一)

本文整理來(lái)自深入Vue3+TypeScript技術(shù)棧-coderwhy大神新課,只作為個(gè)人筆記記錄使用,請(qǐng)大家多支持王紅元老師盾碗。

Composition API其實(shí)就是用來(lái)替代Mixin的税弃,我們先來(lái)學(xué)習(xí)一下Mixin降盹。

認(rèn)識(shí)Mixin

目前我們是使用組件化的方式在開(kāi)發(fā)整個(gè)Vue的應(yīng)用程序馅笙,但是組件和組件之間有時(shí)候會(huì)存在相同的代碼邏輯骏令,我們希望對(duì)相同的代碼邏輯進(jìn)行抽取蔬捷。

在Vue2和Vue3中都支持的一種方式就是使用Mixin來(lái)完成,Mixin提供了一種非常靈活的方式榔袋,來(lái)分發(fā)Vue組件中的可復(fù)用功能周拐,一個(gè)Mixin對(duì)象可以包含任何組件選項(xiàng),當(dāng)組件使用Mixin對(duì)象時(shí)凰兑,所有Mixin對(duì)象的選項(xiàng)將被混合進(jìn)入該組件本身的選項(xiàng)中妥粟。

Mixin的基本使用

組件中使用Mixin一般通過(guò)一個(gè)數(shù)組,因?yàn)榻M件中可能使用不止一個(gè)Mixin對(duì)象吏够。

Mixin的合并規(guī)則

如果Mixin對(duì)象中的選項(xiàng)和組件對(duì)象中的選項(xiàng)發(fā)生了沖突勾给,那么Vue會(huì)如何操作呢?這里分成不同的情況來(lái)進(jìn)行處理:

  • 情況一:如果是data函數(shù)的返回值對(duì)象
    返回值對(duì)象默認(rèn)情況下會(huì)進(jìn)行合并锅知,如果data返回值對(duì)象的屬性發(fā)生了沖突播急,那么會(huì)保留組件自身的數(shù)據(jù)。
  • 情況二:生命周期鉤子函數(shù)
    生命周期的鉤子函數(shù)會(huì)被合并到數(shù)組中喉镰,都會(huì)被調(diào)用旅择。
  • 情況三:值為對(duì)象的選項(xiàng)
    例如 methods惭笑、components 和 directives侣姆,將被合并為同一個(gè)對(duì)象,比如都有methods選項(xiàng)沉噩,并且都定義了方法捺宗,那么它們都會(huì)生效,但是如果對(duì)象的key相同川蒙,那么會(huì)取組件對(duì)象的鍵值對(duì)蚜厉。

全局混入Mixin

如果組件中的某些選項(xiàng),是所有的組件都需要擁有的畜眨,那么這個(gè)時(shí)候我們可以使用全局的mixin昼牛。全局的Mixin可以使用應(yīng)用app的方法 mixin 來(lái)完成注冊(cè)术瓮,一旦注冊(cè),那么全局混入的選項(xiàng)將會(huì)影響每一個(gè)組件贰健。使用全局mixin之后我們就不用在組件中一個(gè)一個(gè)寫(xiě)mixins: [sayHelloMixin]了胞四。

import { createApp } from 'vue';
import App from './01_mixin和extends/App.vue';

const app = createApp(App);

app.mixin({
  data() {
    return {}
  },
  methods: {
  },
  created() {
    console.log("全局的created生命周期");
  }
});

app.mount("#app");

extends

另外一個(gè)類似于Mixin的方式是通過(guò)extends屬性,extends是繼承的意思伶椿,繼承只會(huì)把組件的export default{}對(duì)象繼承過(guò)去辜伟,組件的html結(jié)構(gòu)不會(huì)繼承過(guò)去。

如下代碼脊另,左邊是BasePage.vue导狡,里面有個(gè)message屬性,在右邊組件中我們引入BasePage.vue偎痛,然后指定extends: BasePage旱捧,就可以使用message屬性了。

在實(shí)際開(kāi)發(fā)中ext ends用的非常少看彼,在Vue2中比較推薦大家使用Mixin廊佩,而在Vue3中推薦使用Composition API。

Options API的弊端

在Vue2中靖榕,我們編寫(xiě)組件的方式是Options API标锄,Options API的一大特點(diǎn)就是在對(duì)應(yīng)的屬性中編寫(xiě)對(duì)應(yīng)的功能模塊,比如data定義數(shù)據(jù)茁计、methods中定義方法料皇、computed中定義計(jì)算屬性、watch中監(jiān)聽(tīng)屬性改變星压,也包括生命周期鉤子践剂。但是這種代碼有一個(gè)很大的弊端:當(dāng)我們實(shí)現(xiàn)某一個(gè)功能時(shí),這個(gè)功能對(duì)應(yīng)的代碼邏輯會(huì)被拆分到各個(gè)屬性中娜膘。當(dāng)我們組件變得更大逊脯、更復(fù)雜時(shí),邏輯關(guān)注點(diǎn)的列表就會(huì)增長(zhǎng)竣贪,那么同一個(gè)功能的邏輯就會(huì)被拆分的很分散军洼,尤其對(duì)于那些一開(kāi)始沒(méi)有編寫(xiě)這些組件的人來(lái)說(shuō),這個(gè)組件的代碼是難以閱讀和理解的演怎。

下面我們來(lái)看一個(gè)非常大的組件匕争,其中的邏輯功能按照顏色進(jìn)行了劃分,這種碎片化的代碼使用理解和維護(hù)這個(gè)復(fù)雜的組件變得異常困難爷耀,并且隱藏了潛在的邏輯問(wèn)題甘桑,并且當(dāng)我們處理單個(gè)邏輯關(guān)注點(diǎn)時(shí),需要不斷的跳到相應(yīng)的代碼塊中。

大組件的邏輯分散

Options API:

Composition API:

如果我們能將同一個(gè)邏輯關(guān)注點(diǎn)相關(guān)的代碼收集在一起會(huì)更好跑杭,這就是Composition API想要做以及可以幫助我們完成的事情铆帽,所以也有人把Vue Composition API簡(jiǎn)稱為VCA。

認(rèn)識(shí)Composition API

為了開(kāi)始使用Composition API德谅,我們需要有一個(gè)可以實(shí)際使用它(編寫(xiě)代碼)的地方锄贼,在Vue組件中,這個(gè)位置就是 setup 函數(shù)女阀。setup其實(shí)就是組件的另外一個(gè)選項(xiàng)宅荤,只不過(guò)這個(gè)選項(xiàng)強(qiáng)大到我們可以用它來(lái)替代之前所編寫(xiě)的大部分其他選項(xiàng),比如methods浸策、computed冯键、watch、data庸汗、生命周期等等惫确。

setup函數(shù)的參數(shù)

我們先來(lái)研究一個(gè)setup函數(shù)的參數(shù),它主要有兩個(gè)參數(shù):props和context蚯舱。

props非常好理解改化,它其實(shí)就是父組件傳遞過(guò)來(lái)的屬性會(huì)被放到props對(duì)象中,我們?cè)趕etup中如果需要使用枉昏,那么就可以直接通過(guò)props參數(shù)獲瘸赂亍:

  • 對(duì)于定義props的類型,我們還是和之前的規(guī)則是一樣的兄裂,在props選項(xiàng)中定義句旱,并且在template中依然是可以正常去使用props中的屬性,比如message晰奖。
  • 如果我們?cè)趕etup函數(shù)中想要使用props谈撒,那么不可以通過(guò) this 去獲取(后面我會(huì)講到為什么)匾南,因?yàn)閜rops有直接作為參數(shù)傳遞到setup函數(shù)中啃匿,所以我們可以直接通過(guò)參數(shù)來(lái)使用即可,比如:props.message蛆楞。

另外一個(gè)參數(shù)是context溯乒,我們也稱之為是一個(gè)SetupContext,它里面包含三個(gè)屬性:

  • attrs:所有的非prop的attribute臊岸;
  • slots:父組件傳遞過(guò)來(lái)的插槽(這個(gè)在以渲染函數(shù)返回時(shí)才會(huì)有用橙数,后面會(huì)講到尊流,用的不多)帅戒;
  • emit:當(dāng)我們組件內(nèi)部需要發(fā)出事件時(shí)會(huì)用到emit(因?yàn)槲覀儾荒茉L問(wèn)this,所以不可以通過(guò) this.$emit發(fā)出事件);
// setup(props, context), 下面是對(duì)象解構(gòu)
setup(props, {attrs, slots, emit}) {
  console.log(props.message);
  console.log(attrs.id, attrs.class);
  console.log(slots);
  console.log(emit);
} 

setup函數(shù)的返回值

setup函數(shù)的返回值可以在模板template中被使用逻住,也就是說(shuō)我們可以通過(guò)setup的返回值來(lái)替代data選項(xiàng)钟哥。

// setup(props, context), 下面是對(duì)象解構(gòu)
setup(props, {attrs, slots, emit}) {
  console.log(props.message);
  console.log(attrs.id, attrs.class);
  console.log(slots);
  console.log(emit);

  //返回?cái)?shù)據(jù)對(duì)象
  return { 
    title: "Hello Home",
    counter: 100
  }
},

甚至是我們可以返回一個(gè)執(zhí)行函數(shù)來(lái)代替在methods中定義的方法,如下計(jì)數(shù)器的案例:

<template>
  <div>
    Home Page
    <h2>{{message}}</h2>

    <h2>{{title}}</h2>
    <h2>當(dāng)前計(jì)數(shù): {{counter}}</h2>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
  export default {
    props: {
      message: {
        type: String,
        required: true
      }
    },
    setup() {
      let counter = 100;

      // 局部函數(shù)
      const increment = () => {
        counter++;
        console.log(counter);
      }

      return {
        title: "Hello Home",
        counter,
        increment
      }
    }
  }
</script>

<style scoped>
</style>

但是瞎访,如果我們將 counter 進(jìn)行 increment 操作時(shí)腻贰,是否可以實(shí)現(xiàn)界面的響應(yīng)式呢?
答案是不可以扒秸。這是因?yàn)閷?duì)于一個(gè)定義的變量 (let counter = 100) 來(lái)說(shuō)播演,默認(rèn)情況下,Vue并不會(huì)跟蹤它的變化來(lái)引起界面的響應(yīng)式操作伴奥。我們以前在data()函數(shù)中定義的是響應(yīng)式的写烤,那是因?yàn)閂ue內(nèi)部通過(guò)reactive()函數(shù)包裹了一下。

setup不可以使用this

官方關(guān)于this有這樣一段描述:

表達(dá)的含義是this并沒(méi)有指向當(dāng)前組件實(shí)例拾徙,并且在setup被調(diào)用之前洲炊,data、computed尼啡、methods等都沒(méi)有被解析暂衡,所以無(wú)法在setup中獲取this。

其實(shí)之前的這段描述是和源碼有出入的崖瞭,coderwhy向官方提交了PR做出了描述的修改狂巢,后來(lái)coderwhy的PR也有被合并到官方文檔中。之前的描述大概含義是不可以使用this是因?yàn)榻M件實(shí)例還沒(méi)有被創(chuàng)建出來(lái)书聚。

其實(shí)Vue源碼是在調(diào)用createComponentInstance()方法之后再調(diào)用的setup()函數(shù)隧膘,所以調(diào)用setup()函數(shù)的時(shí)候組件實(shí)例肯定已經(jīng)創(chuàng)建出來(lái)了,只不過(guò)在setup()函數(shù)中沒(méi)有進(jìn)行任何this的綁定寺惫,所以不可以使用this疹吃。

coderwhy之前關(guān)于this的描述問(wèn)題

coderwhy是如何發(fā)現(xiàn)官方文檔的錯(cuò)誤的呢?

在閱讀源碼的過(guò)程中西雀,代碼是按照如下順序執(zhí)行的:

  1. 調(diào)用 createComponentInstance 創(chuàng)建組件實(shí)例萨驶;
  2. 調(diào)用 setupComponent 初始化 component 內(nèi)部的操作;
  3. 調(diào)用 setupStatefulComponent 初始化有狀態(tài)的組件艇肴;
  4. 在 setupStatefulComponent 取出了 setup 函數(shù)腔呜;
  5. 通過(guò) callWithErrorHandling 的函數(shù)執(zhí)行 setup;

從上面的代碼我們可以看出再悼,組件的instance肯定在執(zhí)行setup函數(shù)之前就已經(jīng)創(chuàng)建出來(lái)了核畴,只不過(guò)在setup()函數(shù)中沒(méi)有進(jìn)行任何this的綁定,所以不可以使用this冲九。

Reactive API

接著上面計(jì)數(shù)器的案例谤草,如果想要為在setup中定義的數(shù)據(jù)提供響應(yīng)式的特性,那么我們可以使用reactive函數(shù)。

<template>
  <div>
    Home Page
    <h2>{{message}}</h2>
    <h2>當(dāng)前計(jì)數(shù): {{state.counter}}</h2>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
  //先導(dǎo)入函數(shù)
  import { reactive } from 'vue';

  export default {
    props: {
      message: {
        type: String,
        required: true
      }
    },
    setup() {
      const state = reactive({
        counter: 100
      })

      // 局部函數(shù)
      const increment = () => {
        state.counter++;
        console.log(state.counter);
      }

      return {
        state,
        increment
      }
    }
  }
</script>

<style scoped>
</style>

那么這是什么原因呢丑孩?為什么就可以變成響應(yīng)式的呢冀宴?
這是因?yàn)楫?dāng)我們使用reactive函數(shù)處理我們的數(shù)據(jù)之后,數(shù)據(jù)再次被使用時(shí)就會(huì)進(jìn)行依賴收集温学,當(dāng)數(shù)據(jù)發(fā)生改變時(shí)略贮,所有收集到的依賴都是進(jìn)行對(duì)應(yīng)的響應(yīng)式操作(比如更新界面)。事實(shí)上仗岖,我們編寫(xiě)的data選項(xiàng)逃延,也是在內(nèi)部交給了reactive函數(shù)將其編程響應(yīng)式對(duì)象的。

上面的reactive函數(shù)要求傳入的必須是對(duì)象或數(shù)組,所以上面,即使我們只有counter: 100芋哭,也需要包裹成對(duì)象,就顯得很麻煩盔然,這時(shí)候我們可以使用Ref API。

Ref API

reactive函數(shù)對(duì)傳入的類型是有限制的是嗜,它要求我們必須傳入的是一個(gè)對(duì)象或者數(shù)組類型愈案,如果我們傳入一個(gè)基本數(shù)據(jù)類型(String、Number鹅搪、Boolean)會(huì)報(bào)一個(gè)警告:

這個(gè)時(shí)候Vue3給我們提供了另外一個(gè)Ref API站绪,ref 會(huì)返回一個(gè)可變的響應(yīng)式對(duì)象,該對(duì)象作為一個(gè)響應(yīng)式的引用維護(hù)著它內(nèi)部的值丽柿,這就是 ref 名稱的來(lái)源恢准,它內(nèi)部的值是在 ref 的 value 屬性中被維護(hù)的。

let counter = ref(100);

這里有兩個(gè)注意事項(xiàng):

  1. 在模板中引入ref的值時(shí)甫题,Vue會(huì)自動(dòng)幫助我們進(jìn)行解包操作馁筐,所以我們并不需要在模板中通過(guò) ref.value 的方式來(lái)使用。
  2. 但是在 setup 函數(shù)內(nèi)部坠非,它依然是一個(gè) ref 引用敏沉, 所以對(duì)其進(jìn)行操作時(shí),我們依然需要使用 ref.value的方式炎码。
<template>
  <div>
    Home Page
    <h2>{{message}}</h2>
    <!-- 當(dāng)我們?cè)趖emplate模板中使用ref對(duì)象, 它會(huì)自動(dòng)進(jìn)行解包 -->
    <h2>當(dāng)前計(jì)數(shù): {{counter}}</h2>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
  //先導(dǎo)入函數(shù)
  import { ref } from 'vue';

  export default {
    props: {
      message: {
        type: String,
        required: true
      }
    },
    setup() {
      // counter編程一個(gè)ref的可響應(yīng)式的引用
      let counter = ref(100);

      // 局部函數(shù)
      const increment = () => {
        // 在setup中就要通過(guò).value訪問(wèn)
        counter.value++;
        console.log(counter.value);
      }

      return {
        counter,
        increment
      }
    }
  }
</script>

<style scoped>
</style>

Ref的淺層解包

模板中的解包是淺層解包盟迟,比如:我們用ref包裹一個(gè)"Hello World",賦值給message潦闲,再把message放到一個(gè)普通info對(duì)象里面攒菠,這時(shí)候在模板中我們只能使用info.message.value來(lái)獲取"Hello World",直接使用info.message獲取的就只是一個(gè)包裹對(duì)象歉闰,而不是包裹對(duì)象里面的"Hello World"辖众。

如果我們把上面的info對(duì)象用reactive函數(shù)包裹一下卓起,那么在模板中使用時(shí),它會(huì)自動(dòng)解包:

總結(jié):

  • 如果ref對(duì)象沒(méi)被其他對(duì)象包裹赵辕,在模板中使用會(huì)自動(dòng)解包。
  • 如果ref對(duì)象有被其他對(duì)象包裹概龄,如果包裹的是普通對(duì)象还惠,在模板中使用時(shí),不會(huì)自動(dòng)解包私杜,如果是reactive函數(shù)包裹的對(duì)象蚕键,則會(huì)自動(dòng)解包。
  • 但是在 setup 函數(shù)內(nèi)部衰粹,它依然是一個(gè) ref 引用锣光, 所以對(duì)其進(jìn)行操作時(shí),我們依然需要使用 ref.value的方式铝耻。

認(rèn)識(shí)readonly

我們通過(guò) reactive 或者 ref 可以獲取到一個(gè)響應(yīng)式的對(duì)象誊爹,但是某些情況下,我們傳入給其他地方(組件)的這個(gè)響應(yīng)式對(duì)象希望在另外一個(gè)地方(組件)能被使用瓢捉,但是不能被修改频丘,這個(gè)時(shí)候如何防止這種情況的出現(xiàn)呢?

Vue3為我們提供了 readonly 的方法泡态,readonly會(huì)返回原生對(duì)象的只讀代理(也就是它依然是一個(gè)Proxy搂漠,這個(gè) proxy 的 set 方法被劫持,并且不能對(duì)其進(jìn)行修改)某弦。

在開(kāi)發(fā)中常見(jiàn)的 readonly 方法會(huì)傳入三個(gè)類型的參數(shù):
類型一:普通對(duì)象桐汤;
類型二:reactive返回的對(duì)象;
類型三:ref的對(duì)象靶壮;

readonly的使用

readonly返回的對(duì)象都是不允許修改的怔毛,但是經(jīng)過(guò)readonly處理的原來(lái)的對(duì)象是允許被修改的。比如 const info = readonly(obj)腾降,info對(duì)象是不允許被修改的馆截,obj可以被修改,當(dāng)obj被修改時(shí)蜂莉,readonly返回的info對(duì)象也會(huì)被修改蜡娶,所以一般我們會(huì)把info傳遞給其他組件使用。

<template>
  <div>
    <button @click="updateState">修改狀態(tài)</button>
  </div>
</template>

<script>
  // 導(dǎo)入函數(shù)
  import { reactive, ref, readonly } from 'vue';

  export default {
    setup() {
      // 1.普通對(duì)象
      const info1 = {name: "why"};
      const readonlyInfo1 = readonly(info1);

      // 2.響應(yīng)式的對(duì)象reactive
      const info2 = reactive({
        name: "why"
      })
      const readonlyInfo2 = readonly(info2);

      // 3.響應(yīng)式的對(duì)象ref
      const info3 = ref("why");
      const readonlyInfo3 = readonly(info3);

      const updateState = () => {
        // info1.name = "coderwhy"; 可修改
        // info2.name = "coderwhy"; 可修改
        // info3.value = "coderwhy"; 可修改

        // readonlyInfo1.name = "coderwhy" 不可修改 非響應(yīng)式
        // readonlyInfo2.name = "coderwhy" 不可修改 響應(yīng)式
        // readonlyInfo3.value = "coderwhy" 不可修改 響應(yīng)式
      }

      return {
        updateState,
      }
    }
  }
</script>

<style scoped>
</style>

在我們傳遞給其他組件數(shù)據(jù)時(shí)映穗,往往希望其他組件使用我們傳遞的內(nèi)容窖张,但是不允許它們修改,這時(shí)就可以使用readonly了蚁滋,而且我們希望子組件使用的數(shù)據(jù)是響應(yīng)式的宿接,所以我們可以使用reactive函數(shù)或者ref赘淮。

Reactive判斷的API

  • isProxy:檢查對(duì)象是否是由 reactive 或 readonly創(chuàng)建的 proxy。
  • isReactive:檢查對(duì)象是否是由 reactive創(chuàng)建的響應(yīng)式代理睦霎,如果該代理是 readonly 建的梢卸,但包裹了由 reactive 創(chuàng)建的另一個(gè)代理,它也會(huì)返回 true副女。
  • isReadonly:檢查對(duì)象是否是由 readonly 創(chuàng)建的只讀代理蛤高。
  • toRaw:返回 reactive 或 readonly 代理的原始對(duì)象(不建議保留對(duì)原始對(duì)象的持久引用,請(qǐng)謹(jǐn)慎使用)碑幅。
  • shallowReactive:翻譯過(guò)來(lái)就是淺層響應(yīng)式戴陡,創(chuàng)建一個(gè)響應(yīng)式代理,它跟蹤其自身 property 的響應(yīng)性沟涨,但不執(zhí)行嵌套對(duì)象的深層響應(yīng)式轉(zhuǎn)換(深層還是原生對(duì)象)恤批。比如:對(duì)象里面還有對(duì)象,如果我們希望外面的屬性改變才是響應(yīng)式的裹赴,里面深層對(duì)象的改變不是響應(yīng)式的喜庞,這時(shí)候可以用shallowReactive。
  • shallowReadonly:淺層只讀棋返,創(chuàng)建一個(gè) proxy赋荆,使其自身的 property 為只讀,但不執(zhí)行嵌套對(duì)象的深度只讀轉(zhuǎn)換(深層還是可讀可寫(xiě)的)懊昨。

toRefs

如果我們使用ES6的解構(gòu)語(yǔ)法窄潭,對(duì)reactive返回的對(duì)象進(jìn)行解構(gòu)獲取值,那么之后無(wú)論是修改結(jié)構(gòu)后的變量酵颁,還是修改reactive返回的state對(duì)象嫉你,數(shù)據(jù)都不再是響應(yīng)式的。

這是因?yàn)榻鈽?gòu)其實(shí)就相當(dāng)于重新聲明變量然后賦值躏惋,如下:

//解構(gòu)
const { name, age } = state;
//相當(dāng)于如下
const name = "why";
const age = 18;

那么有沒(méi)有辦法讓我們解構(gòu)出來(lái)的屬性是響應(yīng)式的呢幽污?
Vue為我們提供了一個(gè)toRefs函數(shù),可以將reactive返回的對(duì)象中的屬性都轉(zhuǎn)成ref簿姨,那么我們?cè)俅芜M(jìn)行結(jié)構(gòu)出來(lái)的 name 和 age 本身都是 ref的距误。

然后在模板中我們直接使用name、age就行扁位,因?yàn)槟0逯袝?huì)自動(dòng)解構(gòu)准潭,但是在下面的邏輯代碼中我們還是要使用 .value 來(lái)修改值:

const changeAge = () => {
  age.value++;
}

這種做法相當(dāng)于已經(jīng)在 state.name 和 ref.value 之間建立了鏈接,任何一個(gè)修改都會(huì)引起另外一個(gè)變化域仇。

toRef

上面的toRefs是將reactive對(duì)象中的所有屬性都轉(zhuǎn)成ref刑然,建立鏈接,但是有時(shí)候有些屬性我們用不到暇务,這就額外增加了不必要的開(kāi)銷(xiāo)泼掠。

如果我們只希望轉(zhuǎn)換一個(gè)reactive對(duì)象中的屬性為ref怔软,那么可以使用toRef的方法:

//普通的解構(gòu)
let { name } = state;
//解構(gòu)age,并建立連接,第一個(gè)參數(shù)是對(duì)象,第二個(gè)參數(shù)是對(duì)象的屬性名
let age = toRef(state, "age");

ref其他的API

  • unref:如果我們想要獲取一個(gè)ref引用中的value,那么也可以通過(guò)unref方法择镇。如果參數(shù)是一個(gè) ref挡逼,則返回內(nèi)部值,否則返回參數(shù)本身腻豌,這是val = isRef(val) ? val.value : val的語(yǔ)法糖函數(shù)家坎。
  • isRef:判斷值是否是一個(gè)ref對(duì)象。
  • shallowRef:創(chuàng)建一個(gè)淺層的ref對(duì)象饲梭。
  • triggerRef:手動(dòng)觸發(fā)和 shallowRef 相關(guān)聯(lián)的副作用乘盖。

默認(rèn)情況下焰檩,不管是reactive還是ref創(chuàng)建的響應(yīng)式對(duì)象都是深層次的憔涉,如下:

const info = ref({name: "why"})

const changeInfo = () => {
  //通過(guò)value拿到原對(duì)象,再修改原對(duì)象的值,這時(shí)候界面的數(shù)據(jù)也會(huì)改變,這就是深層次的響應(yīng)式
  info.value.name = "james";
}

如果我們不希望深層次的響應(yīng)式,只希望改變外面大的對(duì)象的時(shí)候才是響應(yīng)式的析苫,改變里面的屬性值不是響應(yīng)式的兜叨,我們就可以使用shallowRef。
如果我們又想觸發(fā)響應(yīng)式了衩侥,就可以調(diào)用triggerRef国旷,來(lái)手動(dòng)觸發(fā)相關(guān)聯(lián)的副作用,這時(shí)候界面就又變成響應(yīng)式的了茫死。

//淺層次的響應(yīng)式
const info = shallowRef({name: "why"})

const changeInfo = () => {
  //這時(shí)候修改里面的屬性值,界面就不會(huì)變化了
  info.value.name = "james";
  //如果我們又想觸發(fā)響應(yīng)式,就可以調(diào)用triggerRef,來(lái)觸發(fā)相關(guān)聯(lián)的副作用,這時(shí)候界面就又變成響應(yīng)式的了
  triggerRef(info);
}

customRef

下面代碼跪但,我們?cè)谳斎肟蛑休斎胛淖郑旅骘@示的文字會(huì)立馬更新:

<template>
  <div>
    <input v-model="message"/>
    <h2>{{message}}</h2>
  </div>
</template>

<script>
  import ref from 'vue';

  export default {
    setup() {
      const message = ref("Hello World");

      return {
        message
      }
    }
  }
</script>

<style scoped>
</style>

如果我們不想更新這么頻繁峦萎,比如輸入后300ms才更新屡久,那么使用ref就做不到了,所以我們需要自定義ref爱榔。

創(chuàng)建一個(gè)自定義的ref被环,并對(duì)其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行顯示控制。它需要一個(gè)工廠函數(shù)详幽,該函數(shù)接受 track 和 trigger 函數(shù)作為參數(shù)筛欢,并且應(yīng)該返回一個(gè)帶有 get 和 set 的對(duì)象。

自定義ref的useDebounceRef.js文件代碼如下:

import { customRef } from 'vue';

// 自定義ref
export default function(value, delay = 300) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        //收集依賴
        track();
        return value;
      },
      set(newValue) {
        //如果在300ms內(nèi)又輸入值了唇聘,就把定時(shí)器清空版姑,也就是取消觸發(fā)更新
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          //觸發(fā)更新
          trigger();
        }, delay);
      }
    }
  })
}

然后使用我們自定義的ref,就可以達(dá)到我們想要的延遲效果了迟郎,這樣做可以提升一點(diǎn)點(diǎn)性能漠酿。

<template>
<div>
  <input v-model="message"/>
  <h2>{{message}}</h2>
</div>
</template>

<script>
import debounceRef from './hook/useDebounceRef';

export default {
  setup() {
    const message = debounceRef("Hello World");

    return {
      message
    }
  }
}
</script>

<style scoped>
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谎亩,隨后出現(xiàn)的幾起案子炒嘲,更是在濱河造成了極大的恐慌宇姚,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夫凸,死亡現(xiàn)場(chǎng)離奇詭異浑劳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)夭拌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)魔熏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鸽扁,你說(shuō)我怎么就攤上這事蒜绽。” “怎么了桶现?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵躲雅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我骡和,道長(zhǎng)相赁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任慰于,我火速辦了婚禮钮科,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婆赠。我一直安慰自己绵脯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布休里。 她就那樣靜靜地躺著蛆挫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪份帐。 梳的紋絲不亂的頭發(fā)上璃吧,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音废境,去河邊找鬼畜挨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛噩凹,可吹牛的內(nèi)容都是我干的巴元。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼驮宴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逮刨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤修己,失蹤者是張志新(化名)和其女友劉穎恢总,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體睬愤,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡片仿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尤辱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砂豌。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖光督,靈堂內(nèi)的尸體忽然破棺而出阳距,到底是詐尸還是另有隱情,我是刑警寧澤结借,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布筐摘,位于F島的核電站,受9級(jí)特大地震影響映跟,放射性物質(zhì)發(fā)生泄漏蓄拣。R本人自食惡果不足惜扬虚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一努隙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜昵,春花似錦荸镊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至舀锨,卻和暖如春岭洲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坎匿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工盾剩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人替蔬。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓告私,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親承桥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驻粟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容