2024.05.14

vue 响应式实现(3)

数组的拦截

数组的读取

const obj = {
  a: 1,
  b: 2,
  c: {
    aa: 11,
    bb: 22
  }
}
const arr = [1, obj, 3]
const proxyArr = reactive(arr)

✅ 没问题的情况:

proxyArr[0]
proxyArr.length
for (const key in proxyArr) {
  proxyArr[key]
}
proxyArr.includes(3) // true

❌ 错误的结果:

proxyArr.includes(obj) // false

情况不对劲,分析一下


// utils/enum.js
export const RAW = Symbol('raw') 
// behover/index.js

const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach((method) => {  arrayInstrumentations[method] = function (...args) {    // 1. 正常查找,从代理对象中查找
    const res = Array.prototype[method].apply(this, args)     // 如果找不到的话
    if (res < 0 || res === false) {      // 给代理对象增加一个自定义标识,从原始对象找
      return Array.prototype[method].apply(this[RAW], args)     }    return res  }}) 
function get(target, key) {
  if (key === RAW) {    return target  }  trigger(target, TrackOpTypes.GET, key)
  // 如果是数组的某些方法,需要对数组的方法进行重写
  if (arrayInstrumentations.hasOwnProperty(key) && Array.isArray(target)) {    return arrayInstrumentations[key]   }  const result = Reflect.get(target, key)
  if (isObject(result)) {
    return reactive(result)
  }
  return result
}
  • 之前的代码是将 result 作为一个代理对象 return,将代理对象与原始对象进行对比,所以返回错误结果
  • 为了避免属性名冲突 所以增加一个唯一标识 Symobl,同时改写数组的查询方法

数组的 写入

✅ 没问题的情况:

proxyArr[0] = 10
proxyArr.length = 1

❌ 错误的结果:

proxy[10] = 10 // 没有更改 length 属性

function set(target, key, value) {
  // 是属性值的新增还是修改
  const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD
  const oldValue = target[key]
  const result = Reflect.set(target, key, value)
  // 是否发生改变 决定是否派发更新
  if (hasChanged(oldValue, value)) {
    trigger(target, TriggerOpTypes.SET, key)
  }
  // 如果 length 发生了改变,对 length 派发更新
  if (Array.isArray(target) && oldLength !== target.length) {    if (key !== 'length') {      trigger(target, TriggerOpTypes.SET, 'length')     }  }  return result
}

但是这样的话,在删除的时候不能触发拦截

function set(target, key, value) {
  // 是属性值的新增还是修改
  const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD
  const oldValue = target[key]
  const oldLength = Array.isArray(target) ? target.length : undefined  const result = Reflect.set(target, key, value)
  // 是否发生改变 决定是否派发更新
  if (hasChanged(oldValue, value)) {
    trigger(target, TriggerOpTypes.SET, key)
  }
  // 如果 length 发生了改变,对 length 派发更新
  if (Array.isArray(target) && oldLength !== target.length) {
    if (key !== 'length') {
      trigger(target, TriggerOpTypes.SET, 'length')
    }
    else {      // 进行了删除操作
      for (let i = target.length; i < oldLength; i++) {        trigger(target, TriggerOpTypes.DELETE, i.toString())       }    }  }
  return result
}

proxyArr.push(1)
proxyArr.push(2)

这样的话会一直触发依赖收集 这就需要增加自定义操作,选择是否触发依赖的收集

// effect/track.js

/**
 * 依赖收集的触发暂停
 */
let shouldTrack = trueexport function pauseTracking() {  shouldTrack = false}export function enableTracking() {  shouldTrack = true}export function track(traget, type, key) {  if (!shouldTrack)
    return}
// behover/index.js
import { enableTracking, pauseTracking } from '../effect/track.js';

['push', 'pop', 'shift', 'unshift', 'splice'].forEach((method) => {  arrayInstrumentations[method] = function (...args) {    pauseTracking()     const res = Array.prototype[method].apply(this, args)     enableTracking()     return res  }})