09 组件基础
loyalvi Lv7

09 组件基础

==组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考==。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:==使我们可以在每个组件内封装自定义内容与逻辑==。
!727
使用构建步骤,单文件组件 SFC

1
2
3
4
5
6
7
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>

当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 也可以针对一个 DOM 内联模板:
// template: '#my-template-element'
}

传递 props

Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给博客文章组件一个标题,我们必须在组件的 props 列表上声明它。这里要用到defineProps宏:
defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props:

1
2
const props = defineProps(['title'])
console.log(props.title)

如果你没有使用 <script setup>,props 必须以 props 选项的方式声明,props 对象会作为 setup() 函数的第一个参数被传入:

1
2
3
4
5
6
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}

v-for

1
2
3
4
5
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>

监听事件

这==声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证==。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
和 defineProps 类似,defineEmits 仅可用于 <script setup> 之中,并且不需要导入,它返回一个等同于 $emit 方法的 emit 函数。它可以被用于在组件的 <script setup> 中抛出事件,因为此处无法直接访问 $emit

1
2
3
4
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>

如果你没有在使用 <script setup>,你可以通过 emits 选项定义组件会抛出的事件。你可以从 setup() 函数的第二个参数,即 setup 上下文对象上访问到 emit 函数:

1
2
3
4
5
6
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}

slot

==能和 HTML 元素一样向组件中传递内容==,可以通过 Vue 的自定义 <slot> 元素来实现
使用时

1
2
3
<AlertBox>
Slot 的内容
</AlertBox>

组件

1
2
3
4
5
6
7
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>下面是一个Slot 的内容</strong>
<slot />
</div>
</template>

动态组件

有些场景会需要在两个组件间来回切换,比如 Tab 界面:
通过 Vue 的 <component> 元素和特殊的 is attribute 实现的:

1
2
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

在上面的例子中,被传给 :is 的值可以是以下几种:

  • 被注册的组件名
  • 导入的组件对象
    你也可以使用 is attribute 来创建一般的 HTML 元素。
    当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive> 组件强制被切换掉的组件仍然保持“存活”的状态。

DOM 内模板解析注意事项

如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。由于浏览器的原生 HTML 解析行为限制,有一些需要注意的事项。

大小写区分

==HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写==。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:

1
2
3
4
5
6
7
8
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
1
2
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

闭合标签

我们在上面的例子中已经使用过了闭合标签 (self-closing tag):

1
<MyComponent />

这是因为 Vue 的模板解析器支持任意标签使用 /> 作为标签关闭的标志。
然而在 DOM 内模板中,我们必须显式地写出关闭标签:

1
<my-component></my-component>

这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是 <input> 和 <img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束,用下面这个代码片段举例来说:

1
2
<my-component /> <!-- 我们想要在这里关闭标签... -->
<span>hello</span>

将被解析为:

1
2
3
<my-component>
<span>hello</span>
</my-component> <!-- 但浏览器会在这里关闭标签 -->

元素位置限制

某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul><ol><table> 和 <select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li><tr> 和 <option>
这将导致在使用带有此类限制元素的组件时出现问题。例如:

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

1
2
3
<table>
<tr is="vue:blog-post-row"></tr>
</table>
由 Hexo 驱动 & 主题 Keep
访客数 访问量