Vue3 响应式回顾
- Proxy 对象实现属性监听
- 多层属性嵌套,在访问属性过程中,处理下一级属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组的索引和length 属性
- 可以作为单独的模块使用
核心方法
reactive/effect/track/trigger/ref/toRefs/computed
-
reactive
- 接收一个参数,判断这个参数是否是对象
- 创建一个拦截器对象 handler, 设置get/set/deleteProperty
- 返回Proxy对象
scssexport function reactive(target) { if(!isObject(target)) return target // 处理器或者拦截器对象 handle 对象 const handler = { get (target, key, receiver) { // 收集依赖 track(target, key) console.log('get', key) const result = Reflect.get(target, key, receiver) return convert(result) }, set (target, key, value, receiver ) { const oldValue = Reflect.get(target, key, receiver) let result = true if(oldValue !== value) { result = Reflect.set(target, key, value, receiver) // 触发更新 trigger(target, key) console.log('set', key, value) // set 方法需要返回一个 布尔类型的值,标识着我们的赋值是否成功 } return result }, deleteProperty (target, key ) { const hasKey = hasOwn(target, key) const result = Reflect.deleteProperty(target, key) if(hasKey && result) { // 触发更新 trigger(target, key) console.log('delete', key) } return result } } return new Proxy(target, handler) }
-
effect
- effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行
javascript
let activeEffect = null
// 额外
// effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行
export function effect (callback) {
activeEffect = callback
// 会首先执行一次 callback
callback() // callback 会访问响应式对象的属性,这个过程中去收集依赖,存储callback
// 依赖收集完毕之后 收集依赖的时候,如果有嵌套属性的话 是一个递归的过程 所以需要设置为 null
activeEffect = null
}
- track
- track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
scss
let targetMap = new WeakMap()
// track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
export function track (target, key) {
if(!activeEffect) return
let depsMap = targetMap.get(target)
// 如果 depsMap 没有值,说明没有收集过依赖,没有创建过对应的 Map 对象 需要新建一个
if(!depsMap){
targetMap.set(target, (depsMap = new Map()))
}
// 根据属性查找对应的dep 对象
let dep = depsMap.get(key)
//判断dep是否存在,这个集合是用来存储 属性对应的effect 函数 没有找到 创建一个新的dep集合 添加到 depsMap
if(!dep) {
depsMap.set(key, (dep = new Set()))
}
// 把effect 函数添加到 dep集合中
dep.add(activeEffect)
}
- trigger
scss
export function trigger (target, key) {
// 根据target 去 targetMap 中找到 depsMap
// depsMap 中存储的是属性和对应的 dep 集合 dep集合中存储的就是属性对应的那些 effect 函数
const depsMap = targetMap.get(target)
if(!depsMap) return
const dep = depsMap.get(key)
// dep存在,遍历里面所有的 effect 函数 并进行调用
if(dep){
dep.forEach(effect => {
effect()
})
}
}
-
ref
- 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回
- 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则
- 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则直接返回
- 不管他是什么值,都需要创建一个ref的对象
- 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象
- get中 需要调用track 收集依赖 属性是value属性,因为这个对象只有一个value属性
- set中 首先需要判断 新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象
scssexport function ref (raw) { // 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回 if(isObject(raw) && raw.__V_isRef) { return } // 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则直接返回 let value = convert(raw) // 不管他是什么值,都需要创建一个ref的对象 // 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象 const r = { __V_isRef: true, get value () { // 需要调用track 收集依赖 属性是value属性,因为这个对象只有一个value属性 track(r, 'value') return value }, set value (newValue) { // 首先需要判断 新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象 if(newValue !== value){ raw = newValue value = convert(raw) trigger(r, 'value') } } } }
-
toRefs
- 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
- 创建一个ret 传过来的参数 数组的话 创建一个长度是 length 的数组 否则返回空对象
- 遍历 proxy 对象所有的属性 数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
- 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
csharp
// 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
export function toRefs (proxy) {
// 判断这个参数是不是reactive 创建的对象,如果不是的话,发送警告
// ret 传过来的参数 数组的话 创建一个长度是 length 的数组 否则返回空对象
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
// 遍历 proxy 对象所有的属性 数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
for (const key in proxy) {
ret[key] = toProxyRef(proxy, key) // 把所有转换好的属性 存储到ret 对象中来
}
return ret
}
function toProxyRef(proxy, key) {
// 这个函数里面创建一个对象 最终返回这个对象
const r = {
__V_isRef: true,
get value () {
return proxy[key]
// 这里不需要去收集依赖,这里访问的就是一个响应式对象,当访问他的key属性的时候,proxy内部的get会去收集依赖
},
set value (newValue) {
proxy[key] = newValue
}
}
return r
}
- computed
- computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回
scss
// computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回
export function computed (getter) {
// 返回一个 ref 创建的 具有 value 属性的对象
const result = ref() // ref 不需要传入参数, 如果不传参数的话,默认传入的undefined
// 调用getter 函数,把 getter() 的结果存储到 result.value 中
effect(()=>(result.value = getter()))
// computed 是通过 effect 监听整个函数内部使用的响应式数据的变化,
// 因为在 effect 中 执行getter的时候,访问响应式数据的属性,会去收集依赖,
// 当数据发生变化之后,会去重新执行 effect函数,把getter的结果重新存储到result 中
return result
}
scss
const isObject = val => val !== null && typeof val === 'object';
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target,key)
// 通过call 修改hasOwnProperty 里面的this 指向
// reactive 返回的对象 是一个 proxy 实例 传入的必须是一个对象
export function reactive(target) {
if(!isObject(target)) return target
// 处理器或者拦截器对象 handle 对象
const handler = {
get (target, key, receiver) {
// 收集依赖
track(target, key)
console.log('get', key)
const result = Reflect.get(target, key, receiver)
return convert(result)
},
set (target, key, value, receiver ) {
const oldValue = Reflect.get(target, key, receiver)
let result = true
if(oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
console.log('set', key, value)
// set 方法需要返回一个 布尔类型的值,标识着我们的赋值是否成功
}
return result
},
deleteProperty (target, key ) {
const hasKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if(hasKey && result) {
// 触发更新
trigger(target, key)
console.log('delete', key)
}
return result
}
}
return new Proxy(target, handler)
}
let activeEffect = null
// 额外
// effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行
export function effect (callback) {
activeEffect = callback
// 会首先执行一次 callback
callback() // callback 会访问响应式对象的属性,这个过程中去收集依赖,存储callback
// 依赖收集完毕之后 收集依赖的时候,如果有嵌套属性的话 是一个递归的过程 所以需要设置为 null
activeEffect = null
}
let targetMap = new WeakMap()
// track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
export function track (target, key) {
if(!activeEffect) return
let depsMap = targetMap.get(target)
// 如果 depsMap 没有值,说明没有收集过依赖,没有创建过对应的 Map 对象 需要新建一个
if(!depsMap){
targetMap.set(target, (depsMap = new Map()))
}
// 根据属性查找对应的dep 对象
let dep = depsMap.get(key)
//判断dep是否存在,这个集合是用来存储 属性对应的effect 函数 没有找到 创建一个新的dep集合 添加到 depsMap
if(!dep) {
depsMap.set(key, (dep = new Set()))
}
// 把effect 函数添加到 dep集合中
dep.add(activeEffect)
}
export function trigger (target, key) {
// 根据target 去 targetMap 中找到 depsMap
// depsMap 中存储的是属性和对应的 dep 集合 dep集合中存储的就是属性对应的那些 effect 函数
const depsMap = targetMap.get(target)
if(!depsMap) return
const dep = depsMap.get(key)
// dep存在,遍历里面所有的 effect 函数 并进行调用
if(dep){
dep.forEach(effect => {
effect()
})
}
}
export function ref (raw) {
// 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回
if(isObject(raw) && raw.__V_isRef) {
return
}
// 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则
let value = convert(raw)
// 不管他是什么值,都需要创建一个ref的对象
// 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象
const r = {
__V_isRef: true,
get value () {
// 需要调用track 收集依赖 属性是value属性,因为这个对象只有一个value属性
track(r, 'value')
return value
},
set value (newValue) {
// 首先需要判断 新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象
if(newValue !== value){
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
}
// 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
export function toRefs (proxy) {
// 判断这个参数是不是reactive 创建的对象,如果不是的话,发送警告
// ret 传过来的参数 数组的话 创建一个长度是 length 的数组 否则返回空对象
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
// 遍历 proxy 对象所有的属性 数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
for (const key in proxy) {
ret[key] = toProxyRef(proxy, key) // 把所有转换好的属性 存储到ret 对象中来
}
return ret
}
function toProxyRef(proxy, key) {
// 这个函数里面创建一个对象 最终返回这个对象
const r = {
__V_isRef: true,
get value () {
return proxy[key]
// 这里不需要去收集依赖,这里访问的就是一个响应式对象,当访问他的key属性的时候,proxy内部的get会去收集依赖
},
set value (newValue) {
proxy[key] = newValue
}
}
return r
}
// computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回
export function computed (getter) {
// 返回一个 ref 创建的 具有 value 属性的对象
const result = ref() // ref 不需要传入参数, 如果不传参数的话,默认传入的undefined
// 调用getter 函数,把 getter() 的结果存储到 result.value 中
effect(()=>(result.value = getter()))
// computed 是通过 effect 监听整个函数内部使用的响应式数据的变化,
// 因为在 effect 中 执行getter的时候,访问响应式数据的属性,会去收集依赖,
// 当数据发生变化之后,会去重新执行 effect函数,把getter的结果重新存储到result 中
return result
}