Vue3 官方文档速通(上)https://developer.aliyun.com/article/1511920?spm=a2c6h.13148508.setting.14.f8774f0euyBLtl
三 深入组件
1. 注册
1.1 全局注册
使用 .component() 方法,让组件在全局可用:
import { createApp } from "vue"; const app = createApp({}); app.component( // 注册的名字 "MyComponent", // 组件的实现 { /* ... */ } );
.component() 可以链式调用:
app .component("ComponentA", ComponentA) .component("ComponentB", ComponentB) .component("ComponentC", ComponentC);
注意:全局注册存在以下几个问题:1. 全局注册组件后,即使没有被实际使用,仍会被打包在 JS 文件中。2. 全局注册使依赖关系变得不那么明确。不容易定位子组件的实现。
1.2 局部注册
导入后可以直接使用:
<script setup> import ComponentA from "./ComponentA.vue"; </script> <template> <ComponentA /> </template>
1.3 组件名格式
官方推荐使用 PascalCase 作为组件名的注册格式。
2. Props
2.1 Props 声明
使用 defineProps() 宏来声明:
<script setup> // defineProps传入字符串数组 const props = defineProps(["foo"]); // 或 传入对象:可进行类型检测。 const props = defineProps({ title: String, likes: Number, }); console.log(props.foo); </script>
2.2 传递 prop 的细节
如果 prop 的名字很长,官方建议使用 camelCase 形式:
defineProps({ greetingMessage: String, });
传递 props 时,官方推荐以 kebab-case 形式:
<MyComponent greeting-message="hello" />
传递不同类型的值:
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind --> <!-- 因为这是一个 JS 表达式而不是一个字符串 --> <BlogPost :likes="42" /> <!-- 根据一个变量的值动态传入 --> <BlogPost :likes="post.likes" /> <!-- 仅写上 prop 但不传值,会隐式转换为 `true` --> <BlogPost is-published /> <!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind --> <!-- 因为这是一个 JS 表达式而不是一个字符串 --> <BlogPost :is-published="false" /> <!-- 根据一个变量的值动态传入 --> <BlogPost :is-published="post.isPublished" /> <!-- 虽然这个数组是个常量,我们还是需要使用 v-bind --> <!-- 因为这是一个 JS 表达式而不是一个字符串 --> <BlogPost :comment-ids="[234, 266, 273]" /> <!-- 根据一个变量的值动态传入 --> <BlogPost :comment-ids="post.commentIds" /> <!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind --> <!-- 因为这是一个 JS 表达式而不是一个字符串 --> <!-- <BlogPost :author="{ name: 'Veronica', company: 'Veridian Dynamics', }" /> -->
一个对象绑定多个 prop:
const post = { id: 1, title: "My Journey with Vue", };
<BlogPost v-bind="post" /> <!-- 等价于: --> <BlogPost :id="post.id" :title="post.title" />
2.3 单向数据流
props 都遵循单向绑定原则,不允许在子组件中去更改一个 prop。但有两个场景可能想要修改 prop:1.prop 被用于传入初始值,之后想将其作为一个响应式属性。2. 需要对传入的 prop 值做进一步的转换。
2.4 Prop 校验
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" }; }, }, // 自定义类型校验函数 propF: { validator(value) { // The value must match one of these strings return ["success", "warning", "danger"].includes(value); }, }, // 函数类型的默认值 propG: { type: Function, // 不像对象或数组的默认,这不是一个 // 工厂函数。这会是一个用来作为默认值的函数 default() { return "Default function"; }, }, });
自定义类或构造函数去验证,校验是否 Person 类的实例:
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } } defineProps({ author: Person, });
2.5 Boolean 类型转换
<!-- 等同于传入 :disabled="true" --> <MyComponent disabled /> <!-- 等同于传入 :disabled="false" --> <MyComponent />
3. 事件
3.1 触发与监听事件
模板表达式可以直接使用 $emit 触发自定义事件:
<!-- MyComponent --> <button @click="$emit('someEvent')">click me</button>
父组件通过@来监听事件:
<MyComponent @some-event="callback" />
3.2 事件参数
<button @click="$emit('increaseBy', 1)"> Increase by 1 </button>
<MyButton @increase-by="(n) => (count += n)" />
3.3 声明触发的事件
通过 defineEmits() 宏来声明要触发的事件:
<script setup> const emit = defineEmits(["inFocus", "submit"]); function buttonClick() { emit("submit"); } </script>
3.4 事件校验
校验事件参数是否符合要求,返回布尔值表明事件是否符合:
<script setup> const emit = defineEmits({ // 没有校验 click: null, // 校验 submit 事件 submit: ({ email, password }) => { if (email && password) { return true; } else { console.warn("Invalid submit event payload!"); return false; } }, }); function submitForm(email, password) { emit("submit", { email, password }); } </script>
4. 组件 v-model
v-model 在 input 上的用法:
<input v-model="searchText" /> <!-- 展开 --> <input :value="searchText" @input="searchText = $event.target.value" />
v-model 在组件上的用法,通过 modelValue,update:modelValue 设置:
<!-- CustomInput.vue --> <script setup> defineProps(["modelValue"]); defineEmits(["update:modelValue"]); </script> <template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template>
<CustomInput v-model="searchText" /> <!-- 展开 --> <CustomInput :model-value="searchText" @update:model-value="newValue => searchText = newValue" />
<!-- 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>
4.1 v-model 的参数
<MyComponent v-model:title="bookTitle" />
<!-- MyComponent.vue --> <script setup> defineProps(["title"]); defineEmits(["update:title"]); </script> <template> <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" /> </template>
4.2 多个 v-model 的绑定
<UserName v-model:first-name="first" v-model:last-name="last" />
<script setup> defineProps({ firstName: String, lastName: String, }); defineEmits(["update:firstName", "update:lastName"]); </script> <template> <input type="text" :value="firstName" @input="$emit('update:firstName', $event.target.value)" /> <input type="text" :value="lastName" @input="$emit('update:lastName', $event.target.value)" /> </template>
4.3 处理 v-model 修饰符
<MyComponent v-model.capitalize="myText" />
<script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) }, }); const emit = defineEmits(["update:modelValue"]); function emitValue(e) { let value = e.target.value; if (props.modelModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } emit("update:modelValue", value); } </script> <template> <input type="text" :value="modelValue" @input="emitValue" /> </template>
v-model 参数和修饰符一起绑定:
<MyComponent v-model:title.capitalize="myText"></MyComponent>
const props = defineProps(['title', 'titleModifiers']) defineEmits(['update:title']) console.log(props.titleModifiers) // { capitalize: true }
5. 透传 Attributes
5.1 Attributes 继承
class、style、id 传递给组件,透传给组件的元素。如果只有一个根节点,默认透传给根节点上,如果有多个根节点,通过 v-bind='$attrs'来确定透传给哪个根节点。
5.2 禁用 Attributes 继承
应用场景:组件只有一个根节点,但透传的 attribute 需要传到其他元素上。通过设置 inheritAttrs 选项为 false,v-bind='$attrs',来完全控制透传:
<script setup> defineOptions({ inheritAttrs: false, }); // ...setup 逻辑 </script> <template> <div class="btn-wrapper"> <button class="btn" v-bind="$attrs">click me</button> </div> </template>
5.3 在 JS 中访问透传 Attributes
使用 useAttrs() API 来访问一个组件的所有透传 attribute:
<script setup> import { useAttrs } from "vue"; const attrs = useAttrs(); </script>
6. 插槽
6.1 插槽内容与出口
通过 确定插槽插入的位置:
<!-- FancyButton.vue --> <button class="fancy-btn"> <!-- 插槽出口 --> <slot></slot> </button>
<FancyButton> <!-- 插槽内容 --> Click me! </FancyButton>
6.2 渲染作用域
插槽内容可以访问父组件的状态,无法访问子组件的状态:
<span>{{ message }}</span> <FancyButton>{{ message }}</FancyButton>
6.3 默认内容
外部没有传递内容,展示默认内容:
<button type="submit"> <slot> <!-- 默认内容 --> Submit </slot> </button>
6.4 具名插槽
通过 来设定,name 默认值为 default:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot name="default"></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
通过 v-slot 有对应的简写 # 来使用,因此
Vue3 官方文档速通(下)https://developer.aliyun.com/article/1511958?spm=a2c6h.13148508.setting.32.f8774f0euyBLtl