组件
通用型组件就是各大组件库的组件风格,包括按钮、表单、弹窗等通用功能。
业务型组件包含业务的交互逻辑,包括购物车、登录注册等,会和我们不同的业务强绑定。
特点
- 可通过具名导出在一个文件中导出多个组件
- 在 DOM 模板中,我们必须显式地写出关闭标签。
- 限制
- 某些 HTML 元素对于放在其中的元素类型有限制,例如
<ul>,<ol>,<table> 和 <select>
- 相应的,某些元素仅在放置于特定元素中时才会显示,例如
<li>,<tr> 和 <option>
- 解决:使用is
<table> <tr is="vue:blog-post-row"></tr> </table>
- 一个可以通过其“name”选项递归渲染自己的组件,(如果使用单文件组件,则从文件名推断)
- 全局注册组件问题
- 需要注册
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除
- 全局注册在大型项目中使项目的依赖关系变得不那么明确,定位不易,不好维护。
- 局部组件(推荐)
- 无需注册,引入直接使用
- 局部注册的组件需要在使用它的父组件中显式导入
- 只能在该父组件中使用。
- 使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好
- 格式-双驼峰(推荐)
- props
- 为了和 HTML attribute 对齐
- 一般将props传入写为
kebab-case
形式 - 仅写上 prop 但不传值,会隐式转换为
true
- 一般将props传入写为
- 组件名-双驼峰(推荐)
- 单向绑定、单向数据流
prop
只读,组件内不可更新prop
更改场景- prop为初始值,后续作为组件内部变量使用。可使用
ref(props.a)
- 转换prop值。使用
computed
- prop为初始值,后续作为组件内部变量使用。可使用
- 更改对象 / 数组类型的 props,可被更改但不推荐。推荐使用抛出事件来处理。
defineProps()
宏中的参数不可以访问<script setup>
中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。- 除
Boolean
外的未传递的可选prop
将会有一个默认值undefined
Boolean
类型的未传递prop
将被转换为false
- 如果声明了
default
值,外部设置值为undefine
无效 Boolean
的使用默认值规则。一个prop设置多种包含了Boolean类型的类型,该规则都会被使用。<!-- 等同于传入 :disabled="true" --> <MyComponent disabled /> <!-- 等同于传入 :disabled="false" --> <MyComponent />
- 事件
- 和原生 DOM 事件不一样,组件触发的事件没有冒泡机制。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案。
- emit 可以安全地被解构
- emits 选项还支持对象语法,允许我们对触发事件的参数进行验证。
const emit = defineEmits({ submit(payload) { // 通过返回值为 `true` 还是为 `false` 来判断 // 验证是否通过,提交data给父组件 } })
- 组件事件会覆盖原生事件名
- v-model的使用
- API用法3
- 默认值
modelValue
,配合update:modelValue
- 通过给 v-model 指定一个参数来更改
<MyComponent v-model:title="bookTitle" />
- 自定义的修饰符,设置
modelModifiers
<script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } }) defineEmits(['update:modelValue']) console.log(props.modelModifiers) // { capitalize: true } // 可对该值进行处理 </script> <template> <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template> <MyComponent v-model.capitalize="myText" />
- 另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的计算属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件
<!-- CustomInput.vue --> <script setup> import { computed } from 'vue' const props = defineProps(['modelValue']) const emit = defineEmits(['update:modelValue']) const value = computed({ get() { return props.modelValue }, set(value) { emit('update:modelValue', value) } }) </script> <template> <input v-model="value" /> </template>
- 透传
- 当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
- 如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并
- 监听器同样式效果
- 下一个组件会在根节点上渲染另一个组件,会透传。
- 如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置
inheritAttrs: false
。 - 禁用透传的组件中
- 直接用 $attrs 访问。
- 使用
v-bind="$attrs"
将传递的数据进一步传给其他指定组件。 - 访问:
const attrs = useAttrs()
attrs
不是响应式- 需要使用响应式
attrs
- 使用
prop
。 - 使用
onUpdated()
使得在每次更新时结合最新的attrs
执行副作用
- 使用
- 有着多个根节点的组件没有自动 attribute 透传行为,如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。
- 插槽
- 具名作用域插槽
分两个点,名字和插槽数据传递
子组件内插槽:
父组件使用:<!-- <MyComponent> 的模板 --> <div> <slot name="test" :text="greetingMessage" :count="1"></slot> </div>
<MyComponent #test="slotProps"> {{ slotProps.text }} {{ slotProps.count }} </MyComponent>
- 高级列表组件示例
使用:<FancyList :api-url="url" :per-page="10"> <template #item="{ body, username, likes }"> <div class="item"> <p>{{ body }}</p> <p>by {{ username }} | {{ likes }} likes</p> </div> </template> </FancyList>
<ul> <li v-for="item in items"> <slot name="item" v-bind="item"></slot> </li> </ul>
- 无渲染组件
- 只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。
- 适用:作用域插槽在需要同时封装逻辑、组合视图界面时
- 依赖注入
- 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
- 用法:
注入:
调用:<script setup> import { ref, provide } from 'vue' // 一般使用 provide(/* 注入名 */ 'message', /* 值 */ 'hello!') // 响应式 // 提供的响应式状态使后代组件可以由此和提供者建立响应式的联系 const count = ref(0) provide('key', count) </script>
<script setup> import { inject } from 'vue' // 如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。 // 这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。 const message = inject('message') // 注入默认值 const value = inject('message', '这是默认值') // 调用函数或者初始化类 const value = inject('key', () => new ExpensiveClass()) </script>
- 当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
- 在注入方组件中更改数据,提供
refA
及setRefA
两个函数。 - 确保数据被注入方的组件更改:
readonly()
- 推荐在一个单独的文件中导出这些注入名 Symbol
- 异步组件
- 用法:
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
...
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
- 搭配内置组件 Suspense 使用
API用法
- defineProps,编译宏命令,不需要显示引入,可直接使用。
- defineEmits
设置:
definedEmits([
'sendData'
])
使用:
emit('sendData', data)
- v-model
设置:
definedProps({
modelValue: 'x'
})
const emit = definedEmits([
'update:modelValue'
])
使用:
emit('update:modelValue')