Vue3 中的 setup 是一个新的配置项,值是一个函数。
export default {
name: 'App',
setup: function () {
}
}
</script>
和 Vue2 中的 data 一样,我也可以将 setup 简写成为
export default {
name: 'App',
setup() {
}
}
setup函数的使用
与 Vue2 不一样的是,setup 中包含了 data 数据,methods方法,computed计算属性,watch监听器等等一系列的属性,也就是说,在 Vue3 中,我们不会显式的把这些属性配置到 setup 中,而是直接将属性内部的数据或方法直接暴露在 setup 中
ps:本章所讲内容,不涉及响应式,也就是说 在 setup 中定义的数据,不是响应式的,如何转为响应式数据,下一章节 ref 会讲到。但是 props 传递的数据是响应式的。
setup() {
// 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量
let name = 'al'
let age = 18
// 类似于methods属性中的方法
function hello() {
// 因为这是直接在 setup 函数中访问变量,直接访问就行,不用 this
alert(`我的名字是${name},我的年龄是${age}`)
}
}
setup 的参数
setup 函数接收两个参数,分别是 props 和 上下文 context
props 是响应式的数据,且在传入新的 props 时,会同步更新现有的 props。例如
export default {
// 如果这里接收到的 props 改变了
props: {
title: String
},
// 那么这里的接使用的props也会同步改变
setup(props) {
console.log(props.title)
}
}
同时需要注意的是,在使用props传递的数据时时,尽量采取 props.xxx的方式进行。
对于传递的 props尽量不要解构,因为解构得出的变量将会丢失响应式。如果确实需要解构,或者将 props中的某一项数据传递的同时保持响应式,可以采用 torefs 和 toref 来进行转化。
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
第二个参数则是上下文 context ,其中暴露了一些在setup 中可能会用到的值,例如:
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
这个 context 不是响应式的,可以直接解构,
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
其中 attrs 和 slots 都是有状态的对象,会随着组件自身更新而更新,但是二者都不是响应式数据,所以在使用时,同样避免解构,而是尽量通过 attrs.x 或 slots.x 方式。如果想要基于 attrs 或 slots的改变来执行副作用,那么可以在 onBeforeUpdate 声明周期中编写逻辑。
emit则是用来触发函数,和 Vue2 中作用一致。
expose 的作用则是显式的限制向外暴露的属性。当父组件引用子组件时,只能访问到通过可 expose 暴露特定的方法。当 expose 不传递参数时,代表不暴露任何东西,当传递对象时,则有选择的暴露局部状态。
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = 0
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
setup 的返回值
setup 存在两种返回值:
- 返回一个同步对象,对象中的属性、方法在模板中均可直接使用,例如
export default { name: 'App', setup() { // 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量 let name = 'al' let age = 18 // 类似于methods属性中的方法 function hello() { alert(`我的名字是${name},我的年龄是${age}`) } return { name, age, hello } } }
<template> <h1>我是app组件</h1> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <button @click="hello()">个人信息</button> </template>
- 返回一个渲染函数 ,可以自定义渲染内容(
<script> import { h } from 'vue' export default { name: 'App', setup() { // 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量 let name = 'al' let age = 18 // Vue2 中的 main.js 中,在使用 render 函数时,就使用了 h 这个方法, // 第一个参数是节点,第二个参数是渲染内容 // 当使用 返回 render 函数之后,html 模板中的内容都会被忽略,只会执行 h 函数的渲染内容 return () => h('div','我改名字了') } } </script>
ps:返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。因为此时除了渲染函数之外,暴露不了别的数据和方法了,为了解决这一问题,Vue3采用了 expose 来暴露方法或属性,然后返回渲染函数
setup(props,{ expose }) {
// 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量
let name = 'al'
let age = 18
// 类似于methods属性中的方法
function hello() {
alert(`我的名字是${name},我的年龄是${age}`)
}
// 通过 expose 暴露方法或属性
expose({
name,
hello
})
// Vue2 中的 main.js 中,在使用 render 函数时,就使用了 h 这个方法,
// 第一个参数是节点,第二个参数是渲染内容
// 当使用 返回 render 函数之后,html 模板中的内容都会被忽略,只会执行 h 函数的渲染内容
return () => h('div','我改名字了')
}
}
注意事项
1、Vue2 和 Vue3 不要混用。Vue3 是向下兼容的,所以即使是在 Vue3 项目中,也可以使用 Vue2 的语法,但是也会带来一些问题。
- 兼容性:在 Vue3 项目中也可以使用 Vue2 的写法
<template> <p>在Vue3中访问 Vue2 写法下的属性或方法</p> <p>姓名:{{ newName }}</p> <p>年龄:{{ newAge }}</p> <button @click="getVue3Data">点击获取Vue2写法中的数据</button> </template> <script> export default { name: 'App', data() { return { newName: '汤圆', newAge:1 } }, methods: { testVue2(){ alert(`我的名字是${this.newName},我的年龄是${this.newAge}`) } }, } </script>
2、Vue2 的配置项( data、methods、computed... )中可以访问 Vue3 中 setup 中的属性、方法
<button @click="testVue3">点击后再Vue2写法中获取Vue3写法中setup的数据</button>
<script>
export default {
name: 'App',
methods: {
testVue3() {
console.log(this.name);
console.log(this.age);
console.log(this.hello);
}
},
setup() {
// 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量
let name = 'al'
let age = 18
// 类似于methods属性中的方法
function hello() {
alert(`我的名字是${name},我的年龄是${age}`)
}
return {
name,
age,
hello
}
}
}
</script>
点击按钮之后,控制台上打印出了 Vue3 setup 中定义的变量和方法。
3、但是在 Vue3 中的setup 中调用 Vue2 声明的变量或方法时,会无法找到
setup() {
// 类似于data属性中的数据,不过不用显式的声明data,而是直接设置变量
let name = 'al'
let age = 18
// 类似于methods属性中的方法
function hello() {
alert(`我的名字是${name},我的年龄是${age}`)
}
function testVue4() {
console.log(name);
console.log(age);
console.log(hello);
// 因为这些属性或方法不是在 setup 内部定义的,而是按照Vue2 的配置语法定义的,所以按照Vue2的访问模式 通过 this.xxx 访问
console.log(this.newName);
console.log(this.newAge);
console.log(this.testVue2);
}
return {
name,
age,
hello,
testVue4
}
}
从这里可以看到,在 Vue3 的 setup 中访问 Vue2 中的方法或属性时,得到的是undefined,这是因为 setup()
自身并不含对组件实例的访问权,即在 setup()
中访问 this
会是 undefined
。你可以在选项式 API 中访问组合式 API 暴露的值(在 Vue2 中访问 Vue3的属性或方法),但反过来则不行。
但是但是这里其实还存在一个问题,如果我直接在 setup 中访问 this,得到的是undefined,但是如果我是在 setup 中定义的函数中访问 this,得到的是一个 Proxy 代理对象
setup() {
console.log(this, 'this111');
function testVue4() {
console.log(this,'this');
console.log(this.newName);
console.log(this.newAge);
console.log(this.testVue2);
}
return {
testVue4
}
}
首先说 直接在 setup 中访问 this,问什么会是undefined?这是因为setup函数执行时,不依赖于Vue实例,换句话说就是 setup 函数在Vue实例化之前就执行了,所以无法直接访问到this。
然后再说为什么setup 中的方法作为组件模板内容使用时(例如作为事件处理函数被调用时),那么Vue会将这个方法绑定到Vue实例上。当该函数被调用时,this指向的就是Vue组件实例。
也就是说 Vue3中无法在 setup 内部直接访问到 this,但是通过 return 出去的函数 或者绑定到模板的数据时可以访问到this。
4、如果 Vue2 和 Vue3 混用时,数据或方法出现重名情况,以 setup 中的数据优先
<template>
<p>{{ name }}</p>
<button @click="hello">点击查看</button>
</template>
<script>
export default {
name: "App",
data() {
return {
name: "aha",
};
},
methods: {
hello() {
alert(`我的名字是${this.name}`);
},
},
setup() {
let name = "al";
// 类似于methods属性中的方法
function hello() {
alert(`我的名字是${name}`)
}
return {
// eslint-disable-next-line vue/no-dupe-keys
hello,
// eslint-disable-next-line vue/no-dupe-keys
name,
};
},
};
</script>
5、setup()
应该同步地返回一个对象。唯一可以使用 async setup()
的情况是,该组件是 Suspense 组件的后裔。
具体理解就是 setup 在一般情况下,不能使用 async 包裹,因为被 async 包裹之后,即使你返回的还是原来的对象,但是 async 会对象外部包裹一层 Promise,如果想要拿取数据的话,还需要通过.then() 来获取 ,模板中时无法直接看到并且使用 return 对象中的属性或方法的。而 Vue 是不会自动帮你做这件事的。
但是 也存在一直特殊情况,那就是 该组件是 Suspense 组件的后裔( 这个后在讲,我现在也没研究到这里来 )。
总结
1、Vue2 和 Vue3 的配置尽量不要混用
2、如果混用了,
- Vue2 中的配置 (例如:data、methods、compute、watch等)可以访问到 setup 中的属性和方法
- Vue3 中的setup 不能访问到 Vue2 中的配置项
- 存在重名情况,以 Vue3 setup 中优先
3、setup 一般不用 async,因为 setup 需要同步的返回一个对象,以此来保证 模板中数据或方法正常绑定,如果用了 async 那么返回的数据对象 会被 Promise 包裹,模板中无法看到对象中的属性,无法绑定。但是 当该组件是 Suspense 组件的后裔时,可以使用 async setup()