组件注册
全局注册
js
复制代码
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
.component()
方法可以被链式调用:
js
复制代码
app .component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
局部注册
js
复制代码
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<ComponentA />
</template>
如果没有使用 <script setup>
,则需要使用 components
选项来显式注册:
js
复制代码
import ComponentA from './ComponentA.js'
export default {
components: {
ComponentA
},
setup() {
// ...
}
}
全局注册 vs 局部注册
全局注册方便但是有些问题。
- 如果注册的全局组件没有使用到,不能享受树摇。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确,难以找到对应的组件
组件名格式
使用 PascalCase 作为组件名的注册格式
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent
为名注册的组件,在模板中可以通过 <MyComponent>
或 <my-component>
引用
Props
在使用 <script setup>
的单文件组件中,props 可以使用 defineProps()
宏来声明:
js
复制代码
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
除了使用字符串数组来声明 prop 外,还可以使用对象的形式:
js
复制代码
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
注意这是的值是该 prop 预期类型的构造函数,不是ts声明,比如,如果要求一个 prop 的值是
number
类型,则可使用Number
构造函数作为其声明的值。
使用ts限制props类型,需要写成泛型
js
复制代码
<script setup lang="ts">
defineProps<{
title?: string
likes?: number
}>()
</script>
Props命名格式
如果一个 prop 的名字很长,应使用 camelCase 形式
虽然理论上你也可以在向子组件传递 props 时使用 camelCase 形式 (使用 DOM 内模板时例外),但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式:
js
复制代码
<MyComponent greeting-message="hello" />
静态 vs. 动态 Prop
除非绑定的是字符串,否则都需要使用v-bind
动态绑定,甚至绑定的是一个数字或者布尔值,都需要使用v-bind.官方解释是因为这是一个 JavaScript 表达式而不是一个字符串,因此即使是绑定一个数组/对象常量都需要使用v-bind
props是单向数据流
这意味着你不应该在子组件里修改传递过来的props
js
复制代码
const props = defineProps(['foo'])
// ❌ 警告!prop 是只读的!
props.foo = 'bar'
然而当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。
在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合。在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。
导致你想要更改一个 prop 的需求通常来源于以下两种场景:
- prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
js
复制代码
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
2.需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
js
复制代码
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
事件
js
复制代码
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
组件 v-model
v-model
可以在组件上使用以实现双向绑定。
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel()
宏:
js
复制代码
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Parent bound v-model is: {{ model }}</div>
</template>
js
复制代码
//父组件
<Child v-model="countModel" />
:xx是v-mdoel名字,.xx是v-model修饰符
多个 v-model
绑定
js
复制代码
//父组件
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
//子组件
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
处理 v-model
修饰符
js
复制代码
//父组件
<MyComponent v-model.capitalize="myText" />
//子组件
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>
两种都有的情况
defineModel再加一个参数即可
js
复制代码
const [firstName, firstNameModifiers] = defineModel('firstName',{
set(value) {
if (firstNameModifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
透传 Attributes
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。最常见的例子就是 class
、style
和 id
。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
如果这个根节点也是一个组件,会继续透传到该组件
禁用 Attributes 继承
js
复制代码
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs
访问到。
js
复制代码
<span>Fallthrough attribute: {{ $attrs }}</span>
- 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像
foo-bar
这样的一个 attribute 需要通过$attrs['foo-bar']
来访问。 - 像
@click
这样的一个v-on
事件监听器将在此对象下被暴露为一个函数$attrs.onClick
。
插槽 Slots
插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
在 JavaScript 中访问透传 Attributes
js
复制代码
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
需要注意的是,虽然这里的 attrs
对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated()
使得在每次更新时结合最新的 attrs
执行副作用。
插槽默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个 <SubmitButton>
组件:
js
复制代码
<button type="submit">
<slot>
Submit <!-- 默认内容 -->
</slot>
</button>
具名插槽
js
复制代码
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
这类带 name
的插槽被称为具名插槽 (named slots)。没有提供 name
的 <slot>
出口会隐式地命名为“default”。
条件插槽
有时你需要根据插槽是否存在来渲染某些内容。
js
复制代码
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div v-if="$slots.default" class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
动态插槽名
js
复制代码
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
插槽的内容可以访问到子组件的状态
像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
js
复制代码
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
默认插槽如何接受 props,通过子组件标签上的 v-slot
指令,直接接收到了一个插槽 props 对象:
js
复制代码
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
如果是具名插槽,需要在template写
js
复制代码
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template>
标签
依赖注入
解决prop 逐级透传
Provide (提供)
js
复制代码
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
一个组件可以多次调用 provide()
,使用不同的注入名,注入不同的依赖值。
第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref:提供的响应式状态使后代组件可以由此和提供者建立响应式的联系。
除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
js
复制代码
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
Inject (注入)
js
复制代码
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。
但是并不推荐在子组件直接修改传递过来的值,为了便于维护,推荐在供给方组件内声明并提供一个更改数据的方法函数,将这个方法也provide出来
js
复制代码
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
注入默认值
js
复制代码
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!