淺聊一下Vue3中的component組件
實現(xiàn)一個組件
一個組件其實就是一個vue文件,簡單示例(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>
注冊使用
基于script setup可以自動注冊組件,只需要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>
這里定義了一個title屬性,是一個字符串;一個userInfo屬性,是一個對象,然后在組件中就可以通過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綁定后面雙引號中是表達(dá)式,所以如果類型是:
- 數(shù)值:
:count="3"
- 布爾值:
:isVip="true"
- 數(shù)組:
:array="[1,2,3]"
- 對象:
:info="{name:'名字',isVip:true}"
其他就不一一列舉了。
Props是支持類型如下:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol。
Props支持類型檢查,同時支持默認(rèn)值,如下:
props: { // 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 值會通過任何類型驗證) propA: Number, // 多個可能的類型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 帶有默認(rèn)值的數(shù)字 propD: { type: Number, default: 100 }, // 帶有默認(rèn)值的對象 propE: { type: Object, // 對象或數(shù)組的默認(rèn)值必須從一個工廠函數(shù)返回 default() { return { message: 'hello' } } }, // 具有默認(rèn)值的函數(shù) propG: { type: Function, // 與對象或數(shù)組的默認(rèn)值不同,這不是一個工廠函數(shù)——這是一個用作默認(rèn)值的函數(shù) default() { return 'Default function' } } }
Props是單向數(shù)據(jù)流,這樣可以防止子組件意外變更父組件的狀態(tài),每當(dāng)父組件發(fā)生變更,子組件所有Props都會刷新到最新值。所以子組件中不能更改Props的屬性,否則會在控制臺警告。
非 Prop 的 Attribute
比如class
、style
和 id
等attribute,默認(rèn)是添加到組件的根節(jié)點上,如:
<script setup> ... </script> <template> <div class="header" > <input ...> </div> </template> <style scoped lang="less"> ... </style>
當(dāng)我們使用這個組件時,為其添加attribute,如:
<Header id="my-header"></Header>
實際的渲染結(jié)果是:
<div class="header" id="my-header"> <input ...> </div>
禁用 Attribute 繼承
如果不希望添加到根節(jié)點上,則可以設(shè)置inheritAttrs: false
。
<script setup>
無法聲明inheritAttrs,所以我們需要在添加普通的<script>
,同時使用v-bind="$attrs"
來綁定繼承Attribute的節(jié)點,如
<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就會直接添加到input元素上,實際的渲染結(jié)果是:
<div class="header"> <input ... id="my-header"> </div>
多根節(jié)點
如果組件有多根節(jié)點,那么必須顯式設(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>
這里定義了一個onSelected事件,當(dāng)組件中觸發(fā)selectItem函數(shù)的時候就會執(zhí)行這個事件。
注意:如果沒有參數(shù),則直接emit("onSelected")
即可;如果有多個參數(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的縮寫形式,這樣就綁定了事件。事件同樣可以驗證,這里就不細(xì)說了。
v-model
v-model是雙向數(shù)據(jù)綁定,默認(rèn)情況下,組件上的 v-model 使用 modelValue 作為 prop 和 update:modelValue 作為事件。比如有一個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ū)域是不定的,需要父組件來實現(xiàn),那么怎么辦?這就需要用到插槽slot,插槽使用很簡單,如下:
<script setup> ... </script> <template> <div class="header" > <slot>default</slot> </div> </template> <style scoped lang="less"> ... </style>
這里定義了一個插槽,并且指定了一個默認(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”這個字符串替代,如果這里沒有任何內(nèi)容<Header></Header>
則顯示默認(rèn)內(nèi)容,即"default"。
插槽中不僅是字符串,可以是html或其他組件,比如:
<Header> <button>submit</botton> </Header>
當(dāng)然默認(rèn)內(nèi)容也可以是html。
具名插槽
一個組件里可以有多個插槽,比如左邊欄右邊欄等,這樣就需要具名插槽來區(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>
使用的時候需要用v-slot指令,并且一定是<template>
元素,因為v-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>
插槽名也可以是動態(tài)的<template v-slot:[dynamicSlotName] >
。v-slot也可以縮寫成“#”,如<template #leftbar >
。
作用域
如下代碼:
<div v-for="item in list" > <slot></slot> </div>
這時候當(dāng)我們使用組件的時候,插槽中則無法item,如下
<my-component > <img :src="item.img"></img> </my-component>
像上面這種代碼就會報錯,因為只能在組件中訪問item,而插槽是在父組件上提供的,作用域不同。
當(dāng)然我們可以為插槽添加一個attribute綁定,即插槽 prop,如下:
<div v-for="item in list" > <slot :item="item"></slot> </div>
插槽可以添加多個attribute。
然后在父級中使用帶值的v-slot
來定義我們提供的插槽 prop 的名字,如:
<my-component > <template v-slot:default="slotProps"> <img :src="slotProps.item.img"></img> </template> </my-component>
這樣就不會報錯了。如果只有一個插槽,那么v-slot
可以直接添加組件上,如:
<my-component v-slot="slotProps"> <img :src="slotProps.item.img"></img> </my-component>
就不需要template元素了;但是如果有多個插槽(具名)則必須添加在template元素上。
Provide / Inject
上面知道父組件向子組件傳遞數(shù)據(jù)用Props,但是如果組件層級很深,需要向一個底層的子組件傳遞數(shù)據(jù),如果用Props就需要一層層的去傳遞。這時候就可以使用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對象
在組件中,我們可以給元素設(shè)置id,并通過id的方式來獲取它的DOM對象。但是在頁面上有多個該組件的情況下,這樣獲取DOM對象就會有問題,因為id不唯一。
我們還可以使用 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對象,可以執(zhí)行getElementsByTagName
等方法。
這樣即是頁面上存在多個組件,但是因為作用域的存在,每個headerRef互不干擾。
列表
如果是一個列表中呢?比如:
<template> <div class="container"> <item ref="itemRef" v-for="item in list"> ... </item> </div> </template>
這樣通過const itemRef = ref();
得到的是哪個?其實這樣itemRef.value
就變成了一個列表,itemRef.value[0]
就是第一個item
組件的對象。
元素位置受限
有些 HTML 元素如 <ul>
、<ol>
、<table>
和 <select>
,對于其內(nèi)部是有嚴(yán)格限制的。而有些元素如 <li>
、<tr>
和 <option>
,只能出現(xiàn)在某些特定的元素內(nèi)部。
這會導(dǎo)致我們使用這些有約束條件的元素時遇到一些問題。例如:
<table> <my-component></my-component> </table>
自定義組件 <my-component>
會被作為無效的內(nèi)容提升到外部,導(dǎo)致渲染出錯。這時候需要使用 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ù)獲取對象執(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í)行暴露出來的方法了。
如果有多個子組件,都設(shè)置了ref屬性,則定義多個變量即可,如下:
const headerRef = ref(); const footerRef = ref();
EventBus
更簡單的方法是使用EventBus即可完成組件間通信,使用也非常簡單,參考官方文檔即可https://www.npmjs.com/package/events
我們可以簡單封裝一下以便使用,如下:
import EventEmitter from "events"; const eventEmitter = new EventEmitter(); export const WsEvent = { emit: data => { eventEmitter.emit("wsevent", data); }, on: callback => { eventEmitter.on("wsevent", callback); } };
以上就是詳細(xì)說一說Vue3中的component組件的詳細(xì)內(nèi)容,更多關(guān)于Vue3 component組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue2項目使用element-ui的el-tabs組件導(dǎo)致瀏覽器崩潰卡死問題
這篇文章主要介紹了vue2項目使用element-ui的el-tabs組件導(dǎo)致瀏覽器崩潰卡死問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07