🌤️ Vue3
相比于组件内的状态 ref 和 reactive,props 用来在组件之间传值(父组件 -> 子组件)
声明
❗❗ defineProps()宏中的参数不可访问<script setup>中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。
普通
🎇 在单文件组件的<script setup>中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> // 单纯的声明 const props = defineProps(["foo"]); console.log(props.foo);
// 声明时指定对应类型的 构造函数 const props2 = defineProps({ title: { type: String, required: true }, likes: Number, });
props2.title; // string props2.likes; // number | undefined </script>
|
- 如果只在模板(
<template>)中使用,可不用声明变量props,可直接在模板中使用foo
大写String和小写string不是同一个东西;大写是 JavaScript 中的构造函数,小写用在 TypeScript,是字符串类型
🎇 如果没有使用<script setup>
1 2 3 4 5 6 7
| export default { props: ["foo"], setup(props) { console.log(props.foo); }, };
|
🎇 或者在JSX语法中
1 2 3 4 5 6 7 8
| import { defineComponent } from "vue";
export default defineComponent({ props: { msg: { type: String, required: true } }, setup(props: { msg: string }) { return () => <span class={"error-tips"}>{props.msg}</span>; }, });
|
TS 中
🪄 上面使用构造函数声明 props 的叫做运行时声明,接下来用作泛型传递给<>的方式叫做基于类型的声明
就像创建接口对象一样
1 2 3 4 5 6
| <script setup lang="ts"> defineProps<{ title: string; likes?: number; }>(); </script>
|
可以直接提取成interface
1 2 3 4 5 6
| interface Props { title: string; likes?: number; }
definedProps<Props>();
|
🎇 多行书写时;可省略。由于限制,当前条件类型仅可指定单个属性,不能指定整个 props 对象
校验
普通模式中,检验不是必须的,不进行校验的话直接传递字符串数组就行:
1
| defineProps(["name", "age", "isShow"]);
|
普通
type指定props类型
required为true指定为必传 props,false代表默认可选
default指定默认值
使用type指定类型,这些类型时原生 JavaScript 的构造函数:
| 🌸 |
🌺 |
🏵️ |
🪷 |
| String |
Number |
Boolean |
Array |
| Object |
Date |
Function |
Symbol |
``
除此之外,可以通过自定义类或者构造函数来用作 props 的类型,Vue 会通过instanceof来检查:
1 2 3 4 5 6 7 8 9 10
| class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } }
defineProps({ author: Person, });
|
- Vue 校验的时候会使用到
instanceof Person
校验选项参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| defineProps({ propA: Number, propB: [String, Number], propC: { type: String, required: true, }, propD: { type: Number, default: 100, }, propE: { type: Object, default(rawProps) { return { message: "hello" }; }, }, propF: { validator(value) { return ["success", "warning", "danger"].includes(value); }, }, propG: { type: Function, default() { return "Default function"; }, }, });
|
TS
使用到了 TypeScript 就说明是在校验了:
1 2 3 4
| defineProps<{ foo: string; bar?: number; }>();
|
默认值:需要使用withDefaults编译宏
1 2 3 4 5 6 7 8 9
| export interface Props { msg?: string; labels?: string[]; }
const props = withDefaults(defineProps<Props>(), { msg: "hello", labels: () => ["one", "two"], });
|
在非<script setup>的情况下
1 2 3 4 5 6 7 8 9 10 11
| import { defineComponent } from "vue";
export default defineComponent({ props: { message: String, }, setup(props) { props.message; }, });
|
复杂的 props 类型
🤔 刚接触的时候还挺容易混淆
简单的情况:
1 2 3 4 5 6 7 8 9
| <script setup lang="ts"> interface Book { title: string; author: string; year: number; }
const props = defineProps<Book>(); </script>
|
- props 将会有 title,author,year
复杂的情况,实则是再嵌套:
1 2 3 4 5 6 7 8 9 10 11
| <script setup lang="ts"> interface Book { title: string; author: string; year: number; }
const props = defineProps<{ book: Book; }>(); </script>
|
- 现在是 props 只有 book 一个,然后 book 下面才有这三个属性
即普通又 TS
想用一定的 TypeScript 类型检查,但是又不想传递范式(<T>),会这样使用。通常在在Options API中出现
需要导入工具类型PropType
1 2 3 4 5
| import type { PropType } from "vue";
const props = defineProps({ book: Object as PropType<Book>, });
|
使用
🎇 在<script>中使用的时候推荐 camelCase 的命名方式;在模板中使用的时候推荐 kebab-case 的形式
传递 props(父组件向子组件传值):
- 动态绑定值需要在属性前加上
v-bind或者简写:
- 不加上的都认作是传递字符串
父组件:<Page>
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div> <!-- 如果 likes 在这直接传递数字,并且想得到number类型 --> <!-- 可以动态绑定 v-bind="100" --> <MyTitle title="静夜思" :likes="likes" /> </div> </template> <script setup lang="ts"> import { ref } from "vue";
const likes = ref(100); </script>
|
子组件:<MyTitle>
1 2 3 4 5 6 7 8 9 10
| <template> <h1>{{ title }}</h1> <p>{{ likes }}</p> </template> <script setup lang="ts"> defineProps<{ title: string; likes?: number; }>; </script>
|
单向数据流
所有的 props 都遵循单向绑定原则
- 因父组件变化而变化,更新随之传向子组件,所以子组件中的 props 是最新的
- 子组件中不要修改 props。要想获得衍生值
当 props 是一个对象或者数组时,可以改变内部的值,但是不推荐
Boolean 类型转换
方便在模板中使用时更加简洁,避免类似:disabled='true'的出现,使用简单的disabled表示即可
在<MyComponent>中:
1 2 3
| definedProps({ disabled: Boolean, });
|
使用时:
1 2 3 4 5
| <MyComponent disabled />
<MyComponent />
|
当 props 有多种类型的时候,这种转换依然适用。不过与String搭配时有例外。在排列时Boolean需要排在String的前面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| defineProps({ disabled: [Boolean, Number], });
defineProps({ disabled: [Boolean, String], });
defineProps({ disabled: [Number, Boolean], });
defineProps({ disabled: [String, Boolean], });
|
泛型
使用generic属性,属性值和和在 TypeScript 中的<T>一样
1 2 3 4 5 6
| <script setup lang="ts" generic="T"> defineProps<{ items: T[]; selected: T; }>(); </script>
|
同时也可以使用extends进行泛型约束
1 2 3 4 5 6 7
| <script setup lang="ts" generic="T extends string | number, U extends Item"> import type { Item } from "./types"; defineProps<{ id: T; list: U[]; }>(); </script>
|
provide 和 inject
不局限于相邻的父子组件传值,可以更深层次传递(React 中的context)
provide()提供数据
1 2 3 4 5 6 7 8 9 10
| <script setup> import { ref, provide } from "vue";
const msg = ref("hello"); const setMsg = () => { msg.value = "hi"; };
provide(/* 注入名 */ "message", /* 值 */ { msg, setMsg }); </script>
|
- 一个组件可以多次调用,传递不同的注入名
- 如果传入的是一个普通变量,还可以使用
readonly()将它包住
应用层 provide,全局提供数据。(在编写插件的时候会用到)
1 2 3 4 5
| import { createApp } from "vue";
const app = createApp({});
app.provide( "message", "hello!");
|
inject()获取数据
1 2 3 4 5
| <script setup> import { inject } from "vue";
const { msg, setMsg } = inject("message", "这是默认值"); </script>
|
如果上游组件并没有提供message这样一个数据,给inject传递的第二个参数将会是作为当前返回的默认值
默写情况下为避免产生副作用,默认值可以是一个工厂函数,这时候第三个参数需要设置为true
1
| const value = inject("key", () => new ExpensiveClass(), true);
|
目前 TypeScript 标注好像作用不大
状态管理
- 多个组件共享状态;
- 可通过
props 来解决,但是组件嵌套深了后容易混乱
- 可通过
provide和inject
- 不同的视图交互可修改同一状态
- 通过模板获取对应组件实例,然后触发组件内对应的事件
状态管理中最直接的方法,就是将对应的共享状态抽离出来
响应式 API 做状态管理
将共享的状态单独抽离出来
1 2 3 4 5 6 7 8 9
| import { reactive } from "vue";
export const store = reactive({ count: 0, increment() { this.count++; }, });
|
1 2 3 4 5 6
| <!-- ComponentA.vue --> <script setup> import { store } from "./store.js"; </script>
<template>From A: {{ store.count }}</template>
|
1 2 3 4 5 6
| <!-- ComponentB.vue --> <script setup> import { store } from "./store.js"; </script>
<template>From B: {{ store.count }}</template>
|
每当store被改动时,这两个组件都会自动更新自己的视图
不局限于reactive其他的响应式 API 同样适用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { ref } from "vue";
const globalCount = ref(1);
export function useCount() { const localCount = ref(1);
return { globalCount, localCount, }; }
|
其他
- 使用
Pinia
- SSR ???