定义好的组件需要注册才能被使用。 注册方式有两种
全局注册
局部注册
.component() 方法,让组件在当前 Vue 应用中全局可用。 在 main.ts 中
import?'./assets/main.css'
import?{?createApp?}?from?'vue'
import?{?createPinia?}?from?'pinia'
import?GlobalTitle?from?'@/components/GlobalTitle.vue'
import?App?from?'./App.vue'
import?router?from?'./router'
const?app?=?createApp(App)
app.component('GlobalTitle',?GlobalTitle)
app.use(createPinia())
app.use(router)
app.mount('#app')
在 vue 中直接使用无需导入
<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
</script>
<template>
??<div?class="container">
????<GlobalTitle></GlobalTitle>
??</div>
</template>
<style?lang="scss"?scoped>
.container?{
}
</style>
效果:
全局注册的组件可以在此应用的任意组件的模板中使用,所有的子组件也可以使用全局注册的组件。
全局组件缺点
全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。
它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
定义组件
<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
</script>
<template>
??<div?class="container">
????<h2>这是一个局部组件</h2>
??</div>
</template>
<style?lang="scss"?scoped>
.container?{
}
</style>
局部使用组件
<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
import?PartTitle?from?'@/components/PartTitle.vue'
</script>
<template>
??<div?class="container">
????<PartTitle?/>
??</div>
</template>
<style?lang="scss"?scoped>
.container?{
}
</style>
推荐 PascalCase 作为组件名的注册格式。
PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
<PascalCase /> 在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来。
在单文件组件和内联字符串模板中,我们都推荐这样做。
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。
这意味着一个以 MyComponent 为名注册的组件,在模板中可以通过 <MyComponent> 或 <my-component> 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。
一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute。
setup 写法
组件定义
<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
const?props?=?defineProps(['person'])
</script>
<template>
??<div?class="container">
????<h1>{{?props.person?}}</h1>
??</div>
</template>
<style?lang="scss"?scoped>
.container?{
}
</style>
组件使用
<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
import?Com14?from?'@/components/Com14.vue'
</script>
<template>
??<div?class="container">
????<Com14?person="'王大可'"?/>
??</div>
</template>
<style?lang="scss"?scoped>
.container?{
}
</style>
声明对象形式的属性 setup JavaScript 形式
defineProps({
??title:?String,
??likes:?Number
})
setup TypeScript 形式
<script?setup?lang="ts">
defineProps<{
??title?:?string
??likes?:?number
}>()
</script>
对象形式的 props 声明不仅可以一定程度上作为组件的文档,而且如果其他开发者在使用你的组件时传递了错误的类型,也会在浏览器控制台中抛出警告。
如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符
defineProps({
??greetingMessage:?String
})
<span>{{?greetingMessage?}}</span>
//推荐写法
<MyComponent?greeting-message="hello"?/>
//或者
<MyComponent?greetingMessage="hello"?/>
模版上使用属性则通常会将其写为 kebab-case 形式,这样和 HTML attribute 写法就一致了。
简单讲 就是传入数值是字符串还是变量
//这是静态
<Com14?person="王大可"?/>
//这是动态
<Com14?:person="personName"?/>
v-bind 直接绑定对象,即只使用 v-bind 而非某一个属性
const?post?=?{
id:?1,
title:?'My?Journey?with?Vue'
}
<BlogPost?v-bind="post"?/>
//?实际等价于
<BlogPost?:id="post.id"?:title="post.title"?/>
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
const?props?=?defineProps(['foo'])
//???警告!prop?是只读的!
props.foo?=?'bar'
「更改 props 的场景:」
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。
在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可
const?props?=?defineProps(['initialCounter'])
//?计数器只是将?props.initialCounter?作为初始值
//?像下面这样做就使?prop?和后续更新无关了
const?counter?=?ref(props.initialCounter)
需要对传入的 prop 值做进一步的转换。
在这种情况中,最好是基于该 prop 值定义一个计算属性:
const?props?=?defineProps(['size'])
//?该?prop?变更时计算属性也会自动更新
const?normalizedSize?=?computed(()?=>?props.size.trim().toLowerCase())
Vue 组件可以更细致地声明对传入的 props 的校验要求。
比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。
defineProps({
??//?基础类型检查
??//?(给出?`null`?和?`undefined`?值则会跳过任何类型检查)
??propA:?Number,
??//?多种可能的类型
??propB:?[String,?Number],
??//?必传,且为?String?类型
??propC:?{
????type:?String,
????required:?true
??},
??//?Number?类型的默认值
??propD:?{
????type:?Number,
????default:?100
??},
??//?对象类型的默认值
??propE:?{
????type:?Object,
????//?对象或数组的默认值
????//?必须从一个工厂函数返回。
????//?该函数接收组件所接收到的原始?prop?作为参数。
????default(rawProps)?{
??????return?{?message:?'hello'?}
????}
??},
??//?自定义类型校验函数
??//?在?3.4+?中完整的?props?作为第二个参数传入
??propF:?{
????validator(value,?props)?{
??????//?The?value?must?match?one?of?these?strings
??????return?['success',?'warning',?'danger'].includes(value)
????}
??},
??//?函数类型的默认值
??propG:?{
????type:?Function,
????//?不像对象或数组的默认,这不是一个
????//?工厂函数。这会是一个用来作为默认值的函数
????default()?{
??????return?'Default?function'
????}
??}
})
defineProps() 宏中的参数不可以访问 <script setup> 中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。
额外说明:
所有 prop 默认都是可选的,除非声明了 required: true
除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined。
Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。
如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。
当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。
运行时类型检查
校验选项中的 type 可以是下列这些原生构造函数:
String
Number
Boolean
Array
Object
Date
Function
Symbol
type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。
class?Person?{
??constructor(firstName,?lastName)?{
????this.firstName?=?firstName
????this.lastName?=?lastName
??}
}
defineProps({
??author:?Person
})
Vue 会通过 instanceof Person 来校验 author prop 的值是否是 Person 类的一个实例。
组件属性使用 Boolean 类型
defineProps({
??disabled:?Boolean
})
<!--?等同于传入?:disabled="true"?-->
<MyComponent?disabled?/>
<!--?等同于传入?:disabled="false"?-->
<MyComponent?/>
prop 被声明为允许多种类型时
只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用
//?disabled?将被转换为?true
defineProps({
??disabled:?[Boolean,?Number]
})
//?disabled?将被转换为?true
defineProps({
??disabled:?[Boolean,?String]
})
//?disabled?将被转换为?true
defineProps({
??disabled:?[Number,?Boolean]
})
//?disabled?将被解析为空字符串?(disabled="")
defineProps({
??disabled:?[String,?Boolean]
})