当前位置: 首页 > news >正文

Vue 系列之:defineProps、defineEmits、...

defineProps

用于接收父组件传递的属性值。

父组件:

<!-- 父组件 -->
<template><Child1 str="字符串" :num="num" />-----------------<Child2 str="字符串" :num="num" />
</template><script setup>
import { ref } from 'vue';
import Child1 from './views/Child1.vue';
import Child2 from './views/Child2.vue';const num = ref(18)setInterval(() => {num.value++
}, 1000);
</script>

子组件1:

<!-- Child1 -->
<template><div><p>{{ str }}</p><!-- 两种写法都可以 --><p>{{ props.str }}</p><p>{{ num }}</p><p>{{ obj }}</p><p>{{ fun }}</p><!-- 会报错 --><!-- v-model cannot be used on a prop, because local prop bindings are not writable. --><!-- <input v-model="str" /> --><!-- 在 input 框中输入值并不会引起 str 的变化 --><input v-model="newStr" /></div>
</template><script setup>
import { ref } from 'vue';const props = defineProps({str: String,/*** 通过 defineProps 定义的 props 是响应式的。* 这意味着当父组件传递给子组件的 prop 值发生变化时,* 子组件中的相应 prop 也会自动更新,* 并且任何依赖于这些 props 的计算属性、侦听器(watchers)或模板都会更新。*/num: {type: Number,default: 0},obj: {type: Object,default: () => {return {}}},fun: {type: Function,default: null}
});console.log("child1 created props:", props, typeof props); // Proxy 对象
console.log("child1 created str:", props.str, typeof props.str); // 字符串
console.log("child1 created num:", props.num);
console.log("child1 created obj:", props.obj, typeof props.obj); // object
console.log("child1 created fun:", props.fun); // objectconst newStr = ref(props.str)
console.log("child2 newStr:", newStr.value)
</script>

子组件2:

<!-- Child2 -->
<template><div><p>{{ str }}</p><!-- 这里就不能这样写了,会报错 --><!-- <p>{{ props.str }}</p> --><p>{{ num }}</p><p>{{ obj }}</p><p>{{ fun }}</p></div>
</template><script setup>
import { ref, computed } from 'vue';const { str, num, obj, fun } = defineProps({str: String,num: {type: Number,default: 0},obj: {type: Object,default: () => {return {}}},fun: {type: Function,default: null}
});console.log("child2 created str:", str);
console.log("child2 created num:", num);
console.log("child2 created obj:", obj);
console.log("child2 created fun:", fun);/*** 在 Vue 3 中,通过 defineProps 定义的 props 是只读的,不能直接修改。* 这是为了确保数据流保持单向,即父组件传递给子组件的数据不会被子组件意外地改变。* 非要修改只能使用计算属性或者创建一个响应式变量*/
// str = '改变字符串' // 会报错const changeStr1 = computed(() => {return `使用计算属性改变${str}`
})
console.log("child2 changeStr1:", changeStr1.value);const changeStr2 = ref(str)
changeStr2.value = `使用响应式变量改变${str}`
console.log("child2 changeStr2:", changeStr2.value);
</script>

defineProps() 返回的是一个 Proxy 对象,它既不是 ref 对象,也不是 reactive 对象。

defineProps() 返回的对象的属性值是普通数据类型或普通对象,也不是 ref/reactive 对象。

defineEmits

用在子组件中。表面上看它的作用似乎是用于子组件调用父组件的方法。

更准确的说法是用来定义子组件可以发出的事件,父组件可以通过监听这些事件来响应子组件的行为。

它实际上是在告诉父组件:当‘我’使用 emit 的时候,你父组件需要做出相应的响应,你想怎么响应是你父组件自己的事情。

<!-- 子组件 -->
<template><button @click="handleClick">子组件按钮</button>
</template><script setup>
// 定义可以发出的事件
const emit = defineEmits(['update'])function handleClick() {// 发出 'update' 事件给父组件emit('update', '我是参数')
}
</script>

<!-- 父组件 -->
<template><!-- 监听子组件的 'update' 事件 --><Child1 @update="handleUpdate" />
</template><script setup>
import Child1 from './views/Child1.vue';function handleUpdate(params) {// 父组件做出响应:打印参数值console.log("params:", params);
}
</script>

defineExpose

用在子组件中,用于暴露子组件实例的方法和数据。它可以让父组件通过 ref 获取子组件的特定方法或数据。

<!-- 子组件 -->
<template>子组件
</template><script setup>
import { ref } from 'vue';const str = ref('子组件字符串')
const fun = function () {console.log("子组件方法触发...");
}
const other = '其他'defineExpose({str, // ES6 简化写法num: 18,fun: fun
})
</script>

<!-- 父组件 -->
<template><Child1 ref="child1" /><button @click="handleClick">父组件按钮</button>
</template><script setup>
import { onMounted, ref } from 'vue';
import Child1 from './views/Child1.vue';const child1 = ref(null)console.log("created str:", child1.value.str); // 第 15 行,报错onMounted(() => {console.log("mounted str:", child1.value.str); // 子组件字符串
})function handleClick() {console.log("str:", child1.value.str); // 子组件字符串console.log("num:", child1.value.num); // 18child1.value.fun() // 子组件方法触发...console.log("other:", child1.value.other); // undefined
}
</script>

为什么第 15 行会报错?

因为在 setup 函数执行时,子组件还没有被挂载到 DOM 上,组件的实例也没有准备好。因此,此时 child1.valuenull

为什么 child1.value.other 是 undefined?

因为子组件中没有通过 defineExpose 来暴露 other

defineAsyncComponent

异步组件。可以理解为延迟加载或按需加载。比如当一个大型页面中包含很多个子组件,如果一次性加载所有内容势必会导致页面渲染速度过慢,这时候就可以对那些不需要第一时间就加载的、或者需要经过某些操作后才加载的子组件使用异步组件。

同步组件写法:

<!-- App.vue -->
<template><button @click="handleClick">加载子组件</button><SyncComponent v-if="show" />
</template><script setup>
import { ref } from 'vue';
import SyncComponent from "./views/Child1.vue";const show = ref(false)function handleClick() {show.value = true
}
</script>

异步组件写法:

<!-- App.vue -->
<template><button @click="handleClick">加载子组件</button><AsyncComponent v-if="show" />
</template><script setup>
import { defineAsyncComponent, ref } from 'vue';const AsyncComponent = defineAsyncComponent(() => import('./views/Child1.vue'))
const show = ref(false)function handleClick() {show.value = true
}</script>

检查控制台发现:无论同步组件还是异步组件,页面元素都没有渲染子组件内容。

在这里插入图片描述

那么他们有什么区别?

区别是你可以在控制台的 Network 中发现:

  • 同步组件会在页面初始化时一次性加载 App.vue 和 Child1.vue

  • 异步组件在页面初始化时只加 App.vue,当点击按钮时再加载 Child1.vue。


知道了 defineAsyncComponent 的作用,下面详细介绍 defineAsyncComponent 的用法:

defineAsyncComponent 方法接收一个返回 Promise 的加载函数:

import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() => {return new Promise((resolve, reject) => {// ...从服务器获取组件resolve(/* 获取到的组件 */)})
})

ES6 模块动态导入也会返回一个 Promise,所以也可以这样写:

import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>import('./components/MyComponent.vue')
)

异步组件会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。


异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent 也支持在高级选项中处理这些状态:

import LoadingComponent from './views/Loading.vue';
import ErrorComponent from './views/Error.vue';const AsyncComp = defineAsyncComponent({// 加载函数loader: () => import('./views/Child1.vue'),// 加载异步组件时使用的组件loadingComponent: LoadingComponent,// 展示加载组件前的延迟时间,默认为 200msdelay: 2000,// 加载失败后展示的组件errorComponent: ErrorComponent,// 如果提供了一个 timeout 时间限制,并超时了,// 也会显示加载失败后展示的组件,默认值是:Infinitytimeout: 3000
})

注意:delay: 2000 并不是指等待 2s 后才开始加载异步组件,而是指在异步组件开始加载后,等待 2s 再显示 loadingComponent。

当使用服务器端渲染时还可以配置:在空闲时进行激活、在可见时激活、自定义策略等等…这里不做拓展,详情可以直接访问官网。


Suspense

[səˈspens]

Suspense 是一个包裹异步组件的容器组件,用来处理异步组件加载期间的 UI 状态。

Suspense 组件有两个插槽:

  • #default:默认插槽,这个插槽用于放置异步组件。当异步组件加载完成后,#default 插槽中的内容将被渲染。

  • #fallback:备用插槽,当异步组件正在加载时,#fallback 插槽中的内容会被渲染。

父组件:

<!-- 父组件 -->
<template><div>我是父组件内容</div><Suspense><AsyncComponent /><template #fallback><!-- <h1>正在加载中...</h1> --><LoadingComponent /></template></Suspense>
</template><script setup>
import LoadingComponent from "./views/LoadingComponent.vue";
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(function () {return new Promise(resolve => {setTimeout(() => {resolve(import('./views/AsyncComponent.vue'))}, 5000);});
})
</script>

异步子组件:

<!-- 异步子组件 -->
<template><div>我是异步子组件</div>
</template><script setup>
import { onMounted } from 'vue';
onMounted(() => {console.log("子组件 onMounted 执行");
})
</script>

备用组件 LoadingComponent:

<!-- 备用组件 -->
<template><div>加载中...</div>
</template>

在这里插入图片描述

5 秒后页面内容替换:

在这里插入图片描述


注意点:#default 和 #fallback 两个插槽都只允许一个直接子节点。

<template #fallback> Loading... </template>
<template #fallback> <LoadingComponent /> 
</template>
<template #fallback> <h1>正在加载中...</h1>
</template>

都是可以的,但是

<template #fallback>哈哈<h1>正在加载中...</h1>
</template>

就会报错。因为它有两个直接子节点。

Suspense 组件会触发三个事件:pendingfallbackresolve

  • pending 事件是在进入挂起状态时触发。

  • fallback 事件是在 #fallback 插槽的内容显示时触发。

  • resolve 事件是在 #default 插槽完成获取新内容时触发。

<template><div>我是父组件内容</div><Suspense @pending="handlePending" @fallback="handleFallback" @resolve="handleResolve"><AsyncComponent /><template #fallback><h1>正在加载中...</h1></template></Suspense>
</template><script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(function () {return new Promise(resolve => {setTimeout(() => {resolve(import('./views/AsyncComponent.vue'))}, 5000);});
})let num = 1setInterval(() => {console.log(num++);
}, 1000)function handlePending() {console.log("pending...");
}
function handleFallback() {console.log("fallback...");
}
function handleResolve() {console.log("resolve...");
}
</script>

在这里插入图片描述


defineAsyncComponent 的实际作用:

手摸手教你利用defineAsyncComponent实现长页面按需加载组件

路由懒加载:

const routes = [{path: '/dashboard',component: defineAsyncComponent(() => import('./views/Dashboard.vue'))},{path: '/profile',component: defineAsyncComponent(() => import('./views/Profile.vue'))}
];

通过 Vue Router 的懒加载机制,只有在用户访问特定路由时,相关页面组件才会被加载。

拓展:CommonJS 的 require() 也可以实现路由懒加载。

defineOptions

在 Vue 3.3 及之后的版本中,defineOptions 是一个新引入的宏(macro),它允许开发者在 <script setup> 语法糖中声明组件的选项(options)。

这个特性解决了之前需要额外编写一个非 setup 的<script>标签来配置选项的问题。

// 设置组件名并禁止属性继承
defineOptions({name: 'MyComponent', // 组件名称inheritAttrs: false        // 禁止属性继承
});

注意:

可以在<script setup>之外使用<script>标签来配置选项,但是<script setup>和普通<script>中的 setup() 函数不能同时用来定义响应式数据。

例如:

<template><div>{{ name }}{{ age }}</div>
</template><script setup>
import { ref } from 'vue';
const name = ref('张三');
</script><script>
import { ref } from 'vue';
export default {setup() {const age = ref(10);return { age };}
}
</script>

同时使用了<script setup>和 setup(),则页面不会按预期展示。

<template><div>{{ name }}{{ age }}</div>
</template><script setup>
import { ref } from 'vue';
const name = ref('张三');
const age = ref(10);
</script><script>
export default {// 这里可以配置选项式 API 的内容,比如 props、emits、components 等props: {// 示例 props 配置someProp: String},emits: ['someEvent'],components: {// 示例组件配置// SomeComponent}
}
</script>

在这个示例里,<script setup>负责定义响应式数据和逻辑,普通<script>负责配置选项式 API 的内容。这样就能把两种语法风格结合起来使用。

defineComponent

从 API 名称来看,意思是定义一个组件,是 Vue 3 中引入的一个辅助函数,主要用于 TypeScript 项目中。它允许你在定义组件选项时获得更好的类型推断和 IDE(如 VSCode)中的自动补全功能。通过使用 defineComponent,IDE 能够识别这是一个 Vue 组件,并据此提供 Vue 特有的 API 提示和类型检查。

什么意思呢?

在 Vue2 中,我们会习惯这样写:

export default {//...
}

这个时候,对于开发工具而言,{} 只是一个普通的 Object 对象,开发工具不会对一个普通对象做任何特殊的处理。

但是增加一层 defineComponet 的话:

export default defineComponent({//...
})

你实际上就是在告诉开发工具,我使用的是 Vue3,你需要给我一些 Vue3 相关的自动提示。这样在你写代码的时候,开发工具会给出更多的一些自动提示帮你补全代码。

核心源码:

var Vue = (function (exports) {// 定义组件function defineComponent(options) {return isFunction(options) ? { setup: options, name: options.name } : options;}exports.defineComponent = defineComponent;  
}({}));

参数 options 是一个选项对象或 setup 函数。

什么是选项对象?

Vue2 中写的:

export default {data() {// ...},methods: {// ...}
}

这些就是选项对象。

Vue3中defineComponent 的作用详解

Vue 中的 defineComponent

Vue3源码解析-defineComponent

defineSlots

与 defineComponent 类似,辅助功能,用于类型检查和 IDE 的自动补全,主要用于 TypeScript 环境下。

defineCustomElement

defineCustomElement 作用是定义一个自定义元素。在 Vue 中我们可以自己写 .vue 文件封装组件,那么它存在的意义是什么呢?

在 Vue 3 中,defineCustomElement 是一种特殊的 API,它允许你将 Vue 组件转换为 Web Components,这样它们就能在任何现代浏览器中作为原生的自定义 HTML 元素使用,而不仅仅是在 Vue 应用中使用。与常规的 Vue 组件不同,使用 defineCustomElement 创建的组件可以独立于 Vue 环境运行,也可以在非 Vue 项目中使用。

这个方法接收的参数和 defineComponent 完全相同。但它会返回一个继承自 HTMLElement 的自定义元素构造器:

import { defineCustomElement } from 'vue'const MyVueElement = defineCustomElement({// 这里是同平常一样的 Vue 组件选项props: {},emits: {},template: `...`,// defineCustomElement 特有的:注入进 shadow root 的 CSSstyles: [`/* inlined css */`]
})// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签都会被升级
customElements.define('my-vue-element', MyVueElement)

有兴趣的可以看看 Web Component 的相关知识 【zh-CN】。可以将 Web Components 简单理解为一个自定义的 HTML 标签。

用的很少,就不做具体研究。

Vue 与 Web Components

Vue3中defineCustomElement的使用

defineModel

仅在 3.4+ 中可用

v-model

Vue 系列之:自定义双向数据绑定

defineModel 就是简化 v-model 实现过程

下面使用 defineModel 来实现双向数据绑定的例子:

父组件:

<!-- 父组件 -->
<template><div><p>Count in parent: {{ count }}</p><Children v-model="count" /></div>
</template><script setup>
import { ref, watch } from 'vue'
import Children from './Children.vue';const count = ref(1)watch(count, (newVal, oldVal) => {console.log(newVal, oldVal, typeof newVal) // number 类型
})  
</script>

子组件:

<!-- 原子组件代码 -->
<template><div><!-- 这里不能直接 v-model="modelValue" 会报编译错误v-model cannot be used on a prop, because local prop bindings are not writable.--><el-select v-model="selectValue" @change="handleChange"><el-option label="选项1" :value="1" /><el-option label="选项2" :value="2" /><el-option label="选项3" :value="3" /></el-select></div>
</template><script setup>
import { ref, defineProps, defineEmits } from 'vue'const props = defineProps({modelValue: Number
})const emit = defineEmits(['update:modelValue'])const selectValue = ref(props.modelValue)function handleChange() {console.log('选项变化了');emit('update:modelValue', selectValue.value);
}
</script>
<!-- 使用 defineModel 的子组件代码 -->
<template><div><el-select v-model="selectValue" @change="handleChange"><el-option label="选项1" :value="1" /><el-option label="选项2" :value="2" /><el-option label="选项3" :value="3" /></el-select></div>
</template><script setup>
import { ref, defineProps, defineEmits } from 'vue'// const props = defineProps({
//     modelValue: Number
// })// const emit = defineEmits(['update:modelValue'])// const selectValue = ref(props.modelValue)// function handleChange() {
//     console.log('选项变化了');
//     emit('update:modelValue', selectValue.value);
// }const selectValue = defineModel()function handleChange() {console.log('选项变化了:', selectValue.value);// emit('update:modelValue', selectValue.value);
}
</script>

非常简单!

defineModel 就是封装了之前的实现过程:在子组件内定义了一个叫 selectValueref 变量(当然也可以取别的变量名)和名字叫 modelValue 的 props,并且 watch 了 props 中的 modelValue。当父组件改变 modelValue 的值后会同步更新 selectValue 变量的值;当子组件改变 selectValue 变量的值后会调用 update:modelValue 事件,父组件收到这个事件后就会更新父组件中对应的变量值。

defineModel 中的 type 和 default

默认情况就使用:

const model = defineModel();

如果想定义类型:

const model = defineModel({ type: String })

类型 + 默认值:

const model = defineModel({ type: String, default: "张三" });

自定义属性名:

<!-- 父组件 -->
<Children v-model:aa="count"></Children>
// 子组件
const model = defineModel('aa', { type: String, default: "张三" })

绑定多个属性:

<!-- 父组件 -->
<Children v-model:name="myName" v-model:age="myAge"></Children>
// 子组件
const model1 = defineModel('name')
const model2 = defineModel('age', { type: Number, default: 8 })

setup() 和 <script setup> 的区别

编译

在编译时 setup() 难以进行深度静态分析, 因为它的返回值是动态的(比如返回的对象可能包含运行时才能确定的属性或方法)。

<script setup>是编译时语法糖,它的顶层绑定(变量、函数、import 等)是直接暴露给模板的,编译器可以明确知道哪些内容会被模板使用,从而进行更多优化,例如更好的 Tree-shaking:未在模板中使用的代码可以被标记并移除。

总结:

Vue 编译器可以对<script setup>语法糖内部的代码进行静态分析,从而进行更多的编译时优化,减少运行时的开销,提高组件的渲染性能。因此在性能优化上 setup() 不如<script setup>

上下文

setup() 函数通过参数 props 和 context 来访问组件的属性和上下文。

  • props 就是 Vue2 中组件中的 props,指父组件传递来的参数

  • context 有三个属性 attrs slots emit 分别对应 Vue2 中的 attrs 属性、slots 插槽、$emit 事件

子组件接收父组件传递的值:

setup():

<script>
export default {props: {num: {type: Number,default: 1}},setup (props) {console.log(props)}
}
</script>

<script setup>:

<script setup>
import { defineProps } from 'vue'
const props = defineProps({num: {type: Number,default: 1}
})
</script>

子组件给父组件传值:

setup():

<script>
export default {setup (props, context) {const sendNum = () => {context.emit('submit', 1200)}return { sendNum }}
}
</script>

<script setup>:

<script setup>
import { defineProps, defineEmits } from 'vue'
const emit = defineEmits(['submit'])
const sendNum = () => {emit('submit', 1000)
}
</script>

<script setup>使用 defineProps 和 defineEmits 宏来访问组件的属性和触发自定义事件,不需要手动接收 props 和 context。

return

setup() 函数是一个标准的组件选项(Component Option),由 Vue 运行时直接解析。需显式返回对象,其属性暴露给模板。

即:setup() 中的内容需要显式地 return 才能在模板中访问(属性和方法都需要 return):

<template><div><p>{{ message }}</p><button @click="changeMessage">测试</button></div>
</template><script>
import { ref } from 'vue';
export default {setup() {const message = ref('Hello, Vue 3!');const changeMessage = () => {message.value = 'Message changed!';};return { message, changeMessage };}
};
</script>

例如上面这段代码,如果没有 return,则功能无法实现。

如果使用 <script setup> 则不需要 return:

<template><div><p>{{ message }}</p><button @click="changeMessage">测试</button></div>
</template><script setup>
import { ref } from 'vue';
const message = ref('Hello, Vue 3!');
const changeMessage = () => {message.value = 'Message changed!';
};
</script>

这是因为:

编译器会对 <script setup>块进行静态分析和转换,生成等效的 setup() 函数。编译后的代码会提取顶层变量(包括 import 的组件),形成 setup() 的返回对象。

即:<script setup> 是 setup() 的语法糖,在 <script setup> 中定义的变量和方法会自动暴露给模板,无需手动 return。

expose

父组件:

<template><div><Children1 ref="child1" /><Children2 ref="child2" /><button @click="handleClick">按钮</button></div>
</template><script setup>
import { ref } from 'vue';
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
const child1 = ref(null);
const child2 = ref(null);
const handleClick = () => {console.log("child1:", child1);console.log("child1 message:", child1.value.message);console.log("child1 obj:", child1.value.obj);console.log("child1 fn:", child1.value.fn);console.log("----------");console.log("child2:", child2);console.log("child2 message:", child2.value.message);console.log("child2 obj:", child2.value.obj);console.log("child2 fn:", child2.value.fn);
}
</script>

setup() 子组件:

<template><div>setup() 子组件</div>
</template><script>
import { reactive, ref } from 'vue';
export default {setup() {const message = ref('Hello, Vue 3!');const obj = reactive({ name: '张三', age: 10 })const fn = () => {console.log("子组件方法");}return { message, obj }}
}
</script>

<script setup> 子组件:

<template><div><script setup> 子组件</div>
</template><script setup>
import { reactive, ref } from 'vue';
const message = ref('Hello, Vue 3!');
const obj = reactive({ name: '张三', age: 10 })
const fn = () => {console.log("子组件方法");
}
</script>

在这里插入图片描述

可以发现:setup() 会向父组件暴露所有 return 的属性和方法,而<script setup>就不会,<script setup>语法糖只会对外暴露手动 defineExpose 的内容。

其他

其他的一些使用细节上的区别:

注册组件:

setup():需要手动注册

<script>
import Hello from '@/components/HelloWorld'
export default {components: {Hello}
}
</script>

<script setup>:不需要手动注册

<script setup>
import Hello from '@/components/HelloWorld'
</script>

自定义指令:

setup():

<template><h1 v-onceClick>使用了setup函数</h1>
</template>
<script>export default {directives: {onceClick: {mounted (el, binding, vnode) {console.log(el)}}},
}
</script>

<script setup>:

不需要显式注册,但他们必须遵循 vNameOfDirective 这样的命名规范。

<template><h1 v-once-Directive>使用了script setup</h1>
</template>
<script setup>
const vOnceDirective = {beforeMount: (el) => {console.log(el)}
}
</script>

setup 函数特点

4、setup 函数在 beforeCreate 钩子函数之前执行

export default {setup() {console.log("setup");},beforeCreate() {console.log("beforeCreate");},created() {console.log("created");},mounted() {console.log("mounted");}
}// setup
// beforeCreate
// created
// mounted

setup(props, context) 详细说明

执行顺序

setup() 是组件中使用组合式 API 的入口点,它在组件实例创建之前执行,在 beforeCreate 和 created 生命周期钩子之前调用。

注:有说法认为 Vue3 中没有 beforeCreate 和 created 钩子函数,这是不准确的。

  • 组合式 API 中确实没有 beforeCreate 和 created 钩子函数,他们的功能被 setup 取代;

  • 但是在选项式 API 中他们依然存在。

<template>
</template><script>
export default {beforeCreate() {console.log("beforeCreate");},created() {console.log("created");},setup() {console.log("setup");}
}
</script>

在这里插入图片描述
执行顺序:setup——beforeCreate——created

props 参数

特性:

  1. 是响应式的,包含组件接收的所有 prop,当父组件更新 props 时会自动更新

  2. 不能使用 ES6 解构,否则会失去响应性

  3. 如果需要解构,可以使用 toRefs 或 toRef 保持响应性

特性 1 举例:

<!--父组件-->
<template><div><Children name="张三" :age="10" /></div>
</template><script>
import Children from './Children.vue';
export default {components: { Children }
}
</script>
<!--子组件-->
<template><div><p>{{ name }}</p><p>{{ age }}</p></div>
</template><script>
export default {props: {name: String,// age: Number 没有接收 age 属性},setup(props) {console.log("props:", props);console.log("name:", props.name);console.log("age:", props.age);}
}
</script>

在这里插入图片描述
组件没有接收 age 属性,所以 props 参数中的 age 为 undefined。


特性 2、3 举例:

<!--父组件-->
<template><div><Children :name="name" :age="age" @change="handleChange" /></div>
</template><script>
import { ref } from 'vue';
import Children from './Children.vue';
export default {components: { Children },setup() {const name = ref('张三');const age = ref(10);const handleChange = () => {age.value++}return { name, age, handleChange };}
}
</script>
<!--子组件-->
<template><div style="margin-left: 50px;"><p>{{ props.name }}</p><p>{{ props.age }}</p><p>{{ age }}</p><button @click="handleClick">按钮</button></div>
</template><script>
import { toRefs } from 'vue';export default {props: {name: String,age: Number},setup(props, context) {const { age } = propsconst age1 = props.ageconst { age: age2 } = toRefs(props)const handleClick = () => {context.emit('change');setTimeout(() => {console.log("props.age:", props.age);console.log("age:", age);console.log("age1:", age1);console.log("age2.value:", age2.value);console.log("age2:", age2);})};return { props, age, handleClick }}
}
</script>

初始页面:
在这里插入图片描述
点击一次按钮后:
在这里插入图片描述
在这里插入图片描述
可以看到:

const { age } = propsconst age1 = props.age 丢失了响应性,

props.ageconst { age: age2 } = toRefs(props) 保留了响应性。

拓展:

使用 toRefs(props) 创建的 age2 是一个 ref 对象,它包含了多个内部属性:

属性类型说明
__v_isRefboolean标识这是一个 ref 对象,值为 true
_defaultValueany默认值
_keystring对应的 props 键名,这里是 “age”
_objectobject指向原始的 props 响应式对象
_valueany当前存储的 age 的值(与 value 相同)
depSet<ReactiveEffect>存储依赖该 ref 的副作用(effect)
valueany访问或修改

context 参数

context 是一个普通对象(非响应式),包含组件的三个属性:

  • attrs

    • 包含所有未在 props 中声明的 attribute

    • 相当于 Vue 2 中的 this.$attrs

    • 非响应式

    • 示例:

      setup(props, { attrs }) {console.log(attrs.class) // 访问 class 属性
      }
      
  • slots

    • 包含所有插槽内容的对象

    • 相当于 Vue 2 中的 this.$slots

    • 非响应式

    • 示例:

      setup(props, { slots }) {const defaultSlot = slots.default() // 获取默认插槽内容return () => h('div', defaultSlot)
      }
      
  • emit

    • 用于触发自定义事件的函数

    • 相当于 Vue 2 中的 this.$emit

    • 示例:

      setup(props, { emit }) {const handleClick = () => {emit('change', 'new value')}return {handleClick}
      }
      

当然也可以不使用解构写法:

setup(props, context) {console.log(context.attrs.class)const defaultSlot = context.slots.default()const handleClick = () => {context.emit('change', 'new value')}
}

返回值

setup() 可以返回一个对象,该对象的属性/函数将被暴露给模板使用,也可以返回一个渲染函数:

返回对象:

import { ref } from 'vue'setup() {const count = ref(0)return {count,increment: () => count.value++}
}

返回渲染函数:

import { h, ref } from 'vue'setup() {const count = ref(0)return () => h('div', count.value)
}
http://www.xdnf.cn/news/357985.html

相关文章:

  • vue3: pdf.js 2.16.105 using typescript
  • 字符函数和字符串函数
  • MKS RGA 校准调试MKS eVision和Vision 1000p RGA步骤(图文并茂)
  • 使用 Spring 和 Redis 创建处理敏感数据的服务
  • 4.2【LLaMA-Factory实战】金融财报分析系统:从数据到部署的全流程实践
  • 20250509 哲学上的真空和哲学上的虚无是一个概念吗
  • 量子计算在软件开发中的兴起
  • Baklib智能内容推荐中台是什么?
  • canvas坐标系转webgl坐标系
  • 数字化转型-4A架构之数据架构
  • selenium替代----playwright
  • XML Forms Data Format (XFDF) 工作原理、数据结构、使用场景以及与缓冲区的交互方式
  • 【身份证识别表格】批量识别身份证扫描件或照片保存为Excel表格,怎么大批量将身份证图片转为excel表格?基于WPF和腾讯OCR的识别方案
  • 从 JMS 到 ActiveMQ:API 设计与扩展机制分析(一)
  • 37-智慧医疗服务平台(在线接诊/问诊)
  • Windows系统下【Celery任务队列】python使用celery 详解(二)
  • Jsoup与HtmlUnit:两大Java爬虫工具对比解析
  • PostgreSQL逻辑复制(logic replication)
  • 《内存单位:解锁数字世界的“度量衡”》
  • TIME - MoE 模型代码 3.2——Time-MoE-main/time_moe/datasets/time_moe_dataset.py
  • android 媒体框架
  • Android Handler 机制面试总结
  • 力扣刷题 每日四道
  • pandas中的数据聚合函数:`pivot_table` 和 `groupby`有啥不同?
  • 【项目中的流程管理(十一)】
  • MongoDB 创建索引原则
  • 设计模式-策略模式(Strategy Pattern)
  • 前端指南——项目代码结构解析(React为例)
  • 系统文件夹迁移与恢复
  • 系分论文《论多云架构治理的分析和应用》