日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

父組件使用v-model,子組件竟然不用定義props和emit拋出事件

來源: 責編: 時間:2024-04-08 08:59:54 186觀看
導讀前言vue3.4增加了defineModel宏函數,在子組件內修改了defineModel的返回值,父組件上v-model綁定的變量就會被更新。大家都知道v-model是:modelValue和@update:modelValue的語法糖,但是你知道為什么我們在子組件內沒有寫

前言

vue3.4增加了defineModel宏函數,在子組件內修改了defineModel的返回值,父組件上v-model綁定的變量就會被更新。大家都知道v-model是:modelValue和@update:modelValue的語法糖,但是你知道為什么我們在子組件內沒有寫任何關于props的定義和emit事件觸發的代碼嗎?還有在template渲染中defineModel的返回值等于父組件v-model綁定的變量值,那么這個返回值是否就是名為modelValue的props呢?直接修改defineModel的返回值就會修改父組件上面綁定的變量,那么這個行為是否相當于子組件直接修改了父組件的變量值,破壞了vue的單向數據流呢?MTZ28資訊網——每日最新資訊28at.com

先說答案

defineModel宏函數經過編譯后會給vue組件對象上面增加modelValue的props選項和update:modelValue的emits選項,執行defineModel宏函數的代碼會變成執行useModel函數,如下圖:MTZ28資訊網——每日最新資訊28at.com

圖片圖片MTZ28資訊網——每日最新資訊28at.com

經過編譯后defineModel宏函數已經變成了useModel函數,而useModel函數的返回值是一個ref對象。注意這個是ref對象不是props,所以我們才可以在組件內直接修改defineModel的返回值。當我們對這個ref對象進行“讀操作”時,會像Proxy一樣被攔截到ref對象的get方法。在get方法中會返回本地維護localValue變量,localValue變量依靠watchSyncEffect讓localValue變量始終和父組件傳遞的modelValue的props值一致。MTZ28資訊網——每日最新資訊28at.com

對返回值進行“寫操作”會被攔截到ref對象的set方法中,在set方法中會將最新值同步到本地維護localValue變量,調用vue實例上的emit方法拋出update:modelValue事件給父組件,由父組件去更新父組件中v-model綁定的變量。如下圖:MTZ28資訊網——每日最新資訊28at.com

圖片圖片MTZ28資訊網——每日最新資訊28at.com

所以在子組件內無需寫任何關于props的定義和emit事件觸發的代碼,因為在編譯defineModel宏函數的時候已經幫我們生成了modelValue的props選項。在對返回的ref變量進行寫操作時會觸發set方法,在set方法中會調用vue實例上的emit方法拋出update:modelValue事件給父組件。MTZ28資訊網——每日最新資訊28at.com

defineModel宏函數的返回值是一個ref變量,而不是一個props。所以我們可以直接修改defineModel宏函數的返回值,父組件綁定的變量之所以會改變是因為在底層會拋出update:modelValue事件給父組件,由父組件去更新綁定的變量,這一行為當然滿足vue的單向數據流。MTZ28資訊網——每日最新資訊28at.com

什么是vue的單向數據流

vue的單向數據流是指,通過props將父組件的變量傳遞給子組件,在子組件中是沒有權限去修改父組件傳遞過來的變量。只能通過emit拋出事件給父組件,讓父組件在事件回調中去修改props傳遞的變量,然后通過props將更新后的變量傳遞給子組件。在這一過程中數據的流動是單向的,由父組件傳遞給子組件,只有父組件有數據的更改權,子組件不可直接更改數據。MTZ28資訊網——每日最新資訊28at.com

圖片圖片MTZ28資訊網——每日最新資訊28at.com

一個defineModel的例子

我在前面的 一文搞懂 Vue3 defineModel 雙向綁定:告別繁瑣代碼!文章中已經講過了defineModel的各種用法,在這篇文章中我們就不多余贅述了。我們直接來看一個簡單的defineModel的例子。MTZ28資訊網——每日最新資訊28at.com

下面這個是父組件的代碼:MTZ28資訊網——每日最新資訊28at.com

<template>  <CommonChild v-model="inputValue" />  <p>input value is: {{ inputValue }}</p></template><script setup lang="ts">import { ref } from "vue";import CommonChild from "./child.vue";const inputValue = ref();</script>

父組件的代碼很簡單,使用v-model指令將inputValue變量傳遞給子組件。然后在父組件上使用p標簽渲染出inputValue變量的值。MTZ28資訊網——每日最新資訊28at.com

我們接下來看子組件的代碼:MTZ28資訊網——每日最新資訊28at.com

<template>  <input v-model="model" />  <button @click="handelReset">reset</button></template><script setup lang="ts">const model = defineModel();function handelReset() {  model.value = "init";}</script>

子組件內的代碼也很簡單,將defineModel的返回值賦值給model變量。然后使用v-model指令將model變量綁定到子組件的input輸入框上面。并且還在按鈕的click事件時使用model.value = "init"將綁定的值重置為init字符串。請注意在子組件中我們沒有任何定義props的代碼,也沒有拋出emit事件的代碼。而是通過defineModel宏函數的返回值來接收父組件傳過來的名為modelValue的prop,并且在子組件中是直接通過給defineModel宏函數的返回值進行賦值來修改父組件綁定的inputValue變量的值。MTZ28資訊網——每日最新資訊28at.com

defineModel編譯后的樣子

要回答前面提的幾個問題,我們還是得從編譯后的子組件代碼說起。下面這個是經過簡化編譯后的子組件代碼:MTZ28資訊網——每日最新資訊28at.com

import {  defineComponent as _defineComponent,  useModel as _useModel} from "/node_modules/.vite/deps/vue.js?v=23bfe016";const _sfc_main = _defineComponent({  __name: "child",  props: {    modelValue: {},    modelModifiers: {},  },  emits: ["update:modelValue"],  setup(__props) {    const model = _useModel(__props, "modelValue");    function handelReset() {      model.value = "init";    }    const __returned__ = { model, handelReset };    return __returned__;  },});function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {  return (    // ... 省略  );}_sfc_main.render = _sfc_render;export default _sfc_main;

從上面我們可以看到編譯后主要有_sfc_main和_sfc_render這兩塊,其中_sfc_render為render函數,不是我們這篇文章關注的重點。我們來主要看_sfc_main對象,看這個對象的樣子有name、props、emits、setup屬性,我想你也能夠猜出來他就是vue的組件對象。從組件對象中我們可以看到已經有了一個modelValue的props屬性,還有使用emits選項聲明了update:modelValue事件。我們在源代碼中沒有任何地方有定義props和emits選項,很明顯這兩個是通過編譯defineModel宏函數而來的。MTZ28資訊網——每日最新資訊28at.com

我們接著來看里面的setup函數,可以看到經過編譯后的setup函數中代碼和我們的源代碼很相似。只有defineModel不在了,取而代之的是一個useModel函數。MTZ28資訊網——每日最新資訊28at.com

// 編譯前的代碼const model = defineModel();// 編譯后的代碼const model = _useModel(__props, "modelValue");

還是同樣的套路,在瀏覽器的sources面板上面找到編譯后的js文件,然后給這個useModel打個斷點。至于如何找到編譯后的js文件我們在前面的文章中已經講了很多遍了,這里就不贅述了。刷新瀏覽器我們看到斷點已經走到了使用useModel函數的地方,我們這里給useModel函數傳了兩個參數。第一個參數為子組件接收的props對象,第二個參數是寫死的字符串modelValue。進入到useModel函數內部,簡化后的useModel函數是這樣的:MTZ28資訊網——每日最新資訊28at.com

function useModel(props, name) {  const i = getCurrentInstance();  const res = customRef((track2, trigger2) => {    watchSyncEffect(() => {      // 省略    });  });  return res;}

從上面的代碼中我們可以看到useModel中使用到的函數沒有一個是vue內部源碼專用的函數,全都是調用的vue暴露出來的API。這意味著我們可以參考defineModel的實現源碼,也就是useModel函數,然后根據自己實際情況改良一個適合自己項目的defineModel函數。MTZ28資訊網——每日最新資訊28at.com

我們先來簡單介紹一下useModel函數中使用到的API,分別是getCurrentInstance、customRef、watchSyncEffect,這三個API都是從vue中import導入的。MTZ28資訊網——每日最新資訊28at.com

getCurrentInstance函數

首先來看看getCurrentInstance函數,他的作用是返回當前的vue實例。為什么要調用這個函數呢?因為在setup中this是拿不到vue實例的,后面對值進行寫操作時會調用vue實例上面的emit方法拋出update事件。MTZ28資訊網——每日最新資訊28at.com

watchSyncEffect函數

接著我們來看watchSyncEffect函數,這個API大家平時應該比較熟悉了。他的作用是立即運行一個函數,同時響應式地追蹤其依賴,并在依賴更改時立即重新執行這個函數。MTZ28資訊網——每日最新資訊28at.com

比如下面這段代碼,會立即執行console,當count變量的值改變后,也會立即執行console。MTZ28資訊網——每日最新資訊28at.com

const count = ref(0)watchSyncEffect(() => console.log(count.value))// -> 輸出 0

customRef函數

最后我們來看customRef函數,他是useModel函數的核心。這個函數小伙伴們應該用的比較少,我們這篇文章只簡單講講他的用法即可。如果小伙伴們對customRef函數感興趣可以留言或者給我發消息,關注的小伙伴們多了我后面會安排一篇文章來專門講customRef函數。官方的解釋為:MTZ28資訊網——每日最新資訊28at.com

MTZ28資訊網——每日最新資訊28at.com

創建一個自定義的 ref,顯式聲明對其依賴追蹤和更新觸發的控制方式。customRef() 預期接收一個工廠函數作為參數,這個工廠函數接受 track 和 trigger 兩個函數作為參數,并返回一個帶有 get 和 set 方法的對象。MTZ28資訊網——每日最新資訊28at.com

這句話的意思是customRef函數的返回值是一個ref對象。當我們對返回值ref對象進行“讀操作”時,會被攔截到ref對象的get方法中。當我們對返回值ref對象進行“寫操作”時,會被攔截到ref對象的set方法中。和Promise相似同樣接收一個工廠函數作為參數,Promise的工廠函數是接收的resolve和reject兩個函數作為參數,customRef的工廠函數是接收的track和trigger兩個函數作為參數。track用于手動進行依賴收集,trigger函數用于手動進行依賴觸發。MTZ28資訊網——每日最新資訊28at.com

我們知道vue的響應式原理是由依賴收集和依賴觸發的方式實現的,比如我們在template中使用一個ref變量。當template被編譯為render函數后,在瀏覽器中執行render函數時,就會對ref變量進行讀操作。讀操作會被攔截到Proxy的get方法中,由于此時在執行render函數,所以當前的依賴就是render函數。在get方法中會進行依賴收集,將當前的render函數作為依賴收集起來。注意這里的依賴收集是vue內部自動完成的,在我們的代碼中無需手動去進行依賴收集。MTZ28資訊網——每日最新資訊28at.com

當我們對ref變量進行寫操作時,此時會被攔截到Proxy的set方法,在set方法中會將收集到的依賴依次取出來執行,我們前面收集的依賴是render函數。所以render函數就會重新執行,執行render函數生成虛擬DOM,再生成真實DOM,這樣瀏覽器中渲染的就是最新的ref變量的值。同樣這里依賴觸發也是在vue內部自動完成的,在我們的代碼中無需手動去觸發依賴。MTZ28資訊網——每日最新資訊28at.com

搞清楚了依賴收集和依賴觸發現在來講track和trigger兩個函數你應該就能很容易理解了,track和trigger兩個函數可以讓我們手動控制什么時候進行依賴收集和依賴觸發。執行track函數就會手動收集依賴,執行trigger函數就會手動觸發依賴,進行頁面刷新。在defineModel這個場景中track手動收集的依賴就是render函數,trigger手動觸發會導致render函數重新執行,進而完成頁面刷新。MTZ28資訊網——每日最新資訊28at.com

useModel函數

現在我們可以來看useModel函數了,簡化后的代碼如下:MTZ28資訊網——每日最新資訊28at.com

function useModel(props, name) {  const i = getCurrentInstance();  const res = customRef((track2, trigger2) => {    let localValue;    watchSyncEffect(() => {      const propValue = props[name];      if (hasChanged(localValue, propValue)) {        localValue = propValue;        trigger2();      }    });    return {      get() {        track2();        return localValue;      },      set(value) {        if (hasChanged(value, localValue)) {          localValue = value;          trigger2();        }        i.emit(`update:${name}`, value);      },    };  });  return res;}

從上面我們可以看到useModel函數的代碼其實很簡單,useModel的返回值就是customRef函數的返回值,也就是一個ref變量對象。我們看到返回值對象中有get和set方法,還有在customRef函數中使用了watchSyncEffect函數。MTZ28資訊網——每日最新資訊28at.com

get方法

在前面的demo中,我們在子組件的template中使用v-model將defineModel的返回值綁定到一個input輸入框中。代碼如下:MTZ28資訊網——每日最新資訊28at.com

<input v-model="model" />

在第一次執行render函數時會對model變量進行讀操作,而model變量是defineModel宏函數的返回值。編譯后我們看到defineModel宏函數變成了useModel函數。所以對model變量進行讀操作,其實就是對useModel函數的返回值進行讀操作。我們看到useModel函數的返回值是一個自定義ref,在自定義ref中有get和set方法,當對自定義ref進行讀操作時會被攔截到ref對象中的get方法。這里在get方法中會手動執行track2方法進行依賴收集。因為此時是在執行render函數,所以收集到的依賴就是render函數,然后將本地維護的localValue的值進行攔截返回。MTZ28資訊網——每日最新資訊28at.com

set方法

在我們前面的demo中,子組件reset按鈕的click事件中會對defineModel的返回值model變量進行寫操作,代碼如下:MTZ28資訊網——每日最新資訊28at.com

function handelReset() {  model.value = "init";}

和對model變量“讀操作”同理,對model變量進行“寫操作”也會被攔截到返回值ref對象的set方法中。在set方法中會先判斷新的值和本地維護的localValue的值比起來是否有修改。如果有修改那就將更新后的值同步更新到本地維護的localValue變量,這樣就保證了本地維護的localValue始終是最新的值。然后執行trigger2函數手動觸發收集的依賴,在前面get的時候收集的依賴是render函數,所以這里觸發依賴會重新執行render函數,然后將最新的值渲染到瀏覽器上面。MTZ28資訊網——每日最新資訊28at.com

在set方法中接著會調用vue實例上面的emit方法進行拋出事件,代碼如下:MTZ28資訊網——每日最新資訊28at.com

i.emit(`update:${name}`, value)

這里的i就是getCurrentInstance函數的返回值。前面我們講過了getCurrentInstance函數的返回值是當前vue實例,所以這里就是調用vue實例上面的emit方法向父組件拋出事件。這里的name也就是調用useModel函數時傳入的第二個參數,我們來回憶一下前面是怎樣調用useModel函數的 ,代碼如下:MTZ28資訊網——每日最新資訊28at.com

const model = _useModel(__props, "modelValue")

傳入的第一個參數為當前的props對象,第二個參數是寫死的字符串"modelValue"。那這里調用emit拋出的事件就是update:modelValue,傳遞的參數為最新的value的值。這就是為什么不需要在子組件中使用使用emit拋出事件,因為在defineModel宏函數編譯成的useModel函數中已經幫我們使用emit拋出事件了。MTZ28資訊網——每日最新資訊28at.com

watchSyncEffect函數

我們接著來看子組件中怎么接收父組件傳遞過來的props呢,答案就在watchSyncEffect函數中。回憶一下前面講過的useModel函數中的watchSyncEffect代碼如下:MTZ28資訊網——每日最新資訊28at.com

function useModel(props, name) {  const res = customRef((track2, trigger2) => {    let localValue;    watchSyncEffect(() => {      const propValue = props[name];      if (hasChanged(localValue, propValue)) {        localValue = propValue;        trigger2();      }    });    return {     // ...省略    };  });  return res;}

這個name也就是調用useModel函數時傳過來的第二個參數,我們前面已經講過了是一個寫死的字符串"modelValue"。那這里的const propValue = props[name]就是取父組件傳遞過來的名為modelValue的prop,我們知道v-model就是:modelValue的語法糖,所以這個propValue就是取的是父組件v-model綁定的變量值。如果本地維護的localValue變量的值不等于父組件傳遞過來的值,那么就將本地維護的localValue變量更新,讓localValue變量始終和父組件傳遞過來的值一樣。并且觸發依賴重新執行子組件的render函數,將子組件的最新變量的值更新到瀏覽器中。為什么要調用trigger2函數呢?原因是可以在子組件的template中渲染defineModel函數的返回值,也就是父組件傳遞過來的prop變量。如果父組件傳遞過來的prop變量值改變后不重新調用trigger2函數以重新執行render函數,那么子組件中的渲染的變量值就一直都是舊的值了。因為這個是在watchSyncEffect內執行的,所以每次父組件傳過來的props值變化后都會再執行一次,讓本地維護的localValue變量的值始終等于父組件傳遞過來的值,并且子組件頁面上也始終渲染的是最新的變量值。MTZ28資訊網——每日最新資訊28at.com

這就是為什么在子組件中沒有任何props定義了,因為在defineModel宏函數編譯后會給vue組件對象塞一個modelValue的prop,并且在useModel函數中會維護一個名為localValue的本地變量接收父組件傳遞過來的props.modelValue,并且讓localValue變量和props.modelValue的值始終保持一致。MTZ28資訊網——每日最新資訊28at.com

總結

現在我們可以回答前面提的幾個問題了:MTZ28資訊網——每日最新資訊28at.com

  • 使用defineModel宏函數后,為什么我們在子組件內沒有寫任何關于props定義的代碼?答案是本地會維護一個localValue變量接收父組件傳遞過來的名為modelValue的props。調用defineModel函數的代碼經過編譯后會變成一個調用useModel函數的代碼,useModel函數的返回值是一個ref對象。當我們對defineModel的返回值進行“讀操作”時,類似于Proxy的get方法一樣會對讀操作進行攔截到返回值ref對象的get方法中。而get方法的返回值為本地維護的localValue變量,在watchSyncEffect的回調中將父組件傳遞過來的名為modelValue的props賦值給本地維護的localValue變量。并且由于是在watchSyncEffect中,所以每次props改變都會執行這個回調,所以本地維護的localValue變量始終是等于父組件傳遞過來的modelValue。也正是因為defineModel宏函數的返回值是一個ref對象而不是一個prop,所以我們可以在子組件內直接將defineModel的返回值使用v-model綁定到子組件input輸入框上面。
  • 使用defineModel宏函數后,為什么我們在子組件內沒有寫任何關于emit事件觸發的代碼?答案是因為調用defineModel函數的代碼經過編譯后會變成一個調用useModel函數的代碼,useModel函數的返回值是一個ref對象。當我們直接修改defineModel的返回值,也就是修改useModel函數的返回值。類似于Proxy的set方法一樣會對寫行為進行攔截到ref對象中的set方法中。在set方法中會手動觸發依賴,render函數就會重新執行,瀏覽器上就會渲染最新的變量值。然后調用vue實例上的emit方法,向父組件拋出update:modelValue事件。并且將最新的值隨著事件一起傳遞給父組件,由父組件在update:modelValue事件回調中將父組件中v-model綁定的變量更新為最新值。
  • 在template渲染中defineModel的返回值等于父組件v-model綁定的變量值,那么這個返回值是否就是名為modelValue的props呢?從第一個回答中我們知道defineModel的返回值不是props,而是一個ref對象。
  • 直接修改defineModel的返回值就會修改父組件上面綁定的變量,那么這個行為是否相當于子組件直接修改了父組件的變量值,破壞了vue的單向數據流呢?修改defineModel的返回值,就會更新父組件中v-model綁定的變量值。看著就像是子組件中直接修改了父組件的變量值,從表面上看著像是打破了vue的單向數據流。實則并不是那樣的,雖然我們在代碼中沒有寫過emit拋出事件的代碼,但是在defineModel函數編譯成的useModel函數中已經幫我們使用emit拋出事件了。所以并沒有打破vue的單向數據流

本文鏈接:http://www.www897cc.com/showinfo-26-81870-0.html父組件使用v-model,子組件竟然不用定義props和emit拋出事件

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 每天都提交代碼,那你知道.git目錄內部的秘密嗎?

下一篇: promise(A).catch(f1).then(f2),f1執行后f2會執行嗎,為什么?

標簽:
  • 熱門焦點
  • 小米平板5 Pro 12.4簡評:多專多能 兼顧影音娛樂的大屏利器

    疫情帶來了網課,網課盤活了安卓平板,安卓平板市場雖然中途停滯了幾年,但好的一點就是停滯的這幾年行業又有了新的發展方向,例如超窄邊框、高刷新率、多攝鏡頭組合等,這就讓安卓
  • 轎車從天而降電動車主被撞身亡 超速搶道所致:現場視頻讓網友吵翻

    近日,上海青浦區法院判決轎車從天而降電動車主被撞身亡案,轎車車主被判有期徒刑一年。案件顯示當時男子駕駛轎車在上海某路段行駛,前車忽然轉彎提速超車,
  • 一文看懂為蘋果Vision Pro開發應用程序

    譯者 | 布加迪審校 | 重樓蘋果的Vision Pro是一款混合現實(MR)頭戴設備。Vision Pro結合了虛擬現實(VR)和增強現實(AR)的沉浸感。其高分辨率顯示屏、先進的傳感器和強大的處理能力
  • 企業采用CRM系統的11個好處

    客戶關系管理(CRM)軟件可以為企業提供很多的好處,從客戶保留到提高生產力。  CRM軟件用于企業收集客戶互動,以改善客戶體驗和滿意度。  CRM軟件市場規模如今超過580
  • 每天一道面試題-CPU偽共享

    前言:了不起:又到了每天一到面試題的時候了!學弟,最近學習的怎么樣啊 了不起學弟:最近學習的還不錯,每天都在學習,每天都在進步! 了不起:那你最近學習的什么呢? 了不起學弟:最近在學習C
  • 品牌洞察丨服務本地,美團直播成效幾何?

    來源:17PR7月11日,美團App首頁推薦位出現&ldquo;美團直播&rdquo;的固定入口。在直播聚合頁面,外賣&ldquo;神槍手&rdquo;直播間、美團旅行直播間、美團買菜直播間等均已上線,同時
  • 微博大門常打開,迎接海外畫師漂洋東渡

    作者:互聯網那些事&ldquo;起猛了,我能看得懂日語了&rdquo;。&ldquo;為什么日本人說話我能聽懂?&rdquo;&ldquo;中文不像中文,日語不像日語,但是我竟然看懂了&rdquo;&hellip;&hell
  • 回歸OPPO兩年,一加贏了銷量,輸了品牌

    成為OPPO旗下主打性能的先鋒品牌后,一加屢創佳績。今年618期間,一加手機全渠道銷量同比增長362%,憑借一加 11、一加 Ace 2、一加 Ace 2V三款爆品,一加
  • 朋友圈可以修改可見范圍了 蘋果用戶可率先體驗

    近日,iOS用戶迎來微信8.0.27正式版更新,除了可更換二維碼背景外,還新增了多項實用功能。在新版微信中,朋友圈終于可以修改可見范圍,簡單來說就是已發布的朋友圈
Top 主站蜘蛛池模板: 白水县| 枣阳市| 米泉市| 安岳县| 县级市| 沙田区| 台东县| 玉门市| 齐河县| 黄山市| 泊头市| 长宁县| 肇源县| 天镇县| 奉新县| 无极县| 怀仁县| 龙海市| 延边| 尉氏县| 长武县| 沈阳市| 图木舒克市| 海伦市| 安远县| 田东县| 盐边县| 德保县| 肥东县| 毕节市| 鄂尔多斯市| 大英县| 长沙县| 郧西县| 南通市| 海宁市| 响水县| 丹凤县| 宁南县| 抚顺县| 改则县|