6、注册
将 Vue 组件定义在一个单独的 .vue
文件中,这被叫做单文件组件(简称 SFC)。
一个 Vue 组件在使用前需要先被"注册",组件注册有两种方式:全局注册和局部注册。
(1)全局注册
使用vue实例的 .component()
方法,让组件在当前 Vue 应用中全局可用。
javascript
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
如果使用单文件组件,你可以注册被导入的 .vue
文件:
javascript
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
.component()
方法可以被链式调用:
javascript
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
全局注册的组件可以在此应用的任意组件的模板中使用:
javascript
<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>
全局注册的弊端(局部注册的必要性):
-
全局注册的组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
-
全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。
(2)局部注册
局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确。
在使用 <script setup>
的单文件组件中,导入的组件可以直接在模板中使用,无需注册:
javascript
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<ComponentA />
</template>
如果没有使用 <script setup>
,则需要使用 components
选项来显式注册:
javascript
import ComponentA from './ComponentA.js'
export default {
components: {
ComponentA
},
setup() {
// ...
}
}
局部注册的组件在后代组件中不可用。仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。
7、组件名格式
使用 PascalCase 作为组件名的注册格式,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent
为名注册的组件,在模板 (或由 Vue 渲染的 HTML 元素) 中可以通过 <MyComponent>
或 <my-component>
引用。
8、生命周期钩子

beforeCreate :在实例初始化完成并且 props 被解析后立即调用。props 会被定义成响应式属性,data()
和 computed
等选项也开始进行处理。
created: 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el
属性仍不可用。
**beforeMount:**当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
**mounted:**在组件被挂载之后调用(所有同步子组件都已经被挂载,自身的 DOM 树已经创建完成并插入了父容器中),这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用。
**beforeUpdate:**在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用,可以用来在 Vue 更新 DOM 之前访问 DOM 状态。
**updated:**组件因为一个响应式状态变更而更新其 DOM 树之后调用,父组件的更新钩子将在其子组件的更新钩子之后调用,组件的任意 DOM 更新后被调用,不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环。
**beforeUnmount:**在一个组件实例被卸载之前调用。
**unmounted:**在一个组件实例被卸载之后调用。在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
9、组件渲染
(1)条件渲染
v-if、v-else、
v-else-if: 条件区块内的事件监听器和子组件都会被销毁与重建。如果在初次渲染时条件值为 false,则不会做任何事。有更高的切换开销,运行时绑定条件很少改变, v-if
会更合适。
javascript
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
v-show
: 元素无论初始条件如何,始终会被渲染,只有 CSS display
属性会被切换。有更高的初始渲染开销。如果需要频繁切换,则使用 v-show
较好。
(2)列表渲染
v-for:
基于一个数组来渲染一个列表,需要使用 item in items
形式的特殊语法。
javascript
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
同时使用 v-if
和 v-for
是不推荐的, v-if
比 v-for
的优先级更高。v-if
的条件将无法访问到 v-for
作用域内定义的变量别名。
javascript
<!--
这会抛出一个错误,因为属性 todo 此时
没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
在外先包装一层 <template>
再在其上使用 v-for
可以解决这个问题:
javascript
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
(3)事件
v-on: 监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler"
或 @click="handler"
。
事件修饰符:为了方便访问原生 DOM 事件,更专注于数据逻辑不用去处理 DOM 事件的细节
javascript
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
**按键修饰符:**监听键盘事件,检查特定的按键。
.enter
.tab
.delete
(捕获"Delete"和"Backspace"两个按键).esc
.space
.up
.down
.left
.right
javascript
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。
javascript
<input @keyup.page-down="onPageDown" />
10、组件传值
(1)父组件向子组件传值
父组件通过 v-bind 绑定需要传递的数据,子组件上使用 props接收数据。
在 Vue 3 中,props 的使用与 Vue 2 类似,但有一些重要变化。首先,props 的声明方式使用 ES6 的解构语法来声明。其次,Vue 3 强制 props 的类型检查,并提供了更多的选项来控制 props 的行为,例如默认值和必需性等。
在使用 <script setup>
的单文件组件中,props 可以使用 defineProps()
宏来声明,在没有使用 <script setup>
的组件中,props 可以使用 props 选项来声明。
javascript
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
//没有script setup
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}
任何类型的值都可以作为 props 的值被传递。
javascript
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />
所有的 props 都遵循着单向绑定 原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。不应该在子组件中去更改一个 prop。
javascript
const props = defineProps(['foo'])
// ❌ 警告!prop 是只读的!
props.foo = 'bar'
Vue3 组件可以更细致地声明对传入的 props 的校验要求。
javascript
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// 必传但可为 null 的字符串
propD: {
type: [String, null],
required: true
},
// Number 类型的默认值
propE: {
type: Number,
default: 100
},
// 对象类型的默认值
propF: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
// 在 3.4+ 中完整的 props 作为第二个参数传入
propG: {
validator(value, props) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propH: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
(2)子组件向父组件传值
Vue2 中在父组件自定义事件,并使用 v-on 进行监听,在子组件中使用 $emit 触发自定义事件。
javascript
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message-sent', 'Hello from child!');
}
}
};
</script>
Vue 3 引入了新的 setup() API,setup语法糖的是defineEmits。
javascript
<template>
<button @click="clickChild">点击子组件</button>
</template>
<script setup>
import { defineEmits } from 'vue'
// 使用defineEmits创建名称,接受一个数组
const emit = defineEmits(['clickChild'])
const clickChild=()=>{
let param={
content:'b'
}
//传递给父组件
emit('clickChild',param)
}
</script>
<style>
</style>
(3)兄弟组件之间传值
可以通过使用一个共同的父组件,然后将需要共享的数据放在父组件的 data 中,再通过 props 将数据传递给各自的子组件。
(4)跨级组件之间传值
Provide/Inject 是一种在祖先组件和后代组件之间共享数据的方式。通过在祖先组件中使用 provide
方法提供数据,在后代组件中使用 inject
方法获取数据。
javascript
<template>
<child-component></child-component>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
provide() {
return {
message: 'Hello from ancestor!'
};
}
};
</script>
javascript
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
inject: ['message']
};
</script>