重学Vue-认识组件

重学Vue-认识组件

技术博客 admin 525 浏览

组件注册

全局注册

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 局部注册

全局注册方便但是有些问题。

  1. 如果注册的全局组件没有使用到,不能享受树摇。
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确,难以找到对应的组件

组件名格式

使用 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 的需求通常来源于以下两种场景:

  1. 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 事件监听器。最常见的例子就是 classstyle 和 id

当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
如果这个根节点也是一个组件,会继续透传到该组件

禁用 Attributes 继承

js
复制代码
<script setup> defineOptions({ inheritAttrs: false }) // ...setup 逻辑 </script>

这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。

js
复制代码
<span>Fallthrough attribute: {{ $attrs }}</span>
  1. 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问。
  2. 像 @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”。

条件插槽

有时你需要根据插槽是否存在来渲染某些内容。

你可以结合使用 $slots 属性与 v-if 来实现。

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', '这是默认值')

源文:重学Vue-认识组件

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!