响应式原理
Vue2和Vue3的响应式实现原理是不同的
Vue2
底层是通过es5
的Object.defineProperty
,使用Object.defineProperty()进行数据劫持,结合订阅发布的方式实现,有一定的局限性。Vue3
底层是通过es6
的Porxy
, 通过Proxy代理,使用ref或者reactive将数据转化为响应式数据,能够更好地支持动态添加属性和删除属性。它解决了Vue2
底层实现的缺点,对数组、层级比较深的对象处理都很优秀,但缺点是浏览器兼容不是很好。
Vue2的响应式数据
在Vue2中,是使用选项式API的方式来编写代码,比如data()、computed()、watch等方法实现响应式
Vue3的响应式数据
reactive和ref
reactive
reactive定义引用数据类型(以对象和数组举例),它能够将复杂数据类型的内部属性或者数据项声明为响应式数据,所以reactive的响应式是深层次的,其底层是通过ES6的Proxy来实现数据响应式,相对于Vue2的Object.defineProperty,具有能监听增删操作,能监听对象属性的变化等优点
- reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据;
- 通常用来定义响应式的对象数据。
js
<template>
<div>name:{{obj.name}}</div>
<button @click="updateName">修改name</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
// 定义响应式对象
const obj = reactive({
name: 'lisi',
age:20
})
const updateName = () => {
obj.name = '我是修改后的name'
console.log('我是按钮..........');
};
return {
obj,
updateName
}
}
}
</script>
ref函数
ref函数,常用于简单数据类型定义为响应式数据,其实也可以定义复杂数据类型的响应式数据,对于数据未知的情况下ref是最适用的。
在修改值,获取值的时候,需要.value。在模板中使用ref申明的响应式数据,可以省略.value,在js代码中修改ref声明的数据,需要加上.value。
js
<template>
<div>name:{{name}}</div>
<button @click="changeName">修改name</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const name = ref('zhangsan');
const changeName = () => {
name.value = 'lisi'
}
return {
name,
changeName
}
}
}
</script>
两者的不同
- ref用于定义基本类型和引用类型,reactive仅用于定义引用类型
- reactive只能用于定义引用数据类型的原因在于内部是通过ES6的Proxy实现响应式的,而Proxy不适用于基本数据类型
- ref定义对象时,底层会通过reactive转换成具有深层次的响应式对象,所以ref本质上是reactive的再封装(会判断数据的类型进行不同处理)
- 在脚本里使用ref定义的数据时,记得加.value后缀
- 在定义数组时,建议使用ref,从而可避免reactive定义时值修改导致的响应式丢失问题
js
const tableData = reactive([]) // 定义
const getTableData = async () => {
const { data } = await getTableDataApi() // 模拟接口获取表格数据
tableData = data // 修改,错误示例,这样赋值会使tableData失去响应式
}
// 方法一:改为 ref 定义
const tableData = ref([])
const getTableData = async () => {
const { data } = await getTableDataApi()
tableData.value = data // 使用.value重新赋值
}
// 方法二:使用 push 方法
const tableData = reactive([])
const getTableData = async () => {
const { data } = await getTableDataApi()
tableData.push(...data) // 先使用...将data解构,再使用push方法
}
// 方法三:定义时数组外层嵌套一个对象
const tableData = reactive({ list:[] })
const getTableData = async () => {
const { data } = await getTableDataApi()
tableData.list = data // 通过访问list属性重新赋值
}
// 方法四:赋值前再包一层 reactive
const tableData = reactive([])
const getTableData = async () => {
const { data } = await getTableDataApi()
tableData = reactive(data) // 赋值前再包一层reactive
}
为什么需要两个
虽然ref函数既可以处理基本数据类型也可以处理引用数据类型,但是在普通js代码里修改该响应式数据的值时需要使用.value
的写法,会存在.value
的嵌套问题,因此使用reactive
来处理引用数据类型,避免该问题。
toRef和toRefs
ref是对元数据的拷贝,修改响应式数据时不会影响之前的数据,视图会更新。tooRef和toRefs是对元数据的引用,修改响应式数据时,原数据也会改变,但是视图不会更新,只有原始数据改变后,该数据和视图都会更新。toRef修改的是对象的某个属性,toRefs修改的是整个对象
toRef
toRef 函数的作用:转换响应式对象中某个属性为单独响应式数据,并且转换后的值和之前是关联的(ref 函数也可以转换,但值非关联)。
js
<template>
<div class="container">
<h2>name: {{ obj.name }} age: {{obj.age}}</h2>
<button @click="updateName">修改数据</button>
</div></template><script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
name: '初映',
age: 18,
address: '江西',
sex: '男',
})
const updateName = () => {
obj.name = '初映CY的前说'
}
return { obj, updateName }
},
}</script>
这样写有几个问题:
- 模板中都要使用 obj. 进行获取数据,较为麻烦
- 明明模板中只用到了 name 和 age,却把整个 obj 进行了导出,没必要,性能浪费。
使用toRef
进行修改,只需要将需要的属性return出去即好,且模板中也不需要加obj.
前缀了。
js
<template>
<div class="container">
<h2>name: {{ name }} </h2>
<button @click="updateName">修改数据</button>
</div></template><script>
import { reactive,toRef } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
name: '初映',
age: 18,
address: '江西',
sex: '男',
})
const name = toRef(obj, 'name')
const updateName = () => {
obj.name = '初映CY的前说'
}
return { name, updateName }
},
}</script>
toRefs
toRefs 函数的作用:转换响应式对象中所有属性为单独响应式数据,并且转换后的值和之前是关联的。
js
<template>
<div>{{name}}</div>
<div>{{age}}</div>
<button @click="update">修改name</button>
</template>
<script>
import {reactive, toRefs} from 'vue'
export default {
setup() {
const obj = reactive({
name: '张三',
age:18
})
console.log(obj);
const obj2 = toRefs(obj);
console.log(obj2); //发现obj2里面的name和age都是响应式属性,指向obj的属性
// 解构之后重新赋值的是普通对象
const obj3 = { ...obj };
console.log(obj3);
const update =()=> {
obj.name = '我是修改的原始数据的obj'
}
return {
// 解构obj2,用的时候直接拿属性名,不需要obj2.name或obj2.age
...obj2,
update
}
}
}
</script>
修改原始数据obj后会发现,转换过后的obj2中的值会跟着改变。