🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来vue篇专栏内容:vue3-setup()函数
目录
setup()函数
1.1 基本使用
1.2 访问 Prop
1.3 Setup的上下文
1.4 与渲染函数一起使用
setup()函数
setup()
钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
-
需要在非单文件组件中使用组合式 API 时。
-
需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下,都应优先使用 <script setup> 语法。
1.1 基本使用
我们可以使用响应式 API 来声明响应式的状态,在 setup()
函数中返回的对象会暴露给模板和组件实例。其它的选项也可以通过组件实例来获取 setup()
暴露的属性
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
count
}
},
mounted() {
console.log(this.count) // 0
}
}
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
请注意在模板中访问从 setup
返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value
。当通过 this
访问时也会同样如此解包。
setup()
自身并不含对组件实例的访问权,即在setup()
中访问this
会是undefined
。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组合式API</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="add">加1</button>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { ref, onMounted } = Vue
Vue.createApp({
setup () {
const count = ref(0)
const add = () => {
count.value += 1
}
onMounted(() => {
console.log(1111)
})
return {
count,
add
}
},
data () {
return {
count: 10
}
},
methods: {
add () {
this.count += 10
}
},
mounted () {
console.log('2222')
}
}).mount('#app')
</script>
</html>
生命周期先执行 组合式API 后执行选项式API,其余以组合式API为优先
1.2 访问 Prop
setup
函数的第一个参数是组件的 props
。和标准的组件一致,一个 setup
函数的 props
是响应式的,并且会在传入新的 props 时同步更新。
{
props: {
title: String,
count: Number
},
setup(props) {
console.log(props.title)
console.log(props.count)
}
}
请注意如果你解构了
props
对象,解构出的变量将会丢失响应性。因此我们推荐通过props.xxx
的形式来使用其中的 props。
如果你确实需要解构 props
对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 toRefs() 和 toRef() 这两个工具函数:
{
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组合式API</title>
</head>
<body>
<div id="app">
<button @click="num++">加1</button> {{ num }}
<my-root :num="num"></my-root>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<template id="root">
<div>{{ num }} -- {{ test }}</div>
</template>
<script>
const { ref, onMounted, computed } = Vue
const Root = {
props: ['num'],
template: '#root',
// setup (props) { // 千万不要对 props 解构
// console.log('111')
// return {
// test: computed(() => props.num) // 继续保持响应式
// }
// }
setup ({ num }) {
console.log(num)
return {
test: computed(() => num) // 失去了响应式 - test的值不会发生改变
}
}
}
Vue.createApp({
setup () {
const num = ref(10000)
return { num }
},
components: {
MyRoot: Root
}
}).mount('#app')
</script>
</html>
1.3 Setup的上下文
传入 setup
函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup
中可能会用到的值:
{
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
该上下文对象是非响应式的,可以安全地解构:
{
setup(props, { attrs, slots, emit, expose }) {
...
}
}
attrs
和 slots
都是有状态的对象,它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们,并始终通过 attrs.x
或 slots.x
的形式使用其中的属性。此外还需注意,和 props
不同,attrs
和 slots
的属性都不是响应式的。如果你想要基于 attrs
或 slots
的改变来执行副作用,那么你应该在 onBeforeUpdate
生命周期钩子中编写相关逻辑。
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose
函数暴露出的内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>setup上下文对象</title>
</head>
<body>
<div id="app">
<my-com ref="comref" class="myBox" style="color: red" id="box" msg="hello msg" @my-event="getData">
<template #header>
header
</template>
<div>content</div>
<template #footer>
footer
</template>
</my-com>
</div>
</body>
<template id="com">
<div>
<h1>子组件</h1>
<button @click="sendData">发送数据</button>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp, ref, onMounted } = Vue
const Com = {
template: '#com',
setup (props, context) {
// attrs 获取透传过来的值
// slots 如果使用了插槽
// emit 子组件给父组件传值
// expose 子组件暴露给父组件可以调用的属性和方法 ---- options API ref获取子组件的实例
console.log(props)
console.log(context.attrs) // ref 不在透传之列
console.log(context.slots)
const sendData = () => { // 子组件给父组件传值
context.emit('my-event', 1000)
}
// 自定义的属性和方法,供给父组件使用
const a = ref(1)
const b = ref(2)
const c = ref(3)
const fn = () => {
a.value = 100
}
// 暴露出去的是对象
context.expose({
a, b, fn
})
return {
sendData
}
}
}
Vue.createApp({
setup () {
const getData = (val) => { // 接收子组件的值
console.log('666', val)
}
const comref = ref() // comref 就是模版中ref="comref"
onMounted(() => {
console.log('com', comref.value) // {}
console.log('a', comref.value.a) // 1
console.log('b', comref.value.b) // 2
console.log('c', comref.value.c) // undefined 因为没有暴露
comref.value.fn()
console.log('a', comref.value.a) // 100
})
return {
getData,
comref
}
},
components: {
MyCom: Com
}
}).mount('#app')
</script>
</html>
在父组件通过ref获取子组件的实例的属性和方法的需求中,需要注意:
1.如果子组件是 选项式API组件,基本不需要做任何操作
2.如果子组件是 组合式API组件,需要通过 context.expose 暴露给父组件需要使用的属性和方法
3.如果父组件使用 选项式API, 可以通过 this.$refs.refName 访问到子组件想要你看到的属性和方法
4.如果父组件使用 组合式API,需要在setup中先创建 refName,然后再访问子组件想要你看到的属性和方法(const refName = ref() refName.value.X)
1.4 与渲染函数一起使用
setup
也可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
{
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题
我们可以通过调用 expose() 解决这个问题:
{
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渲染函数</title>
</head>
<body>
<div id="app">
<button @click="add">加1</button>
<my-child ref="child"></my-child>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { h, ref } = Vue
const Child = {
// 写法1:
// template: `<div>child</div>`
// 写法2:
// render () {
// return [
// h('div', 'child!')
// ]
// }
// 写法3
setup (props, { expose }) {
const count = ref(10)
const increment = () => {
count.value += 1
}
expose({
increment
})
// 返回一个函数 函数返回 渲染函数的结果
return () => h('div', 'child!!' + count.value)
}
}
Vue.createApp({
components: {
MyChild: Child
},
setup () {
const child = ref()
const add = () => {
child.value.increment()
}
return {
child,
add
}
}
}).mount('#app')
</script>
</html>