script setup API
1. 基本语法
2. 响应式
- 响应式数据需要使用响应式 API 创建
<script setup>
// 导入响应式API
import { ref, reactive } from "vue";
// 创建ref对象
const count = ref(0);
// 创建响应式对象
const obj = reactive({
name: "张三",
age: 18,
});
// 获取ref对象的值
console.log(count.value);
// 获取响应式对象的属性值
console.log(obj.name, obj.age);
</script>
3. 组件
-
常规组件
- 直接在
<script setup>
标签内,使用 import 导入组件 - 可以直接在模板中使用
- 组件是通过变量引用而不是基于字符串组件名注册的
- 变量名与标签名一致
- 直接在
<script setup>
// 导入组件
import MyComponent from "./MyComponent.vue";
</script>
<template>
<!-- 使用组件 -->
<MyComponent />
</template>
- 动态组件
- 在
<component/>
标签中,通过使用:is
动态绑定组件 - is 绑定的值需是存储组件的变量(即 import 中定义的接收组件的变量)
- 在
<script setup>
// 导入组件
import Foo from "./Foo.vue";
import Bar from "./Bar.vue";
</script>
<template>
<!-- component标签会显示 is 绑定的组件 -->
<component :is="Foo" />
<!-- is 的值可以是一个计算属性 -->
<component :is="someCondition ? Foo : Bar" />
</template>
4. 自定义指令
- 在
<script setup>
中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令 - 指令钩子 (可选)
序号 指令钩子 作用 1 created 组件创建完成时调用 2 beforeMount 组件挂载前调用 3 mounted 组件挂载完成时调用 4 beforeUpdate 组件更新前调用 5 updated 组件更新完成时调用 6 beforeUnmount 组件卸载前调用 7 unmounted 组件卸载完成时调用
<template>
<button v-btn:arg="'传递给指令的值'">按钮</button>
</template>
<script setup>
// 自定义指令
const vBtn = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
console.log("组件创建完成");
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {
console.log("组件挂载前");
},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {
console.log("组件挂载完成");
},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log("组件更新前");
},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {
console.log("组件更新完成");
},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {
console.log("组件卸载前");
},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {
console.log("组件卸载完成");
},
};
</script>
-
指令钩子的参数(可选)
-
el:指令绑定到的元素。这可以用于直接操作 DOM。
-
binding:一个对象,包含以下属性。
(1) value:传递给指令的值。
(2) oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用
(3) arg:传递给指令的参数 (如果有的话)。
(4) modifiers:一个包含修饰符的对象 (如果有的话)。
(5) instance:使用该指令的组件实例。
(6) dir:指令的定义对象。
(7) 除了 el 外,其他参数都是只读的,不要更改它们。若需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现 -
vnode:代表绑定元素的底层 VNode。
-
prevNode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
-
-
简化形式
直接定义的函数,会默认在mounted
和updated
时都调用<template> <button v-btn="'red'">count++</button> </template> <script lang="ts" setup> // 自定义指令 const vBtn = (el, binding) => { // 这会在 `mounted` 和 `updated` 时都调用 el.style.color = binding.value; }; </script>
-
对象字面量
使用对象,可以让指令接收多个值,指令也可以接收任何合法的 JavaScript 表达式<template> <button v-btn="{ color: 'red', text: '按钮' }">count++</button> </template> <script setup> // 自定义指令 const vBtn = (el, binding) => { // 这会在 `mounted` 和 `updated` 时都调用 el.style.color = binding.value.color; el.innerText = binding.value.text; }; </script>
-
在组件上使用
当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。
当应用到一个多根组件时,指令将会被忽略且抛出一个警告。
指令不能通过 v-bind=“$attrs” 来传递给一个不同的元素。
不推荐在组件上使用自定义指令。 -
动态绑定参数和接收的值
eg:v-btn:[arg,modifiers]='value'
动态绑定参数:参数为一个数组,第一个元素是提供 arg 的变量,第二个元素是提供 modifiers 的变量;
动态绑定接收的值:value 是一个提供 value 的变量
5. defineProps() 接收父组件传递的属性
-
defineProps() 显式声明 接收父组件传递的参数
- 使用字符串数组来声明 prop
<!-- 父组件文件 --> <template> <Children :num="num" :str="str" :bool="bool" :arr="arr" :obj="obj"></Children> </template> <script lang="ts" setup> // 导入子组件 import Children from "../children.vue"; import { ref, reactive } from "vue"; const num = ref(0); const str = ref("标题"); const bool = ref(true); const arr = reactive([1, 2, 3]); const obj = reactive({ name: "张三", age: 18, }); </script> <!-- 子组件文件 --> <template> <div>子组件</div> </template> <script lang="ts" setup> const props = defineProps(["num", "str", "bool", "arr", "obj"]); console.log(props.num); console.log(props.str); console.log(props.bool); console.log(props.arr); console.log(props.obj); </script>
- 使用对象形式来声明 prop
key 是 prop 的名称,值是该 prop 预期类型的构造函数
<!-- 父组件文件 --> <template> <Children :num="num" :str="str" :bool="bool" :arr="arr" :obj="obj"></Children> </template> <script lang="ts" setup> // 导入子组件 import Children from "../children.vue"; import { ref, reactive } from "vue"; const num = ref(0); const str = ref("标题"); const bool = ref(true); const arr = reactive([1, 2, 3]); const obj = reactive({ name: "张三", age: 18, }); </script> <!-- 子组件文件 --> <template> <div>子组件</div> </template> <script lang="ts" setup> const props = defineProps({ num: Number, str: String, bool: Boolean, arr: Array, obj: Object, }); console.log(props.num); console.log(props.str); console.log(props.bool); console.log(props.arr); console.log(props.obj); </script>
- Prop 校验
要声明对 props 的校验,你可以向 defineProps() 宏提供一个带有 props 校验选项的对象
<script lang="ts" setup> const props = 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"; }, }, }); </script>
- 单向数据流
(1) 所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
(2) 避免子组件意外修改父组件的状态的情况,使应用的数据流容易变得混乱而难以理解。
(3) 不推荐在子组件中去更改一个 prop。若这么做了,Vue 会在控制台上向你抛出警告
6. defineEmits() 接收父组件传递的自定义事件
-
defineEmits() 显式声明 自定义事件
<script setup>
使用自定义事件:
(1) 创建父组件监听自定义事件时要触发的方法 changMyMsg(val)
该方法有一个参数 value,是子组件传给父组件的数据function changMyMsg(val) { msg.value += val; }
(2) 在父组件中设置一个自定义事件 changMsg ,并绑定上述步骤定义的方法 changMyMsg()
<Children @changMsg="changMyMsg"></Children>
(3) 在子组件中声明要接收的 父组件传递过来的自定义事件
const emit = defineEmits(["changMsg"]);
(4) 在子组件中定义一个方法,用于子组件监听事件触发。该方法中调用 emit() 函数
emit() 函数有两个参数:
参数一:父组件传递过来的自定义事件的名称
参数二:子组件要传给父组件的数据function changFatherMsg(num) { emit("changMsg", num); }
(5) 在子组件上绑定一个监听事件,并给该监听事件绑定步骤(4)中定义的方法 changFatherMsg()
<button @click="changFatherMsg(2)">按一下 +2</button>
通过 emit 修改父组件参数的完整实例
<!-- 父组件 --> <template> <Children :msg="msg" @changMsg="changMyMsg"></Children> </template> <script setup> import Children from "./children.vue"; import { ref } from "vue"; const msg = ref(0); function changMyMsg(val) { // val:子组件传递给父组件的数据 msg.value += val; } </script> <!-- 子组件 --> <template> <p>msg:{{ msg }}</p> <button @click="changFatherMsg(2)">按一下 msg+2</button> </template> <script setup> // 声明接收父组件传递给子组件的 porps const props = defineProps(["msg"]); // 声明接收父组件传递给子组件的 自定义事件 const emit = defineEmits(["changMsg"]); function changFatherMsg(num) { // 参数一:自定义事件名 // 参数二:要传递给父组件的数据 emit("changMsg", num); } </script> <!-- 子组件修改父组件参数的流程: (1) 点击子组件中的按钮 => 触发子组件中的 changFatherMsg 函数 (2) changFatherMsg 函数触发内部的 emit 函数 (3) emit 函数触发父组件的 changMyMsg 函数,并将子组件的数据传递给 changMyMsg 函数 (4) changMyMsg 函数触发,修改父组件的数据 -->
7. defineExpose() 对外暴露子组件的属性
- 在
<script setup>
模式下,父组件无法直接通过 ref 直接获取子组件的属性 - 需要子组件通过 defineExpose 编译器宏来显式指定在
<script setup>
组件中要暴露出去的属性 - 父组件才能在组件挂载完成后,通过
ref对象.vlaue
获取子组件暴露出来的属性
<!-- 父组件 -->
<template>
<Children ref="childrenDOM"></Children>
</template>
<script setup>
import Children from "./children.vue";
import { onMounted, ref } from "vue";
const childrenDOM = ref(null);
// 只有在子组件挂载完成后,才能获取 ref对象
onMounted(() => {
// 获取子组件暴露的属性
console.log(childrenDOM.value);
});
</script>
<!-- 子组件 -->
<template>
<button>按钮</button>
</template>
<script setup>
const num = 0;
const str = "字符串";
// 子组件要暴露出去的属性
defineExpose({
num,
str,
});
</script>
8. defineOptions() 声明组件选项
- 用来直接在
<script setup>
defineOptions({
// 设置组件名称
name:'children',
// 属性继承:不允许
inheritAttrs: false,
})
</script>