第三节: 一文带你全面理解 vue3 新增 setup 选项的使用
前言
上一节, 我们分析了官网中通用API
的前三个, 后面两个我们慢慢分析. 从今天这一章开始,我们进入Vue3
的核心, 即组合式API 的学习.
首先我们从组合式API 的入口开始分析, 理解setup
钩子函数的使用
1. setup 钩子函数的认识
钩子函数, 相信学过vue2
的同学都很熟悉了, vue2
组件的生命周期钩子函数会在组件不同的生命周期阶段调用.
但钩子函数的本质, 其实就是组件选项对象的方法. 我们通常说的选项都是选项对象的属性.
vue3
选项对象中新增了一个钩子函数, 即setup
作为组合式API的入口, 也就是说我们以后学习的组合式API 都需要在setup
钩子函数中使用.
示例:
import { defineComponent } from "vue";
export default defineComponent({
// 钩子函数
setup() {
return {};
},
});
和data
钩子函数一样, setup
函数返回一个数据对象.
不同的是,setup
函数返回值除了可以是数据对象, 也可以是一个渲染函数.
2. setup基本使用
setup
钩子函数的使用,我们在上一章中也提及过:
setup
函数可以返回一个渲染函数, 渲染函数返回通过h
创建的vnode
setup
函数可以返回一个对象, 对象的数据可以在模板上直接使用
因为之前的学习, 我们知道,只有组件实例对象上的数据才可以被模板直接使用,
接下来我们分析一下setup
返回对象中数据在组件实例对象上是如何存放的.
2.1. setup 钩子函数返回对象
setup
钩子函数 是vue3
新增的选项, 是组件内部使用组合式API的入口, 在setup()
函数中返回的对象
会暴露给模板和组件实例, 其他选项也可以通过组件实例来获取setup()
暴露的属性
我们使用mounted
钩子函数输出组件实例对象, 即this
的值, 查看setup
返回值是否在组件实例对象上
示例:
import { defineComponent } from "vue";
export default defineComponent({
setup() {
// 声明数据
const msg = "hello";
// 返回数据对象
return { msg };
},
mounted() {
console.log("this", this);
},
});
控制台输出结果:
你应该很明确看到,在组件实例对象上, 具有setup
返回的数据msg
, 因此setup
返回对象中的数据,可以直接在模板中使用
2.2. setup 钩子函数中不能通过this 获取组件对象
上个实例中我们在mounted
钩子函数中通过this
访问组件实例对象. 如果我们在setup
中需要使用组件实例对象怎么办? , 我们第一反应应该是使用this
来获取, 看示例
示例:
import { defineComponent, } from "vue";
export default defineComponent({
setup() {
console.log("this", this);
// 声明响应式数据
const msg = "hello ";
// 返回数据对象
return { msg };
}
});
运行结果:
// 控制台输出结果
this undefined
通过控制台输出的结果, 你就会发现, 在setup
中使用this
无法获取到组件的实例对象.
主要原因在于:
setup 钩子先于组件实例化对象创建之前调用
记住这句话,很重要, 也就是说在执行setup
钩子函数时, 组件对象还未创建完成. 就像vue2
中beforeCreate
钩子函数一样, 无法通过this
获取组件对象
如果你此时查看vue3
文档中的组件生命周期图, 你就会发现setup
钩子函数是在beforeCreate
钩子函数之前调用的, beforeCreate
钩子函数都无法通过this
获取的上下文组件对象, setup
可想而知
vue3
组件生命周期图:
2.3. setup 钩子函数获取组件实例对象
在某些场景下, 我们可能需要通过组件对象访问组件上的数据, 但我们又无法通过this
来获取到组件对象, 那该怎么办呢?
其实你不用担心, vue3
给我们提供了一个全局的getCurrentInstance
函数, 获取组件实例对象.
但需要注意的是getCurrentInstance
获取的组件对象和this
获取的组件对象不太一样.我们通过示例查看getCurrentInstance
返回值
示例:
<template>
<div>
<div>{{ msg }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance } from 'vue';
export default defineComponent({
name: 'app',
// setup函数, vue3 组合式API的入口
setup() {
// 获取组件实例对象
const instance = getCurrentInstance()
console.log('instance', instance)
// 声明响应式数据
const msg = 'hello '
// 返回数据对象
return { msg }
},
})
</script>
控制台输出结果:
// 组件示例对象 instance 具有一个ctx 数据
// ctx 上下文对象存储当前组件setup 暴露的数据
/*
instance {
//...
ctx:{
msg: 'hello'
}
}
*/
通过示例返回的结果, 你可以看到getCurrentInstance
函数返回的组件实例对象中具有一个ctx
上下文对象, 该对象就是你通过this
访问的对象.
如果你要问为什么会有此变化. 这是vue3
版本更新后向前兼容一种手段.
如果你阅读过vue3
源码, 源码中为了兼容vue2
选项式API 做了大量的兼容手段, 如果你有兴趣,可以查看我vue3
源码的文章.
最后提醒一句:
如果你将来选择使用TypeScript
开发, 建议尽量少用getCurrentInstance
主要原因在于getCurrentInstance
会丢失返回值的TS
类型, 比如props
, 具体视情况而定
至此, 你应该已经了解, setup
函数返回的对象中的数据会暴露给组件实例对象
的ctx
上下文对象中,
因此模板中就可以正常使用了
2.4. setup 钩子函数返回渲染函数
setup
钩子函数可以返回一个数据对象, 还可以直接返回渲染函数,
渲染函数就是一个返回vnode
的函数, vue3
给我们提供了一个全局h
函数, 用于创建vnode
示例:
import { defineComponent, h } from "vue";
export default defineComponent({
setup() {
// 返回一个渲染函数
return () => h("h2", null, "hello world");
},
});
页面渲染效果:
3. setup 钩子函数的参数
使用setup
函数时, setup
函数可以接收两个参数:
props:
收集父组件向当前子组件传递的数据,context
: 上下文对象
3.1. props 参数
setup 函数的第一个参数是组件的 props
。
讲到props
参数,在使用时, 你需要注意一下事项:
props
数据是响应式的,也就表示父组件修改了传入的值, 子组件使用的值也会发生同步更新props
中的数据不能通过解构的方式使用, 因为会丢失响应性, 即父组件数据的变化, 不会触发子组件的变化
具体响应性的原理,我们之后通过学习响应式API时, 在详细分析
我们通过示例来理解setup
函数的props
参数
示例:
父组件
<template>
<div>
<!-- 使用子组件,并传递props 参数 -->
<jc-children :count="count"></jc-children>
<!-- 点击修改数据 -->
<button @click="change">修改</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import JxChildren from './views/child.vue'
export default defineComponent({
name: 'app',
components: { JxChildren },
setup() {
// 声明相应式数据
const count = ref(0)
const change = () => {
count.value++
}
return { msg, count, change }
}
})
</script>
父组件中通过ref
API 创建一个具有响应性的数据.
子组件:
<template>
<h1>点击次数:{{ count }}</h1>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
export default defineComponent({
props: {
count: {
type: Number
}
},
setup(props) {
console.log(props.count)
return {props}
}
})
</script>
在示例中, 因为子组件setup
函数第一参数props
是响应数据, 所以当父组件cout
发生变化, 子组件setup
函数中props
就能够同步获取到最新的数据,并且触发视图更行
接下来,我们修改一下子组件中 获取props
数据的用法, 通过解构的方式获取count
数据
示例:
<template>
<h1>点击次数:{{ count2 }}</h1>
<h1>点击次数2:{{ count3 }}</h1>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
export default defineComponent({
props: {
count: {
type: Number
}
},
setup(props) {
// 1. 解构获取props中的数据
const {count: count2} = props
// 2. 赋值新的变量
const count3 = props.count
return {count2,count3}
}
})
</script>
示例中通过解构获取到count2
或者 直接赋值给新的变量count3
, 你会发现他们都是非响应式数据,
即当父组件count
发生变化时, props
本身会更新最新数据, 但是count2
, count3
不会同步更新, 因此视图不会发生变化
如果你确实需要解构赋值,并暴露数据响应特性, 可以使用只要要学习的API, 比如toRefs
, toRef
两个API, 这两个API 后续在说
当然最佳的方式, 就是不要从props
中解构数据, 直接通过props.xxx
方式获取数据, 就不会丢失响应性.
3.2. context 上下文对象
传入 setup
函数的第二个参数是一个 setup
上下文对象。context
上下文对象中暴露了一些常用的数据, 比如attrs
, emit
等
我们可以在控制台输出, 查看context
上下文对象中都具有哪些数据
示例:
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup(props, ctx) {
console.log('ctx', ctx)
/*
上下文对象
ctx {
attrs: (…) // 透传 Attributes(非响应式的对象,等价于 $attrs)
emit: (…) // 触发事件(函数,等价于 $emit)
expose: (exposed) => {…} // 暴露公共属性(函数)
slots: (…) // 插槽(非响应式的对象,等价于 $slots)
}
*/
return { }
}
})
</script>
可以看到context
对象具有attrs
, emit
, expose
, slots
等常用数据.
主要注意的时: 该上下文对象是非响应式的,可以安全地解构
因此在工作中, 推荐使用解构的方式获取上下文对象中具体的数据
示例:
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
3.3. expose 暴露数据
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose
函数暴露出的内容:
简单理解就是, 原来父组件通过ref
只要能获取到子组件对象, 就可以访问子组件中的所有数据,
expose
函数的作用就是在子组件中明确限定父组件可以通过子组件对象访问的数据. 只有通过expose
函数暴露出去数据, 父组件才能访问
示例:
未使用expose 时:
<template>
<div>
<!-- 使用子组件,并传递props 参数 -->
<jc-children ref="child"></jc-children>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import JxChildren from './views/child.vue'
export default defineComponent({
name: 'app',
components: { JxChildren },
setup() {
// 获取子组件实例对象
const child = ref()
// 查看组件实例对象
nextTick(() => {
console.log('child', child)
})
return { child }
}
})
</script>
子组件
<template>
<h1>子组件</h1>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
export default defineComponent({
setup() {
// 定义子组件数据
const name = ref('张三')
const age = ref(18)
return { name, age}
}
})
</script>
通过示例运行结果, 你就会发现, 在父组件中通过ref
获取子组件实例对象
在子组件实例对象上能看到子组件所有的数据
运行结果:
// console.log('child', child) 打印结果
/*
child {
age: (…)
name: (…)
$: (…)
$attrs: (…)
$data: (…)
$el: (…)
$emit: (…)
$forceUpdate: (…)
$nextTick: (…)
$options: (…)
$parent: (…)
$props: (…)
$refs: (…)
$root: (…)
$slots: (…)
$watch: (…)
_: (…)
}
*/
如果在子组件中使用expose
指定暴露数据
子组件
<template>
<h1>子组件</h1>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
export default defineComponent({
setup(props, {expose}) {
// 定义子组件数据
const name = ref('张三')
const age = ref(18)
// 子组件指定暴露age 数据
expose({age})
return { name, age}
}
})
</script>
此时父组件获取子组件实例对象,打印时查看到的只有子组件暴露的数据
运行结果:
// console.log('child', child) 打印结果
/*
child {
age: (…)
}
*/
也就是说, 只有expose
函数参数对象中的数据,才会暴露出去.
4. 结语
至此, 关于setup
钩子函数的使用,我们就已经讲完了, 你应该理解以下知识:
setup
钩子函数不能通过this
获取组件对象, 而是通过getCurrentInstance
函数获取setup
钩子函数可以返回一个数据对象, 也可以直接返回渲染函数setup
钩子函数接收两个参数, 即props
和context
setup
钩子函数中props
参数不推荐使用解构方式获取数据, 会丢失数据的响应性setup
钩子函数的context
参数推荐使用解构方式获取数据, 因为context
就是一个普通对象expose
API 函数暴露出去的数据, 才能在其他组件中通过组件对象访问
如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!
更多推荐
所有评论(0)