选项式API和组合式API
选项式API
选项式API是一种通过包含多个选项的对象来描述组件逻辑的API,其常用的选项包括data、methods、computed、watch等。
组合式API
相比于选项式API,组合式API是将组件中的数据、方法、计算属性、侦听器等代码全部组合在一起,写在setup()函数中。
选项式API的语法格式如下。
<script>
export default {
data() {
return { // 定义数据 }
},
methods: { // 定义方法 },
computed: { // 定义计算属性 },
watch: { // 定义侦听器 }
}
</script>
组合式API的语法格式如下。
<script>
import { computed, watch } from 'vue'
export default {
setup() {
const 数据名 = 数据值
const 方法名 = () => {}
const 计算属性名 = computed(() => {})
watch(侦听器的来源, 回调函数, 可选参数)
return { 数据名, 方法名, 计算属性名 }
}
}
</script>
Vue还提供了setup语法糖,用于简化组合式API的代码。使用setup语法糖时,组合式API的语法格式如下。
<script setup>
import { computed, watch } from 'vue'
// 定义数据
const 数据名 = 数据值
// 定义方法
const 方法名 = () => {}
// 定义计算属性
const 计算属性名 = computed(() => {})
// 定义侦听器
watch(侦听器的来源, 回调函数, 可选参数)
</script>
选项式API和组合式API的关系
Vue提供的选项式API和组合式API这两种写法可以覆盖大部分的应用场景,它们是同一底层系统所提供的两套不同的接口。选项式API是在组合式API的基础上实现的。
生命周期函数
在Vue中,组件的生命周期是指每个组件从被创建到被销毁的整个过程,每个组件都有生命周期。如果想要在某个特定的时机进行特定的处理,可以通过生命周期函数来完成。随着组件生命周期的变化,生命周期函数会自动执行。
组合式API下的生命周期函数如下表所示。
函数 | 说明 |
onBeforeMount() | 组件挂载前 |
onMounted() | 组件挂载成功后 |
onBeforeUpdate() | 组件更新前 |
onUpdated() | 组件中的任意的DOM元素更新后 |
onBeforeUnmount() | 组件实例被销毁前 |
onUnmounted() | 组件实例被销毁后 |
以onMounted()函数为例演示生命周期函数的使用。
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 执行操作
})
</script>
演示生命周期函数的使用方法
创建src\components\LifecycleHooks.vue文件,用于通过生命周期函数查看在特定时间点下的DOM元素。
<template> <div class="container">container</div> </template>
<script setup>
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => {
console.log('DOM元素渲染前', document.querySelector('.container'))
})
onMounted(() => {
console.log('DOM元素渲染后', document.querySelector('.container'))
})
</script>
修改src\main.js文件,切换页面中显示的组件。
import App from './components/LifecycleHooks.vue'
运行结果
选项式API下的生命周期函数如下表所示。
函数 | 说明 |
beforeCreate() | 实例对象创建前 |
created() | 实例对象创建后 |
beforeMount() | 页面挂载前 |
mounted() | 页面挂载成功后 |
beforeUpdate() | 组件更新前 |
updated() | 组件中的任意的DOM元素更新后 |
beforeDestroy() | 组件实例销毁前 |
destroyed() | 组件实例销毁后 |
演示选项式API下beforeCreate()函数和created()函数的使用。
<script>
export default {
data() { return { value: 'Hello Vue.js' } },
beforeCreate() {
console.log('实例对象创建前: ' + this.value)
},
created() {
console.log('实例对象创建后: ' + this.value)
}
}
</script>
组件的注册和引用
当在Vue项目中定义了一个新的组件后,要想在其他组件中引用这个新的组件,需要对新的组件进行注册。在注册组件的时候,需要给组件取一个名字,从而区分每个组件,可以采用帕斯卡命名法(PascalCase)为组件命名。
Vue提供了两种注册组件的方式,分别是全局注册和局部注册。
帕斯卡命名法(PascalCase):
帕斯卡命名法(Pascal Case)是一种在编程中使用的命名约定,它要求将变量名和函数名称等标识符的每个单词首字母大写,并且单词之间直接相连,没有空格。这种命名法通常用于类名、函数名、属性名等标识符的命名。
例如,类名可以使用帕斯卡命名法表示为`MyClass`、`PersonInfo`、`GameManager`;方法名可以是`CalculateTotalScore()`、`PrintUserInfo()`、`InitializeGameWorld()`;属性名可以是`PlayerName`、`HighScore`、`IsGameOver`。
1.全局注册
在实际开发中,如果某个组件的使用频率很高,许多组件中都会引用该组件,则推荐将该组件全局注册。被全局注册的组件可以在当前Vue项目的任何一个组件内引用。在Vue项目的src\main.js文件中,通过Vue应用实例的component()方法可以全局注册组件,该方法的语法格式如下。
component('组件名称', 需要被注册的组件)
上述语法格式中,component()方法接收两个参数,第1个参数为组件名称,注册完成后即可全局使用该组件名称,第2个参数为需要被注册的组件。
在src\main.js文件中注册一个全局组件MyComponent,示例代码如下。
import { createApp } from 'vue';
import './style.css'
import App from './App.vue'
import MyComponent from './components/MyComponent.vue'
const app = createApp(App)
app.component('MyComponent', MyComponent)
app.mount('#app')
component()方法支持链式调用,可以连续注册多个组件,示例代码如下。
app.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
2.局部注册
在实际开发中,如果某些组件只在特定的情况下被用到,推荐进行局部注册。局部注册即在某个组件中注册,被局部注册的组件只能在当前注册范围内使用。
局部注册组件的示例代码如下。
<script>
import ComponentA from './ComponentA.vue'
export default {
components: { ComponentA: ComponentA }
}
</script>
在使用setup语法糖时,导入的组件会被自动注册,无须手动注册,导入后可以直接在模板中使用,示例代码如下。
<script setup>
import ComponentA from './ComponentA.vue'
</script>
引用组件
将组件注册完成后,若要将组件在页面中渲染出来,需要引用组件。
在组件的<template>标签中可以引用其他组件,被引用的组件需要写成标签的形式,标签名应与组件名对应。组件的标签名可以使用短横线分隔或帕斯卡命名法命名。例如,<my-component>标签和<MyComponent>标签都表示引用MyComponent组件。一个组件可以被引用多次,但不可出现自我引用和互相引用的情况,否则会出现死循环。
解决组件之间的样式冲突
掌握组件之间样式冲突问题的解决方法,能够运用<style>标签的scoped属性和深度选择器解决组件之间样式冲突的问题。
在默认情况下,写在Vue组件中的样式会全局生效,很容易造成多个组件之间的样式冲突问题。例如,为ComponentUse组件中的h5元素添加边框样式,具体代码如下。
h5 {
border: 1px dotted black;
}
从上图可以看出,ComponentUse组件、GlobalComponent组件和LocalComponent组件中h5元素的边框样式都发生了改变,但是代码中只有ComponentUse组件设置了边框样式效果,说明组件之间存在样式冲突。
导致组件之间样式冲突的根本原因是:在单页Web应用中,所有组件的DOM结构都是基于唯一的index.html页面进行呈现的。每个组件中的样式都可以影响整个页面中的DOM元素。
在Vue中可以使用scoped属性和深度选择器来解决组件之间的样式冲突。
1.scoped属性
Vue为<style>标签提供了scoped属性,用于解决组件之间的样式冲突。
为<style>标签添加scoped属性后,Vue会自动为当前组件的DOM元素添加一个唯一的自定义属性(如data-v-7ba5bd90),并在样式中为选择器添加自定义属性(如.list[data-v-7ba5bd90]),从而限制样式的作用范围,防止组件之间的样式冲突问题。
下面演示scoped属性的使用。
修改ComponentUse组件,为<style>标签添加scoped属性,具体代码如下。
运行结果
打开开发者工具,切换到Elements面板,查看父组件的h5元素的代码,如下图所示。
从上图可以看出,当<style>标签添加scoped属性后,h5元素和相应的选择器被Vue自动添加了data-v-e4f30916属性,从而解决了样式冲突的问题。
2.深度选择器
如果给当前组件的<style>标签添加了scoped属性,则当前组件的样式对其子组件是不生效的。
如果在添加了scoped属性后还需要让某些样式对子组件生效,则可以使用深度选择器来实现。
深度选择器通过:deep()伪类来实现,在其小括号中可以定义用于子组件的选择器,例如,“:deep(.title)”被编译之后生成选择器的格式为“[data-v-7ba5bd90] .title”。
父组件向子组件传递数据
声明props
若想实现父组件向子组件传递数据,需要先在子组件中声明props,表示子组件可以从父组件中接收哪些数据。
在不使用setup语法糖的情况下,可以使用props选项声明props。props选项的形式可以是对象或字符串数组。声明对象形式的props的语法格式如下。
<script>
export default {
props: {
自定义属性A: 类型,
自定义属性B: 类型,
……
}
}
</script>
如果不需要限制props的类型,可以声明字符串数组形式的props,示例代码如下。
props: ['自定义属性A', '自定义属性B'],
当使用setup语法糖时,可使用defineProps()函数声明props,语法格式如下。
<script setup>
const props = defineProps({'自定义属性A': 类型}, {'自定义属性B': 类型})
</script>
使用defineProps()函数声明字符串数组形式的props,语法格式如下。
const props = defineProps(['自定义属性A', '自定义属性B'])
在组件中声明了props后,可以直接在模板中输出每个prop的值,语法格式如下。
<template>
{{ 自定义属性A }}
{{ 自定义属性B }}
</template>
静态绑定props
当在父组件中引用了子组件后,如果子组件中声明了props,则可以在父组件中向子组件传递数据。如果传递的数据是固定不变的,则可以通过静态绑定props的方式为子组件传递数据。
通过静态绑定props的方式为子组件传递数据,其语法格式如下。
<子组件标签名 自定义属性A="数据" 自定义属性B="数据" />
在上述语法格式中,父组件向子组件的props传递了静态的数据,属性值默认为字符串类型
【注意】
如果子组件中未声明props,则父组件向子组件中传递的数据会被忽略,无法被子组件使用。
动态绑定props
在父组件中使用v-bind可以为子组件动态绑定props,任意类型的值都可以传给子组件的props,包括字符串、数字、布尔值、数组、对象等。
1.字符串
从父组件中为子组件传递字符串类型的props数据,示例代码如下。
<template>
<Child :init="username" />
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const username = ref('小圆')
</script>
上述代码用到了名称为Child的子组件,该子组件的示例代码如下。
<template></template>
<script setup>
const props = defineProps(['init'])
console.log(props)
</script>
2.数字
从父组件中为子组件传递数字类型的props数据,示例代码如下。
<template>
<Child :init="12" />
<Child :init="age" />
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const age = ref(12)
</script>
3.布尔值
从父组件中为子组件传递布尔类型的props数据,示例代码如下。
<template>
<Child init />
<Child :init="false" />
<Child :init="isFlag" />
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const isFlag = ref(true)
</script>
4.数组
从父组件中为子组件传递数组类型的props数据,示例代码如下。
<template>
<Child :init="['唱歌', '跳舞', '滑冰']" />
<Child :init="hobby" />
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const hobby = ref(['唱歌', '跳舞', '滑冰'])
</script>
5.对象
从父组件中为子组件传入一个对象类型的props数据,或者将对象中的部分属性作为被传入的props数据,示例代码如下。
<template>
<Child :init=“{ height: '180厘米’, weight: '70千克' }" />
<Child :height="bodyInfo.height" :weight="bodyInfo.weight" />
<Child v-bind="bodyInfo" />
</template>
<script setup>
import Child from './Child.vue'
import { reactive } from 'vue'
const bodyInfo = reactive({
height: '180厘米',
weight: '70千克'
})
</script>
props单向数据流
在Vue中,所有的props都遵循单向数据流原则,props数据因父组件的更新而变化,变化后的数据将向下流往子组件,而且不会逆向传递,这样可以防止因子组件意外变更props导致数据流向难以理解的问题。
每次父组件绑定的props发生变更时,子组件中的props都将会刷新为最新的值。开发者不应该在子组件内部改变props,如果这样做,Vue会在浏览器的控制台中发出警告。
验证props
在封装组件时,可以在子组件中对从父组件传递过来的props数据进行合法性校验,从而防止出现数据不合法的问题。
使用字符串数组形式的props的缺点是无法为每个prop指定具体的数据类型,而使用对象形式的props的优点是可以对每个prop进行数据类型的校验。
对象形式的props可以使用多种验证方案,包括基础类型检查、必填项的校验、属性默认值、自定义验证函数等。在声明props时,可以添加验证方案。
1.基础类型检查
在开发中,有时需要对从父组件中传递过来的props数据进行基础类型检查,这时可以通过type属性检查合法的类型,如果从父组件中传递过来的值不符合此类型,则会报错。
常见的类型有String(字符串)、Number(数字)、Boolean(布尔值)、Array(数组)、Object(对象)、Date(日期)、Function(函数)、Symbol(符号)以及任何自定义构造函数。
为props指定基础类型检查,示例代码如下。
通过配置对象的形式定义验证规则,示例代码如下。
如果某个prop的类型不唯一,可以通过数组的形式为其指定多个可能的类型,示例代码如下
必填项的校验
父组件向子组件传递props数据时,有可能传递的数据为空,但是在子组件中要求该数据是必须传递的。此时,可以在声明props时通过required属性设置必填项,强调组件的使用者必须传递属性的值,示例代码如下。
props: {
自定义属性: { required: true },
}
默认属性值
在声明props时,可以通过default属性定义属性默认值,当父组件没有向子组件的属性传递数据时,属性将会使用默认值,示例代码如下。
props: {
自定义属性: { default: 0 },
}
自定义验证函数
如果需要对从父组件中传入的数据进行验证,可以通过validator()函数来实现。validator()函数可以将prop的值作为唯一参数传入自定义验证函数,如果验证失败,则会在控制台中发出警告。为prop属性指定自定义验证函数的示例代码如下。
props: {
自定义属性: {
validator(value) {
return ['success', 'warning', 'danger'].indexOf(value) !== -1;
},
},
}
子组件向父组件传递数据
在子组件中声明自定义事件
若想使用自定义事件,首先需要在子组件中声明自定义事件。
在不使用setup语法糖时,可以通过emits选项声明自定义事件,示例代码如下。
<script>
export default {
emits: ['demo']
}
</script>
在使用setup语法糖时,需要通过调用defineEmits()函数声明自定义事件,示例代码如下。
<script setup>
const emit = defineEmits(['demo'])
</script>
在上述代码中,defineEmits()函数的参数与emits选项中的相同。
在子组件中触发自定义事件
在子组件中声明自定义事件后,接着需要在子组件中触发自定义事件。
当使用场景简单时,可以使用内联事件处理器,通过调用$emit()方法触发自定义事件,将数据传递给使用的组件,示例代码如下。
<button @click="$emit('demo', 1)">按钮</button>
在上述代码中,$emit()方法的第1个参数为字符串类型的自定义事件的名称,第2个参数为需要传递的数据,当触发当前组件的事件时,该数据会传递给父组件。
除了使用内联方式外,还可以直接定义方法来触发自定义事件。
在不使用setup语法糖时,可以从setup()函数的第2个参数(即setup上下文对象)来访问到emit()方法,示例代码如下。
export default {
setup(props, ctx) {
const update = () => {
ctx.emit('demo', 2)
}
return { update }
}
}
如果使用setup语法糖,可以调用emit()函数来实现,示例代码如下。
<script setup>
const update = () => {
emit('demo', 2)
}
</script>
在父组件中监听自定义事件
在父组件中通过v-on可以监听子组件中抛出的事件,示例代码如下。
<子组件名 @demo="fun" />
在上述代码中,当触发demo事件时,会接收到从子组件中传递的参数,同时会执行fun()方法。父组件可以通过value属性接收从子组件中传递来的参数。
在父组件中定义fun()方法,示例代码如下。
const fun = value => {
console.log(value)
}
演示子组件向父组件传递数据的方法
1.创建src\components\CustomSubComponent.vue文件,用于展示子组件的相关内容。
<template>
<p>count值为:{{ count }}</p>
<button @click="add">加n</button>
</template>
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['updateCount'])
const count = ref(1)
const add = () => { count.value++
emit('updateCount', 2) }
</script>
2.创建src\components\CustomEvents.vue文件,用于展示父组件的相关内容。
<template>
<p>父组件当前的值为:{{ number }}</p>
<CustomSubComponent @updateCount="updateEmitCount" />
</template>
<script setup>
import CustomSubComponent from './CustomSubComponent.vue'
import { ref } from 'vue'
const number = ref(10)
const updateEmitCount = (value) => { number.value += value }
</script>
3.修改src\main.js文件,切换页面中显示的组件。
import App from './components/CustomEvents.vue'
运行示例
单击“加n”按钮后的页面效果如下图所示。
跨级组件之间的数据传递
Vue提供了跨级组件之间数据传递的方式——依赖注入。一个父组件相对于其所有的后代组件而言,可作为依赖提供者。而任何后代的组件树,无论层级多深,都可以注入由父组件提供的依赖。
对于父组件而言,如果要为后代组件提供数据,需要使用provide()函数。对于子组件而言,如果想要注入上层组件或整个应用提供的数据,需要使用inject()函数。
1.provide()函数
provide()函数可以提供一个值,用于被后代组件注入。provide()函数的语法格式如下。
provide(注入名, 注入值)
provide()函数可以接收2个参数,第1个参数是注入名,后代组件会通过注入名查找所需的注入值;第2个参数是注入值,值可以是任意类型,包括响应式数据,例如通过ref()函数创建的数据。
在不使用setup语法糖的情况下,provide()函数必须在组件的setup()函数中调用。使用provide()函数的示例代码如下。
<script>
import { ref, provide } from 'vue'
export default {
setup() {
const count = ref(1)
provide( 'message', count )
}
}
</script>
当使用setup语法糖时,使用provide()函数的示例代码如下。
<script setup>
import { provide } from 'vue'
provide('message', 'Hello Vue.js')
</script>
provide()函数除了可以在某个组件中提供依赖外,还可以全局提供依赖。例如,在src\main.js文件中添加全局依赖,示例代码如下
const app = createApp(App)
app.provide('message', 'Hello Vue.js')
2.inject()函数
通过inject()函数可以注入上层组件或者整个应用提供的数据。inject()函数的语法格式如下
inject(注入值, 默认值, 布尔值)
inject()函数有3个参数。
- 第1个参数是注入值,Vue会遍历父组件,通过匹配注入的值来确定所提供的值,如果父组件链上多个组件为同一个数据提供了值,那么距离更近的组件将会覆盖更远的组件所提供的值。
- 第2个参数是可选的,用于在没有匹配到注入的值时使用默认值。第2个参数可以是工厂函数,用于返回某些创建起来比较复杂的值。如果提供的默认值是函数,还需要将false作为第3个参数传入,表明这个函数就是默认值,而不是工厂函数。
- 第3个参数是可选的,类型为布尔值,当参数值为false时,表示默认值是函数;当参数值为true时,表示默认值为工厂函数;当省略参数值时,表示默认值为其他类型的数据,不是函数或工厂函数。
在不使用setup语法糖的情况下,inject()函数必须在组件的setup()函数中调用。使用inject()函数的示例代码如下。
<script>
import { inject } from 'vue';
export default {
setup() {
const count = inject('count')
const foo = inject('foo', 'default value')
const baz = inject('foo', () => new Map())
const fn = inject('function', () => { }, false)
}
}
</script>
当使用setup语法糖时,使用inject()函数的示例代码如下。
<script setup>
import { inject } from 'vue';
const count = inject('count')
</script>
演示跨级组件之间的数据传递
1.创建src\components\ProvideChildren.vue文件,用于展示子组件中的相关内容。
<template>
<div>子组件</div>
</template>
2.创建src\components\ProvideParent.vue文件,用于展示父组件中的相关内容。
<template>
<div>父组件</div>
<hr>
<ProvideChildren />
</template>
<script setup>
import ProvideChildren from './ProvideChildren.vue'
</script>
3.创建src\components\ProvideGrand.vue文件,用于展示父组件的父组件(即爷爷组件)中的相关内容
<template> <div>爷爷组件</div> <hr> <ProvideParent />
</template>
<script setup>
import ProvideParent from './ProvideParent.vue'
import { ref, provide } from 'vue'
let money = ref(1000)
let updateMoney = (value) => { money.value += value}
provide('money', money)
provide('updateMoney', updateMoney)
</script>
4.修改src\components\ProvideChildren.vue文件,通过inject()函数接收爷爷组件中传过来的数据。
<template>
<div>子组件</div> <hr> 从爷爷组件接收到的资金:{{ money }}
<button @click="updateMoney(500)">单击按钮增加资金</button>
</template>
<script setup>
import { inject } from 'vue'
let money = inject('money')
let updateMoney = inject('updateMoney')
</script>
5.修改src\main.js文件,切换页面中显示的组件。
import App from './components/ProvideGrand.vue'
示例结果
单击“单击按钮增加资金”按钮后页面效果如下图所示。
本章小结
本章主要讲解了Vue中组件的基础知识,内容主要包括选项式API和组合式API、生命周期函数、组件的注册和引用、解决组件之间的样式冲突、父组件向子组件传递数据、子组件向父组件传递数据和跨级组件之间的数据传递。通过本章的学习,能够对Vue的组件有整体的认识,能够利用组件进行项目开发。