vue3 基础-API-响应式 ref, reactive
上篇咱介绍了 CompositionAPI, 其核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题.
然后初介绍了 setup 函数的作用, 即其是在 created 实例完全初始化之前调用的, 因此不能用 this
, 它的主要作用就可以是管理我们接下来要介绍的 API. 在项目中通常扮演一个"任务调度" 的角色, 对整个单页面的逻辑起一个核心调度的作用.
响应式变量
首先, 注意上面这里有个关键词叫 响应式状态变量
那我们先用一个案例来认识该现象.
<!DOCTYPE html>
<html lang="en">
<head>
<title>响应式变量</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `<div>{{name}}</div>`,
setup (props, context) {
let name = "youge"
// 延时器的作用是 2秒后, 将 name 的值 改为 "cj"
setTimeout(() => {
name = 'cj'
}, 2000);
return { name }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
经过 2 秒后, 发现页面的数据并没有改变 !, 这就引入了一个响应式变量的概念.
Vue3 是使用 Proxy, 它可以劫持整个data对象,然后递归返回属性的值的代理即可实现响应式;但是它的兼容性不是很好;
Vue2 是使用 Object.defineProperty,它只能劫持对象的属性,所以它需要深度遍历data中的每个属性,这种方式对于数组很不友好,而且对象观测后,新增的属性就不是响应式的,不过可以用Vue.set()来添加新的属性;
总之结论就是在 vue 中, dom 的更新由数据驱动, 将数据进行 Proxy 封装, 当数据变化时, 会自动触发模板 dom 的更新啦.
ref
像 ref, reactive 他们都是将一个 vue 将数据变量封装为响应式变量的方法啦 ( ref 的底层也是 reactive) .
在使用中呢, ref 用来处理基础类型的数据 (number, string, null, boolean, undefined) 将其变为响应式.
<!DOCTYPE html>
<html lang="en">
<head>
<title>ref</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
// 当时 ref 时, 这里不需要写成 name.value, 直接 name
template: `<div>{{name}}</div>`,
setup (props, context) {
const { ref } = Vue
// proxy: 将 'youge' 封装成 proxy({value: 'youge'}) 的响应式
let name = ref("youge")
// 延时器的作用是 2秒后, 将 name 的值 改为 "cj"
setTimeout(() => {
// ref 底层也是 reactive, 其实是一个对象
name.value = 'cj'
}, 2000);
return { name }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
再来一波关键点的复述, 这个蛮重要的其实, 就先不探究原理, 重在使用轮子哈:
// 1. 通过结构的方式从 Vue 中引入 ref
// 2. 响应变量 = ref(基础类型变量) 即完成包装
// 3. 通过 响应变量.value = 'xxx' 即完成数据变更
// 4. setup 直接 return 响应变量, 模板便可用, 而不用 .value 的方式
const { ref } = Vue
let name = ref("youge")
name.value = 'newValue'
`<div>{{name}}</div>`,
reactive
同 ref 一样的作用 ( ref 基于 reactive) 用来将引用类型数据 ( object, array, function ... ) 等给封装为响应式变量啦.
当然普通类型也是可以的, 就 reactive 的适普性会更强哦. 还是同上的例子, 我们用 reactive 来改写一波:
<!DOCTYPE html>
<html lang="en">
<head>
<title>reactive</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj}}</div>
<div>{{nameObj.name}}</div>
`,
setup (props, context) {
const { reactive } = Vue
// proxy: 将 { name: 'youge' } 封装成 proxy({name: 'youge'}) 的响应式
let nameObj = reactive({ name: 'youge' })
setTimeout(() => {
nameObj.name = 'cj'
}, 2000);
return { nameObj }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
当然数组也是一样的啦.
setup (props, context) {
const { reactive } = Vue
let arr = reactive(['a', 'b', 'c'])
setTimeout(() => {
arr[1] = 'cj'
}, 2000);
return { arr }
}
这样通过 ref 和 reactive 将数据封装为响应式变量,则就可以代替掉原来的 data ( ) 方法啦. 这个会更加通用和方便维护的哦.
readonly
即对响应式变量进行 "只读" 的限定哈.
setup (props, context) {
const { reactive, readonly } = Vue
let arr = reactive(['a', 'b', 'c'])
// 只要用了 readonly 就不能修改啦, 会警告的
const copyArr = readonly(arr)
setTimeout(() => {
arr[1] = 'cj'
copyArr[0] = 666
}, 2000);
return { arr, copyArr }
}
toRefs
它的作用其实就是将咱响应式变量中的值, 通过结构的方式获取到时也是一个响应式变量的值, 可以被模板直接引用, 就不用在模板中写类似 xxxObj.xxx 的写法啦.
一句话: 将 reactive 的数据转为 ref 数据, 也是响应式的啦.
在本例中, proxy( { name: 'youge' }) 中的值给再包装为 { name: proxy( { value: 'youge' })} 这样.
<!DOCTYPE html>
<html lang="en">
<head>
<title>toRefs</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `
<div>{{name}} : {{age}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue
let dataObj = reactive({ name: 'youge', age: 18 })
setTimeout(() => {
dataObj.name = 'cj'
dataObj.age = 26
}, 2000);
// toRefs: proxy({name: 'cj', age: 18}) 转为:
// name: proxy({value: 'cj'}),
// age: proxy({value: 18})
// 然后直接来一波结构赋值
const { name, age } = toRefs(dataObj)
return { name, age }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
小结
- ref 和 reactive 都是对 js 数据类型进行响应式的封装, ref 针对基础类型, reactive 针对引用类型
- ref 原理: proxy: 将 'youge' 封装成 proxy({value: 'youge'}) 的响应式
- reactive 原理: proxy: 将 { name: 'youge' } 封装成 proxy({name: 'youge'}) 的响应式
- toRefs 的作用: 将 proxy({name: 'cj' }) 转化为 { name: proxy({ value: 'cj' })}