#响应式基础
可以使用 ref()函数声明一个响应式状态。
可以使用 reactive()函数使一个对象具有响应性,使用 Proxy 拦截对响应式对象所有属性的访问和修改。当 ref 函数包装一个对象时,内部会调用 reactive 函数
#为什么要使用 ref
js 中不能检测基本类型变量的访问和修改,但是可以拦截对象的 get 和 set
因此可以把 ref 视作一个把变量包装为同名对象的函数,.value 是原始值,可以追踪.value 的 get set。
并且,当一个组件初次渲染时,Vue 会自动追踪渲染时使用的每一个 ref,当某个 ref 被修改时,Vue 会重新渲染追踪他的组件。
#reactive proxy
reactive 会返回一个 Proxy 对象,和原始对象并不相等,更改原始对象不会影响 Vue Proxy
- 修改 origin obj,视图不更新
- 修改代理对象,视图更新,原始对象更新
改一次origin,再改proxy,第一次修改也会被应用,不会丢失
vue<script setup lang="ts"> import { reactive } from 'vue' const obj = { a: 1, } const _obj = reactive(obj) </script> <template> <div class="m-2 w-32">origin:{{ obj.a }}</div> <div class="m-2 w-32">proxy: {{ _obj.a }}</div> <button className="btn btn-warning" @click="obj.a += 1">add origin</button> <button className="btn btn-warning" @click="_obj.a += 1">add proxy</button> <button class="btn btn-success" @click="console.log(obj)">click</button> </template>
#reactive 的局限性
- 不能用于基本类型
- 直接替换 reactive 的返回值,响应式会丢失
- 解构的值不具有响应性
下例中,使用 reactive,17 行视图不更新,响应丢失
使用 ref,17 行视图更新,响应保持,因为 obj 自身的指向没有被修改,而是内部的 value 被修改了,所以可以被 vue 追踪到,而 reactive 则不行。
vue<script lang="ts" setup> import { reactive, ref } from 'vue' // let obj = reactive({ count: 1 }) let obj = ref({ count: 1 }) const btnClick = () => { // obj = reactive({ count: 2 }) obj.value = { count: 2 } console.log(obj) } </script> <template> <div class="wrapper"> <h2>{{ obj.count }}</h2> <button className="btn btn-warning" @click="btnClick">btnClick</button> </div> </template>
#怎么样更新整个 reactive 响应式对象
Vue 官方文档告诉我们:
不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失
我们一般更新 reactive 的属性是一个个更新的,但是如果有大量属性要更新会很麻烦,替换整个 reactive 的方法是使用 Object.assign,把新对象合并到旧的响应式对象里面,这样响应式才不会丢失
vue<script setup> import { reactive } from 'vue' let obj = reactive({ msg: "hello world" }) function msgChange() { // obj = reactive({ msg: "你好世界" }) obj = Object.assign(obj, { msg: "你好世界" }) // 正确的方式 } </script> <template> <h1>{{ obj.msg }}</h1> <button @click="msgChange">msgChange</button> </template>
#ref、reactive 其他
ref
- 只有顶层的 ref,会被模板自动解包
- 嵌套的 ref 可以使用解构,解构为顶层 ref
reactive
- reactive 对象中可以放 ref 作为属性,不需要手动添加.value
- 给 reactive 对象的某个 ref 属性赋值新的 ref,旧 ref 会被新 ref 替换
- reactive 数组不包括上述两个特性
#dom 更新和数据更新
响应式数据更新是同步(立即发生)的
但是视图的更新不是同步的,而是一段时间发生一次
Vue 把这段时间称作
next tick
更新周期,一段周期只会发生一次视图更新如果希望等待视图更新,可以使用 nextTick 函数
jsawait nextTick()
#shallowRef
默认情况下 ref 会调用 reactive 处理引用类型,当修改深层属性时会被追踪
如果不希望递归追踪内部属性,可以使用 shallowRef
shallowRef 包裹的引用类型,只有当.value 自身发生改变才会被追踪。
可以优化性能
jsconst state = shallowRef({ count: 1 }) // 不会触发更改 state.value.count = 2 // 会触发更改 state.value = { count: 2 }