Vue3、setup、ref、reactive、toRef、toRefs
1、setup的使用
1.1、简介
setup选项是一个接受props和context的函数,也就是说它的基本写法应该是这样的:
export default {
name: 'test',
setup(props, context){
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
}
接受一个props和context函数并且将setup内的内容通过return暴露给组件的其余部分。
1.2、setup注意点
由于在执行setup函数的时候,还没执行created生命周期方法,所以在setup函数中,无法使用data和methods的变量和方法。
由于我们不能在setup函数中使用data和methods,所以Vue为了避免我们错误的使用,直接将setup函数中的this修改成了undefined。
1.3、定义响应式数据
ref、reactive
- ref我们用来将基本数据类型定义为响应式数据,其本质是基于Object.defineProperty()重新定义属性的方式来实现(ref更适合定义基本数据类型)
- reactive用来将引用类型定义为响应式数据,其本质是基于Proxy实现对象代理。
- 基本数据类型(单类型):除Object。String、Number、boolean、null、undefined。
- 引用数据类型:object。里面包含的function、Array、Date。
- 定义:
<script>
import { ref, reactive } from "vue";
export default {
name: "test",
setup(){
// 基本类型
const nub = ref(0)
const str = ref('inline')
const boo = ref(false)
// 引用类型
const obj = reactive({
name: 'inline',
age: '18'
})
const arr = reactive(['0', '1', '2'])
return {
nub,
str,
boo,
obj,
arr
}
}
}
</script>
- 使用:
<template>
<div>
<h1>基本类型</h1>
<p>nub:{{ nub }}</p>
<p>str:{{ str }}</p>
<p>boo:{{ boo }}</p>
<h1>引用类型</h1>
<p>obj:{{ obj.name }}</p>
<p>arr:{{ arr[1] }}</p>
</div>
</template>
- 结果:
1.4、toRefs
如果我们用reactive的形式来定义响应式变量
setup(){
const obj = reactive({
name: 'inline',
gender: '男',
age: '18'
})
return {
obj
}
}
使用:
<div>
<p>姓名:{{ obj.name }}</p>
<p>性别:{{ obj.gender }}</p>
<p>年龄:{{ obj.age }}</p>
</div>
这样我们是不是发现在模版内使用参数很麻烦,那我们想直接用{{ name }}的方式访问行不行,答案是不行的。
这里我们使用es6的扩展运算符。
setup(){
const obj = reactive({
name: 'inline',
gender: '男‘,
age: '18',
})
return {
...obj
}
}
使用:
<div>
<p>姓名:{{ name }}</p>
<p>性别:{{ gender }}</p>
<p>年龄:{{ age }}</p>
<button @click="name = 'lgg'">改变姓名</button>
<button @click="gender = '未知'">改变性别</button>
<button @click="age = '18'">改变年龄</button>
</div>
结果:
这里我们看到,参数都正常的显示在了页面上,但是我们去改变参数时发现视图并没有更新,why?
我们把扩展运算符写成它的等价格式
setup(){
const obj = reactive({
name: 'inline',
gender: '男‘,
age: '18',
})
return {
...obj ==> name:obj.name
}
}
当鼠标浮动上去提示name只是个字符串?
那我们再看看我们用ref定义值时提示的是什么
这时我们看到name是一个Ref,是一个响应式字符串。
这时就知道了为什么没有更新视图了,当我们用…扩展运算符时我们得到的只是一个普通类型的数值,并不是一个响应式数据。
问了解决这个问题,vue3给我们提供了**toRefs函数,来让我们看看效果如何:
setup(){
const obj = reactive({
name: 'inline',
gender: '男',
age: '18'
})
return {
...toRefs(obj)
}
}
<div>
<p>姓名:{{ name }}</p>
<p>性别:{{ gender }}</p>
<p>年龄:{{ age }}</p>
<button @click="name = 'lgg'">改变姓名</button>
<button @click="gender = '未知'">改变性别</button>
<button @click="age = '18'">改变年龄</button>
</div>
参数都可以正常改变,成功改头换面。
toRefs总结:
toRefs会将一个响应式的对象转变为一个普通对象,然后将这个普通对象里的每一个属性变为一个响应式的数据。
1.5、setup中执行方法
1.5.1、方式一:
以reactive定义响应式数据的方式来定义方法:
<script>
import { ref, reactive, toRefs } from 'vue';
export default {
name: 'test',
setup(){
const str = ref('inline')
const fun = reactive({
fun1(data) {
console.log(str.value)
this.fun2(data)
},
fun2(data) {
console.log(data)
console.log('我是fun2')
}
})
return {
...toRefs(fun)
}
}
}
</script>
通过点击事件将值传给fun1,fun1接受到后再传给fun2。
这里我们用this.fun2()的方式去调用fun2,为什么这里用this可以正常执行不会报undefind,因为这里的this非彼this,Vue2里的this是实例,这里的this是对象。
<button @click="fun1('你还')">点我</button>
结果:
1.5.2、方式二:
注意这里调用fun2的方式与方式一不同,直接调用就可以,不用this调用。
export default {
name: 'test',
setup(){
const fun1 = (data) => {
fun2(data)
}
const fun2 = (data) => {
console.log(data)
}
return {
fun1
}
}
}
调用:
<button @click="fun1('你好 inline')">点我1</button>
结果:
1.5.3、方式三:
这种方式避免了将功能逻辑都堆叠在setup的问题,我们可以将独立的功能写成单独的函数,这里我在setup外写了fun()login()两个功能函数,并在setup内分别调用。
import { ref, reactive, toRefs } from 'vue';
export default {
name: 'test',
setup(){
const test1 = fun() // 如果函数返回的参数过多,可以赋值给变量并用扩展运算符暴露给组件的其余部分。
const { test } = login() // 也可单个接受
return {
...toRefs(test1),
test
}
}
}
// 功能1
function fun() {
let str = ref('我是功能1')
function fun1(data) {
console.log(str.value)
fun2(data)
}
function fun2(data) {
console.log(data)
}
return{
fun1,
fun2
}
}
// 功能2
function login() {
const obj = reactive({
msg: '我是功能2'
})
function test() {
console.log(obj.msg)
}
return{
test
}
}
调用:
<button @click="fun1(你好 inline)">点击1</button>
<button @click="test">点击2</button>
1.5.4、方式四:
与方式三一样,只是我们将两个功能函数提取出来放在单独的.js文件中:
然后引入组件,并在setup内调用
<template>
<div>
<button @click="fun1('你好 inline')">点击1</button>
<button @click="test">点击2</button>
</div>
</tempalte>
<script>
import { ref, reactive, toRefs } from 'vue';
import { fun, login } from './test.js'
export default {
name: "test",
setup(){
const test1 = fun()
const { test } = login()
}
return{
...toRefs(test1),
test
}
}
</script>
1.5.5、方式五:
我们还可以这样写,这里我定义一个reactive响应式对象,赋值给login变量,这个响应式对像内有我们登录需要的参数、验证和方法,这里我们全部放在login这个响应式对象内然后用toRefs及其扩展运算符暴露出去。
<script>
import { ref, reactive, toRefs } from "vue";
export default {
name: "test",
setup(){
const login = reactive({
param: {
username: '123',
password: '123456',
},
rules: {
username: [{
required: true,
message: '请输入用户名',
trigger: 'blur'
}],
password: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}]
},
login(){
this.param.username = 'inline'
this.param.password = '123456'
console.log('登录成功!‘)
}
})
return{
...toRefs
}
}
}
</script>
使用:
<input type="text" v-model="param.username">
<input type="password" v-model="param.password">
<button @click="login">登录</button>
结果:
正常执行,所以我们还可以将一个功能的所有方法和相关参数写在一个reactive对像内。
1.6、script setup:
script setup已经在vue3.2的版本上正式发布
用法:
<script setup></script>
- 变量方法无需return
- 使用<script setup》时,模版被编译成一个内联在setup函数作用域内的渲染函数。这意味着内部声明的任何顶级绑定<script setup》都可以直接在模版中使用。
<script setup>
const msg = 'hello!'
</script>
<template>
<div>{{ msg }}</div>
</template>
script setup内定义的变量和方法无需返回,可以直接使用。
- 组件引入:
导入的组件无需注册,可直接使用。
<script setup>
// 导入的组件也可以直接在模版中使用
import Foo from './Foo.vue'
import { ref } from 'vue'
// 编写合成API代码,就像在正常设置中一样
// 不需要手动返回所有内容
const count = ref(0)
const inc = () => {
count.value++
}
</script>
<template>
<Foo :count="count" @click="inc">
</template>
- 发布Props和Emits
<script setup>
const props = defineProps({ foo: String })
const emit = defineEmits(['update', 'delete'])
</script>
- 普通script和script setup:
script setup 可以和script同时存在
<script>
export const name = 1
</script>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
- script setup 附加选项:
script setup给提供了大多数与options api等效的能力;
就是说options api能办到的事script setup大部分都能办到。
那还有哪些是script setup做不到的呢?
- name
- inheritAttrs
- 插件或库所需要的自定义选项
怎么办?分开写:
<script>
export default {
name: 'CustomName', // 自定义名称
inheritAttrs: false, // 继承属性
customOptions: {} // 自定义选项
}
</script>
<script setup></script>
1.7、defineExpose:
script setup定义的变量默认不会暴露出去,因为变量这时候包含在setup的闭包中。这时我们可以使用defineExpose({})来暴露组件内部属性给父组件使用:
<script setup>
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
当父组件通过模版引用获取此组件的实例时,检索到的实例将会是这样{ a: number, b: number } (引用会像在普通实例上一样自动展开)
2、ref、reactive、toRef、toRefs的作用及区别
2.1、reactive
reactive用于为对象添加响应式状态。
接受一个js对象作为参数,返回一个具有响应式状态的副本。
- 获取数据值的时候直接获取,不需要加.value。
- 参数只能传入对象类型。
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
// 打印count的值
console.log(state.count)
2.2、ref
ref用于为数据添加响应式状态。
由于reactive只能传入对象类型的参数,而对于基本数据类型要添加响应式状态就只能用ref了,同样返回一个具有响应式状态的副本。
- setup获取数据值的时候需要加.value,template则会自动带入.value。(对于基本类型,ref是自己的实现方式且性能优于reactive,而对于对象类型,ref可以理解为是通过reactive包装实现的)
- 参数可以传递任意数据类型,传递对象类型时也能保持深度响应式,所以适用性更广。
- vue3.0 setup里定义数据时优先使用ref,方便逻辑拆分和业务解耦。
import { ref } from 'vue'
// 为基本数据类型添加响应式状态
const name = ref('lgg')
// 为复杂数据类型添加响应式状态
const state = ref({
count: 0
})
// 打印name的值
console.log(name.value)
// 打印count的值
console.log(state.value.count)
2.3、toRef
toRef用于为**源响应式对象上的属性**新建一个ref,从而保持对其源对象属性的响应式连接。
接受两个参数:1、源响应式对象;2、属性名。返回一个ref数据。
例如:使用父组件传递的props数据时,要引用props数据时,要引用props的某个属性且要保持响应式连接时就很有用。
- 获取数据值的时候需要加.value。
- toRef后的ref数据如果是复杂类型数据时,不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据。
import { defineComponent, toRef } from 'vue'
export default defineComponent({
props: [title],
setup(props) {
// 创建变量myTitle
const myTitle = toRef(props, 'title')
console.log(myTitle.value)
}
})
2.4、toRefs
toRefs用于将响应式对象转换为结果对象,其中结果对象的每个属性都是指向原始对象响应的属性的ref。
常用于es6的解构赋值操作,因为在对一个响应式对象直接解构时解构后的数据将不再有响应式,而使用toRefs可以方便解决这一问题。
- 获取数据值的时候需要加.value。
- toRefs后的ref数据如果是复杂类型数据时,不是原始数据的拷贝,而是引用,改变结果数据的值,也会同时改变原始数据。
- 作用其实和toRef类似,只不过toRef是对一个个属性手动赋值,而toRefs是自动解构赋值。
import { defineComponent, toRefs} from 'vue'
export default defineComponent({
props: [title],
setup(props) {
// 使用了解构赋值语法创建了变量myTitle
const { myTitle } = toRefs(props)
console.log(myTitle.value)
}
})