詳細(xì)說一說Vue3中的component組件

前言

開發(fā)過程中我們會(huì)經(jīng)常遇到一些復(fù)雜的頁面,而這些頁面大部分由一個(gè)個(gè)小部分組合起來的,而且不同頁面中可能有些部分是一樣的躬充,所以我們通常會(huì)將這些部分封裝成組件。在Vue中讨便,我們可以使用components組件(模板)來實(shí)現(xiàn)充甚。

實(shí)現(xiàn)一個(gè)組件

一個(gè)組件其實(shí)就是一個(gè)vue文件,簡(jiǎn)單示例(header.vue)如下:

<script setup></script>

<template>
  <div class="header"></div>
</template>

<style scoped lang="less">
.header {
  position: absolute;
  width: 100%;
  height: 80px;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.6) 0%,
    rgba(0, 0, 0, 0) 100%
  );
}
</style>

注冊(cè)使用

基于script setup可以自動(dòng)注冊(cè)組件霸褒,只需要import即可使用伴找,如下:

<script setup>
import Header from "./Header.vue";
</script>

<template>
  <div class="container">
    <Header></Header>
  </div>
</template>

<style scoped lang="less">
.container {
  width: 100%;
  height: 100%;
}
</style>

數(shù)據(jù)

可以通過Prop向組件傳遞數(shù)據(jù),先在組件中定義废菱,如下:

<script setup>
const props = defineProps({
  title: String,
  userInfo: Object
});

function doSomething(){
  if(props.userInfo.isVip){
    ...
  }
}
</script>

<template>
  <div class="header">{{props.title}}</div>
</template>

<style scoped lang="less">
...
</style>

這里定義了一個(gè)title屬性技矮,是一個(gè)字符串;一個(gè)userInfo屬性殊轴,是一個(gè)對(duì)象衰倦,然后在組件中就可以通過props.xxx來使用這些屬性。

那么如何將數(shù)據(jù)傳遞給這些屬性呢旁理,直接通過v-bind綁定數(shù)據(jù)即可樊零,如下:

<script setup>
import Header from "./Header.vue";
let userInfo;
let title;
</script>

<template>
  <div class="container">
    <Header :title="title" :userInfo="userInfo"></Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

這里使用的是v-bind的縮寫。v-bind綁定后面雙引號(hào)中是表達(dá)式韧拒,所以如果類型是:

  • 數(shù)值::count="3"
  • 布爾值::isVip="true"
  • 數(shù)組::array="[1,2,3]"
  • 對(duì)象::info="{name:'名字',isVip:true}" 其他就不一一列舉了淹接。

Props是支持類型如下:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol。

Props支持類型檢查叛溢,同時(shí)支持默認(rèn)值塑悼,如下:

  props: {
    // 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 值會(huì)通過任何類型驗(yàn)證)
    propA: Number,
    // 多個(gè)可能的類型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 帶有默認(rèn)值的數(shù)字
    propD: {
      type: Number,
      default: 100
    },
    // 帶有默認(rèn)值的對(duì)象
    propE: {
      type: Object,
      // 對(duì)象或數(shù)組的默認(rèn)值必須從一個(gè)工廠函數(shù)返回
      default() {
        return { message: 'hello' }
      }
    },
    // 具有默認(rèn)值的函數(shù)
    propG: {
      type: Function,
      // 與對(duì)象或數(shù)組的默認(rèn)值不同,這不是一個(gè)工廠函數(shù)——這是一個(gè)用作默認(rèn)值的函數(shù)
      default() {
        return 'Default function'
      }
    }
  }

Props是單向數(shù)據(jù)流楷掉,這樣可以防止子組件意外變更父組件的狀態(tài)厢蒜,每當(dāng)父組件發(fā)生變更,子組件所有Props都會(huì)刷新到最新值烹植。所以子組件中不能更改Props的屬性斑鸦,否則會(huì)在控制臺(tái)警告。

非 Prop 的 Attribute

比如class草雕、styleid等attribute巷屿,默認(rèn)是添加到組件的根節(jié)點(diǎn)上,如:

<script setup>
...
</script>

<template>
  <div class="header" >
    <input ...>
  </div>
</template>

<style scoped lang="less">
...
</style>

當(dāng)我們使用這個(gè)組件時(shí)墩虹,為其添加attribute嘱巾,如:

<Header id="my-header"></Header>

實(shí)際的渲染結(jié)果是:

<div class="header" id="my-header">
  <input ...>
</div>

禁用 Attribute 繼承

如果不希望添加到根節(jié)點(diǎn)上憨琳,則可以設(shè)置inheritAttrs: false

<script setup>無法聲明inheritAttrs旬昭,所以我們需要在添加普通的<script>篙螟,同時(shí)使用v-bind="$attrs"來綁定繼承Attribute的節(jié)點(diǎn),如

<script>
export default {
  inheritAttrs: false
};
</script>
<script setup>
...
</script>

<template>
  <div class="header" >
    <input ... v-bind="$attrs">
  </div>
</template>

<style scoped lang="less">
...
</style>

這樣添加給組件的Attribute就會(huì)直接添加到input元素上问拘,實(shí)際的渲染結(jié)果是:

<div class="header">
  <input ...  id="my-header">
</div>

多根節(jié)點(diǎn)

如果組件有多根節(jié)點(diǎn)遍略,那么必須顯式設(shè)置v-bind="$attrs",否則警告骤坐。

事件

數(shù)據(jù)傳遞清楚了绪杏,那么如何響應(yīng)組件的一些自定義事件呢?通過emits來定義事件或油,如下:

<script setup>
const emit = defineEmits(["onSelected"]);

function selectItem(item){
  emit("onSelected", item);
}
</script>

<template>
  <div class="header" >
    ...
  </div>
</template>

<style scoped lang="less">
...
</style>

這里定義了一個(gè)onSelected事件寞忿,當(dāng)組件中觸發(fā)selectItem函數(shù)的時(shí)候就會(huì)執(zhí)行這個(gè)事件。

注意:如果沒有參數(shù)顶岸,則直接emit("onSelected")即可;如果有多個(gè)參數(shù)叫编,則emit("onSelected", param1, param2, ...)

在父組件中則通過v-on來綁定事件辖佣,如下:

<script setup>
import Header from "./Header.vue";

function headerSelected(item){
}
</script>

<template>
  <div class="container">
    <Header @onSelected="headerSelected"></Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

這里同樣是v-on的縮寫形式,這樣就綁定了事件搓逾。事件同樣可以驗(yàn)證卷谈,這里就不細(xì)說了。

v-model

v-model是雙向數(shù)據(jù)綁定霞篡,默認(rèn)情況下世蔗,組件上的 v-model 使用 modelValue 作為 prop 和 update:modelValue 作為事件。比如有一個(gè)title屬性:

<my-component v-model:title="bookTitle"></my-component>

那么在子組件中就可以這樣做:

<script setup>
const props = defineProps({
  title: String
});

const emit = defineEmits(["update:title"]);

const setTitle = (newTitle) => {
  emit("update:title", newTitle);
}
</script>

<template>
  <div class="header" >
    ...
  </div>
</template>

<style scoped lang="less">
...
</style>

這樣子組件中可以通過update:title來同步title數(shù)據(jù)朗兵。

插槽

如果子組件中部分區(qū)域是不定的污淋,需要父組件來實(shí)現(xiàn),那么怎么辦余掖?這就需要用到插槽slot寸爆,插槽使用很簡(jiǎn)單,如下:

<script setup>
...
</script>

<template>
  <div class="header" >
    <slot>default</slot>
  </div>
</template>

<style scoped lang="less">
...
</style>

這里定義了一個(gè)插槽盐欺,并且指定了一個(gè)默認(rèn)內(nèi)容赁豆,在父組件中:

<script setup>
import Header from "./Header.vue";

</script>

<template>
  <div class="container">
    <Header>newtitle</Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

這樣插槽就被“newtitle”這個(gè)字符串替代,如果這里沒有任何內(nèi)容<Header></Header>則顯示默認(rèn)內(nèi)容冗美,即"default"魔种。

插槽中不僅是字符串,可以是html或其他組件粉洼,比如:

<Header>
 <button>submit</botton>
</Header>

當(dāng)然默認(rèn)內(nèi)容也可以是html节预。

具名插槽

一個(gè)組件里可以有多個(gè)插槽甲抖,比如左邊欄右邊欄等,這樣就需要具名插槽來區(qū)分心铃,如下:

<script setup>
...
</script>

<template>
  <div class="header" >
    <div name="leftbar" >
      <slot>left</slot>
    </div>
    <div name="rightbar" >
      <slot>right</slot>
    </div>
  </div>
</template>

<style scoped lang="less">
...
</style>

使用的時(shí)候需要用v-slot指令准谚,并且一定是<template>元素,因?yàn)関-slot只能添加在<template>上去扣,如下:

<script setup>
import Header from "./Header.vue";

</script>

<template>
  <div class="container">
    <Header>
      <template v-slot:leftbar >
        <button>left</button>
      </template>
      <template v-slot:rightbar >
        <button>right</button>
      </template>
    </Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

插槽名也可以是動(dòng)態(tài)的<template v-slot:[dynamicSlotName] >柱衔。v-slot也可以縮寫成“#”,如<template #leftbar >愉棱。

作用域

如下代碼:

<div v-for="item in list" >
  <slot></slot>
</div>

這時(shí)候當(dāng)我們使用組件的時(shí)候唆铐,插槽中則無法item,如下

<my-component >
  <img :src="item.img"></img>
</my-component>

像上面這種代碼就會(huì)報(bào)錯(cuò)奔滑,因?yàn)橹荒茉诮M件中訪問item艾岂,而插槽是在父組件上提供的,作用域不同朋其。

當(dāng)然我們可以為插槽添加一個(gè)attribute綁定王浴,即插槽 prop,如下:

<div v-for="item in list" >
  <slot :item="item"></slot>
</div>

插槽可以添加多個(gè)attribute梅猿。

然后在父級(jí)中使用帶值的v-slot來定義我們提供的插槽 prop 的名字氓辣,如:

<my-component >
  <template v-slot:default="slotProps">
    <img :src="slotProps.item.img"></img>
  </template>   
</my-component>

這樣就不會(huì)報(bào)錯(cuò)了。如果只有一個(gè)插槽袱蚓,那么v-slot可以直接添加組件上钞啸,如:

<my-component v-slot="slotProps">
  <img :src="slotProps.item.img"></img> 
</my-component>

就不需要template元素了;但是如果有多個(gè)插槽(具名)則必須添加在template元素上喇潘。

Provide / Inject

上面知道父組件向子組件傳遞數(shù)據(jù)用Props体斩,但是如果組件層級(jí)很深,需要向一個(gè)底層的子組件傳遞數(shù)據(jù)颖低,如果用Props就需要一層層的去傳遞絮吵。這時(shí)候就可以使用Provide / Inject。如: 父組件中通過provide(key, value)來設(shè)置數(shù)據(jù)

<script setup>
const userid = "123";
provide("userid", userid);
</script>
...

那么在子組件中通過inject(key, defaultValue)來獲取數(shù)據(jù)

<script setup>
const userid = inject("userid", defaultId);
</script>
...

當(dāng)然上面是一次性的數(shù)據(jù)枫甲,如果需要響應(yīng)源武,則在父組件中使用ref或reactive,如:

<script setup>
const userid = ref("123");
provide("userid", userid);
</script>
...

獲取DOM對(duì)象

在組件中想幻,我們可以給元素設(shè)置id粱栖,并通過id的方式來獲取它的DOM對(duì)象。但是在頁面上有多個(gè)該組件的情況下脏毯,這樣獲取DOM對(duì)象就會(huì)有問題闹究,因?yàn)閕d不唯一。

我們還可以使用 ref attribute 為子組件或 HTML 元素指定引用 ID食店。如:

<script setup>
import Header from "./Header.vue";
const headerRef = ref();

</script>

<template>
  <div class="container">
    <Header ref="headerRef">
      ...
    </Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

上面Header的ref屬性是“headerRef”渣淤,所以變量名也保持一致赏寇,即const headerRef = ref();,這樣headerRef.value就是它的DOM對(duì)象价认,可以執(zhí)行getElementsByTagName等方法嗅定。

這樣即是頁面上存在多個(gè)組件,但是因?yàn)樽饔糜虻拇嬖谟貌龋總€(gè)headerRef互不干擾帖世。

列表

如果是一個(gè)列表中呢绽淘?比如:

<template>
  <div class="container">
    <item ref="itemRef" v-for="item in list">
      ...
    </item>
  </div>
</template>

這樣通過const itemRef = ref();得到的是哪個(gè)?其實(shí)這樣itemRef.value就變成了一個(gè)列表增炭,itemRef.value[0]就是第一個(gè)item組件的對(duì)象杨耙。

元素位置受限

有些 HTML 元素如 <ul>疏橄、<ol>闰集、<table><select>桨螺,對(duì)于其內(nèi)部是有嚴(yán)格限制的。而有些元素如 <li>佛南、<tr><option>梗掰,只能出現(xiàn)在某些特定的元素內(nèi)部。

這會(huì)導(dǎo)致我們使用這些有約束條件的元素時(shí)遇到一些問題共虑。例如:

<table>
  <my-component></my-component>
</table>

自定義組件 <my-component> 會(huì)被作為無效的內(nèi)容提升到外部愧怜,導(dǎo)致渲染出錯(cuò)。這時(shí)候需要使用 is attribute妈拌,如:

<table>
  <tr is="vue:my-component"></tr>
</table>

注意:is 的值必須以 vue: 開頭,才可以被解釋為 Vue 組件蓬蝶。這是避免和原生自定義元素混淆尘分。

調(diào)用子組件方法

上面事件章節(jié)說的是父組件響應(yīng)子組件的事件,也就是說是子組件調(diào)用父組件的方法丸氛。那么父組件如何調(diào)用子組件的方法培愁?

Expose

首先子組件的方法需要暴露出去,如下:

<script setup>
defineExpose({
  onEvent
});
function onEvent(event) {
  console.log(`header: ${event}`);
}
</script>

<template>
  <div class="header" >
    ...
  </div>
</template>

<style scoped lang="less">
...
</style>

然后在父組件中為子組件添加ref屬性缓窜,然后通過ref()函數(shù)獲取對(duì)象執(zhí)行方法即可定续,如下:

<script setup>
import Header from "./Header.vue";
const headerRef = ref();

function headerEvent(event){
  headerRef.value.onEvent(event);
}

</script>

<template>
  <div class="container">
    <Header ref="headerRef">
      ...
    </Header>
  </div>
</template>

<style scoped lang="less">
...
</style>

這樣就可以通過headerRef.value執(zhí)行暴露出來的方法了。

如果有多個(gè)子組件禾锤,都設(shè)置了ref屬性私股,則定義多個(gè)變量即可,如下:

const headerRef = ref();
const footerRef = ref();

EventBus

更簡(jiǎn)單的方法是使用EventBus即可完成組件間通信恩掷,使用也非常簡(jiǎn)單倡鲸,參考官方文檔即可 www.npmjs.com/package/eve…

我們可以簡(jiǎn)單封裝一下以便使用,如下:

import EventEmitter from "events";
const eventEmitter = new EventEmitter();

export const WsEvent = {
  emit: data => {
    eventEmitter.emit("wsevent", data);
  },
  on: callback => {
    eventEmitter.on("wsevent", callback);
  }
};

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黄娘,一起剝皮案震驚了整個(gè)濱河市峭状,隨后出現(xiàn)的幾起案子克滴,更是在濱河造成了極大的恐慌,老刑警劉巖优床,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劝赔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡胆敞,警方通過查閱死者的電腦和手機(jī)着帽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竿秆,“玉大人启摄,你說我怎么就攤上這事∮母郑” “怎么了歉备?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)匪燕。 經(jīng)常有香客問我蕾羊,道長(zhǎng),這世上最難降的妖魔是什么帽驯? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任龟再,我火速辦了婚禮,結(jié)果婚禮上尼变,老公的妹妹穿的比我還像新娘利凑。我一直安慰自己,他們只是感情好嫌术,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布哀澈。 她就那樣靜靜地躺著,像睡著了一般度气。 火紅的嫁衣襯著肌膚如雪割按。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天磷籍,我揣著相機(jī)與錄音适荣,去河邊找鬼。 笑死院领,一個(gè)胖子當(dāng)著我的面吹牛弛矛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栅盲,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汪诉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扒寄,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鱼鼓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后该编,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迄本,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年课竣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘉赎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡于樟,死狀恐怖公条,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迂曲,我是刑警寧澤靶橱,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站路捧,受9級(jí)特大地震影響关霸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杰扫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一队寇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧章姓,春花似錦佳遣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窗声,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辜纲,已是汗流浹背笨觅。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耕腾,地道東北人见剩。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扫俺,于是被迫代替她去往敵國(guó)和親苍苞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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