Vue3使用setup、ref和reactive
1. 初识setup函数
- 理解: 它是Vue3中新的一个配置项, 值为函数
- setup是所有的Composetion API(组合API)"表演的舞台"
- 组件中所用到: 数据方法等等,均要配置到setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法,在模版中局可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用,有下面奇怪的情形出现:
- Vue2.x配置(data,methods、computed...)中可以访问到setup中的属性,方法。
- 但在setup中不能访问到Vue2.x配置(data, methods、computed...)。
- 如果有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise, Vue不会自动解析then中的值,自然模版看不到return对象中的属性。
提示
例外:用到
<Suspense>
和异步组件配合使用时可以return一个Promise实例对象,即此时是异步的 - 尽量不要与Vue2.x配置混用,有下面奇怪的情形出现:
2. setup函数深入理解
- setup执行的时机:在beforeCreate之前执行一次,this的值是undefined
- setup的参数:
- props: 值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
- context(上下文对象):
- attrs: 值为对象,包含组件外部传递过来,但没有在props配置中声明的属性
- slots: 收到的插槽内容,相当于
this.$slots
- emit: 分发自定义事件的函数, 相当于
this.$emit
在Vue3中:
js
<script>
export default {
name: 'Demo',
beforeCreate(){
console.log('------------beforeCreate-------------')
},
// Vue3中提供两个参数方便调用,避免没有this使用影响,需要重点关注props和context
setup(props, context){
console.log('------------setup-------------', this);
}
}
</script>
执行结果:
提示
因此在Vue3中不用关心是否能写箭头函数了,因为已经没有了this,箭头函数中现在不用写this避免undefined出现。
- context参数
attrs
源自Vue2的$attrs, context参数slots
源自Vue2中的$slots
2.1 回顾Vue2中的attrs和slots
$attrs
用来保存父组件传入子组件的属性信息,子组件$attrs
会自动接收,它和props
互斥,在props中定义的会在$attrs
移除 在Vue2中About.vue代码如下:
vue
<template>
<div>
<About msg="早上好" name="jack">
<span>在广州打工</span>
<span>在成都打工</span>
</About>
</div>
</template>
可以查看控制台打印About.vue的实例对象查看上面的属性: 其中$slots
中存放了两个<span>
的虚拟节点, default表示为默认插槽。 带名字的插槽传入
vue
<template>
<div>
<About msg="早上好" name="jack">
<template v-slot:test1>
<span>在广州打工</span>
</template>
<template v-slot:test2>
<span>在成都打工</span>
</template>
</About>
</div>
</template>
可见出现两个带有名称的插槽在$slots上面。
2.2 分析props对象
在Vue3中父组件App.vue传入的数据:"你好啊"
vue
<template>
<Demo hello="你好啊"></Demo>
</template>
<script>
import { ref, reactive} from 'vue'
import Demo from './components/Demo.vue'
export default {
name: 'App',
components: {Demo},
setup(props, context) {
}
}
</script>
vue
<template>
<div>{{ hello }}</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Demo',
setup(props, context) {
console.log('------------setup-------------', props);
}
}
</script>
运行会发现警告:提示我们没有使用传值变量hello,分析console打印发现props里面什么也没有,说明Vue3中参数props传入进来并没有自动包含携带父传子数据 这时需要
props
属性,Vue3中依然有效,修改Demo.vue:
vue
<template>
<div>{{ hello }}</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Demo',
props: ['hello'],
setup(props, context) {
console.log('------------setup-------------', props);
}
}
</script>
运行警告消失, props数据出来了:
2.2 分析context对象
打印context对象如下,有四大属性attrs、emit、expose、slots
vue
<template>
<div>{{ hello }}</div>
<button @click="invokeWalk">触发自定义事件</button>
<slot></slot>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Demo',
props: ['hello'],
emits: ['walkCode'], // Vue3需要在子组件中申明使用自定义函数,不然会有警告
setup(props, context) {
console.log('------------context-------------', context)
console.log('attrs', context.attrs); // 由于配置props: ['hello'],attrs中也就没有了hello值
console.log('slots', context.slots); // slots不会随着子组件中配置了<slot></slot>,就会消失里面的值
console.log('emit', context.emit);
let invokeWalk = ()=>{
context.emit('walkCode', 'jack')
}
return {
invokeWalk
}
}
}
</script>
vue
<template>
<Demo hello="你好啊" @walkCode="goWalkCode">
<span>我是自定义Html代码</span>
</Demo>
</template>
<script>
import { ref, reactive} from 'vue'
import Demo from './components/Demo.vue'
export default {
name: 'App',
components: {Demo},
setup(props, context) {
let goWalkCode=(name)=>{
alert(`继续搞java, ${name}`)
}
return {
goWalkCode
}
}
}
</script>
2.3 setup返回数据误区
- 不具有响应式,只在第一次页面正常显示,点击按钮页面不变。
js
return {
name: person.name,
age: person.age,
salary: person.job.j1.salary,
}
- 虽然具有响应式,但是数据已经没有和person关联,person的数据不再受到变化,响应式的数据只是return产生的新数据,不能在返回数据中加入ref。
js
return {
name: ref(person.name),
age: ref(person.age),
salary: ref(person.job.j1.salary)
}
3. ref函数
不同于Vue2中的ref
类似id的效果,获取组件实例对象,Vue3中的ref
是一个函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象)
- JS中操作数据: xxx.value
- 模版中数据读取:不需要.value得到值,直接
{{xxx}}
即可,Vue3读取模版插值的时候会判断是RefImpl类型自动读取value属性。 - 接收的数据可以是: 基本类型、也可以是对象类型。
- 基本类型的数据: 响应式依然是靠Object.defineProperties()的get与set的实现, 是RefImpl类型数据
- 对象类型的数据: 内部"求助"了Vue3.x中的新函数--reactive函数, 是Proxy类型数据
4. reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要使用它,要用ref函数)
- 语法: const 代理对象 = reactive(源对象)接收一个对象(或数组), 返回一个代理对象,返回一个代理对象(Proxy的实例对象, 简称proxy对象)
- reactive定义的响应式数据是深层次的
- 内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作。
5. 对比ref和reactive
- 从定义数据角度对比:
- ref用来定义: 基本类型数据。
- reative用来定义: 对象(或数组)类型数据
- 备注: ref也可以定义对象(或数组)类型数据, 它内部会自动reactive转为代理对象。
- 从原理角度对比:
- ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)
- reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据
- 从使用角度对比:
- ref定义的数据: 操作数据需要.value, 读取数据时模版中直接读取不需要.value
- reactive定义的数据:操作数据与读取数据: 均不需要.value