浅聊一下Vue3中的component组件
实现一个组件
一个组件其实就是一个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>
数据
可以通过Prop向组件传递数据,先在组件中定义,如下:
<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
来使用这些属性。
那么如何将数据传递给这些属性呢,直接通过v-bind绑定数据即可,如下:
<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绑定后面双引号中是表达式,所以如果类型是:
- 数值:
:count="3"
- 布尔值:
:isVip="true"
- 数组:
:array="[1,2,3]"
- 对象:
:info="{name:'名字',isVip:true}"
其他就不一一列举了。
Props是支持类型如下:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol。
Props支持类型检查,同时支持默认值,如下:
props: { // 基础的类型检查 (`null` 和 `undefined` 值会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组的默认值必须从一个工厂函数返回 default() { return { message: 'hello' } } }, // 具有默认值的函数 propG: { type: Function, // 与对象或数组的默认值不同,这不是一个工厂函数——这是一个用作默认值的函数 default() { return 'Default function' } } }
Props是单向数据流,这样可以防止子组件意外变更父组件的状态,每当父组件发生变更,子组件所有Props都会刷新到最新值。所以子组件中不能更改Props的属性,否则会在控制台警告。
非 Prop 的 Attribute
比如class
、style
和 id
等attribute,默认是添加到组件的根节点上,如:
<script setup> ... </script> <template> <div class="header" > <input ...> </div> </template> <style scoped lang="less"> ... </style>
当我们使用这个组件时,为其添加attribute,如:
<Header id="my-header"></Header>
实际的渲染结果是:
<div class="header" id="my-header"> <input ...> </div>
禁用 Attribute 继承
如果不希望添加到根节点上,则可以设置inheritAttrs: false
。
<script setup>
无法声明inheritAttrs,所以我们需要在添加普通的<script>
,同时使用v-bind="$attrs"
来绑定继承Attribute的节点,如
<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元素上,实际的渲染结果是:
<div class="header"> <input ... id="my-header"> </div>
多根节点
如果组件有多根节点,那么必须显式设置v-bind="$attrs"
,否则警告。
事件
数据传递清楚了,那么如何响应组件的一些自定义事件呢?通过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事件,当组件中触发selectItem函数的时候就会执行这个事件。
注意:如果没有参数,则直接emit("onSelected")
即可;如果有多个参数,则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的缩写形式,这样就绑定了事件。事件同样可以验证,这里就不细说了。
v-model
v-model是双向数据绑定,默认情况下,组件上的 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数据。
插槽
如果子组件中部分区域是不定的,需要父组件来实现,那么怎么办?这就需要用到插槽slot,插槽使用很简单,如下:
<script setup> ... </script> <template> <div class="header" > <slot>default</slot> </div> </template> <style scoped lang="less"> ... </style>
这里定义了一个插槽,并且指定了一个默认内容,在父组件中:
<script setup> import Header from "./Header.vue"; </script> <template> <div class="container"> <Header>newtitle</Header> </div> </template> <style scoped lang="less"> ... </style>
这样插槽就被“newtitle”这个字符串替代,如果这里没有任何内容<Header></Header>
则显示默认内容,即"default"。
插槽中不仅是字符串,可以是html或其他组件,比如:
<Header> <button>submit</botton> </Header>
当然默认内容也可以是html。
具名插槽
一个组件里可以有多个插槽,比如左边栏右边栏等,这样就需要具名插槽来区分,如下:
<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>
插槽名也可以是动态的<template v-slot:[dynamicSlotName] >
。v-slot也可以缩写成“#”,如<template #leftbar >
。
作用域
如下代码:
<div v-for="item in list" > <slot></slot> </div>
这时候当我们使用组件的时候,插槽中则无法item,如下
<my-component > <img :src="item.img"></img> </my-component>
像上面这种代码就会报错,因为只能在组件中访问item,而插槽是在父组件上提供的,作用域不同。
当然我们可以为插槽添加一个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
上面知道父组件向子组件传递数据用Props,但是如果组件层级很深,需要向一个底层的子组件传递数据,如果用Props就需要一层层的去传递。这时候就可以使用Provide / Inject。如: 父组件中通过provide(key, value)
来设置数据
<script setup> const userid = "123"; provide("userid", userid); </script> ...
那么在子组件中通过inject(key, defaultValue)
来获取数据
<script setup> const userid = inject("userid", defaultId); </script> ...
当然上面是一次性的数据,如果需要响应,则在父组件中使用ref或reactive,如:
<script setup> const userid = ref("123"); provide("userid", userid); </script> ...
获取DOM对象
在组件中,我们可以给元素设置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对象,可以执行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>
,对于其内部是有严格限制的。而有些元素如 <li>
、<tr>
和 <option>
,只能出现在某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
<table> <my-component></my-component> </table>
自定义组件 <my-component>
会被作为无效的内容提升到外部,导致渲染出错。这时候需要使用 is
attribute,如:
<table> <tr is="vue:my-component"></tr> </table>
注意:is
的值必须以 vue:
开头,才可以被解释为 Vue 组件。这是避免和原生自定义元素混淆。
调用子组件方法
上面事件章节说的是父组件响应子组件的事件,也就是说是子组件调用父组件的方法。那么父组件如何调用子组件的方法?
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()函数获取对象执行方法即可,如下:
<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
执行暴露出来的方法了。
如果有多个子组件,都设置了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); } };
以上就是详细说一说Vue3中的component组件的详细内容,更多关于Vue3 component组件的资料请关注脚本之家其它相关文章!
相关文章
Vue插件报错:Vue.js is detected on this page.问题解决
这篇文章主要介绍了Vue插件报错:Vue.js is detected on this page.问题解决,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下2021-07-07
最新评论