vue2 的响应式原理通过 defineProperty
实现,vue3 通过 Proxy
实现。
但这里不讨论原理,讨论的是 vue3 新增 API 的使用和注意点。因为 vue3 将响应式 API 暴露了出来,是一套独立的数据响应式系统,和组件没有关系,这样也就能够实现单一状态管理。
data(){}
中,最终会被注入到组件实例上。setup()
返回。vue3 中的响应式数据,有2种格式:
reactive
返回的 Proxy 对象,可直接访问属性。ref
和 computed
返回的 Ref 对象,需要通过 .value
访问属性。官网参考
先说几点重要的:
reactive()
会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive()
会返回其本身(下面有例子说明)。readonly
的唯一区别是返回的代理对象是只读的,修改属性会报错。看下面的例子:
import { reactive, readonly } from 'vue'
const origin = { a: 1, b: 2 }
const state = reactive(origin)
console.log(state === reactive(origin)) // true 相同的代理对象
console.log(state === reactive(state)) // true 返回自身
const stateOnly = readonly(state)
console.log(stateOnly === state) // false
state.a++
console.log(stateOnly.a) // 2
虽然 stateOnly
是只读的。但因为代理的是 state
,所以当 state
被修改时 stateOnly
也会被修改。
另外,stateOnly
代理–> state
代理–> { a: 1, b: 2 }
,所以 stateOnly !== state
简单应用:
import { readonly, reactive } from 'vue'
/**
* 返回1个对象和2个方法,
* 对象是是响应式的,不允许直接修改。只能通过提供的方法修改指定属性。
* @returns Object
*/
function useUser() {
const userOrigin = reactive({})
const user = readonly(userOrigin)
const setUserName = (name) => {
userOrigin.name = name
}
const setUserAge = (age) => {
userOrigin.age = age
}
return {
user,
setUserName,
setUserAge
}
}
const { user, setUserName, setUserAge } = useUser()
console.log(user)
setUserName('下雪天的夏风')
setUserAge(18)
console.log(user)
官网参考
可以代理任何数据,因为它是把这些数据都放到了一个对象的 .value
属性上,并返回这个对象。
注意,这个对象不是 Proxy 对象,而是 Ref 对象。区别在上面已经说明了。
如果 ref()
的参数是Proxy 对象,则直接将它放到 .value
属性上。
const state = reactive({ a: 1, b: 2 })
const stateRef = ref(state)
console.log(stateRef.value === state) // true
使用 .value
的原因是,无法直接检测到普通变量的访问或修改。所以通过 getter
和 setter
方法来拦截对象属性的 get 和 set 操作。
ref 的大致原理如下:ref 原理参考
const myRef = {
_value: 0,
get value() {
// 触发依赖收集,重新渲染
track()
return this._value
},
set value(newValue) {
this._value = newValue
// 通知依赖它的对象更新
trigger()
}
}
官网参考
这个函数会立即运行,同时响应式地追踪其依赖,并在依赖更改时重新执行。
也就是说,可以同时监听多个。只要该函数中使用到的响应式数据被修改了,这个函数就会重新执行。
在 vue3 中,大多数情况下监听数据使用 watchEffect()
就够了。
官网参考
watch
的使用方式和 vue2 中差别不大,和 watchEffect
的区别是:
watch
默认是懒侦听,即仅在侦听源发生变化时才执行回调函数,可以指定配置项 { immediate: true }
来立即执行一次。注意点:
watch
不能监听普通数据,监听来源只能是下面4种:
看下面的例子:
import { reactive, watch } from 'vue'
const state = reactive({ a: 1, b: 2 })
watch(state.a, () => {
console.log('变化了')
})
state.a++ // watch 函数并不会执行。
state.a
是普通的数据,不是响应式的,所以无法监听。
import { reactive, watch } from 'vue'
const state = reactive({ a: 1, b: 2 })
watch(() => state.a, () => {
console.log('变化了')
})
state.a++ // 变化了
如果 watch
的第一个参数是函数,则会调用该函数来收集依赖。也就是说,用到的 state.a
中的 state 是响应式数据,所以能被监听到了。
但如果是监听的是 const count = ref(0)
,就可以直接监听 count
而不使用函数的方式返回。因为传递的是对象。所以可以监听到 value
的值。
API | 含义 |
---|---|
isProxy |
判断数据是否reactive 或readonly 创建的 |
isReactive |
判断数据是否reactive 创建的 |
isReadonly |
判断数据是否readonly 创建的 |
isRef |
判断数据是否是ref 对象 |
更多参考工具函数
1,unref()
如果参数是 ref,则返回内部值,否则返回参数本身。
val = isRef(val) ? val.value : val
2,toRefs
这个比较重要。
toRefs
会将一个响应式对象的所有属性转换为 ref
格式,然后包装到一个普通对象中返回。
看下面的例子:
import { reactive, toRefs } from 'vue'
const state = reactive({ a: 1, b: 2 })
const stateRef = toRefs(state)
console.log(stateRef) // {a: refObj, b:refObj},所以将 stateRef 展开也是响应式的。
console.log(stateRef.a.value) // 1
应用:
当使用方法返回的是代理对象时,可以解构而不失去响应式。
import { reactive, toRefs } from "vue";
// composition function
function usePos(){
const pos = reactive({x:0, y:0});
return pos;
}
setup(){
const {x, y} = usePos(); // lost reactivity
const {x, y} = toRefs(usePos()); // reactivity
}
以上。