侧边栏壁纸
博主头像
MicroMatrix博主等级

曲则全,枉则直,洼则盈,敝则新,少则得,多则惑。是以圣人抱一为天下式。不自见,故明;不自是,故彰;不自伐,故有功;不自矜,故长。夫唯不争,故天下莫能与之争。古之所谓“曲则全”者,岂虚言哉!诚全而归之。

  • 累计撰写 80 篇文章
  • 累计创建 21 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

vue3源码学习-7-computed的实现

蜗牛
2022-06-18 / 0 评论 / 0 点赞 / 7 阅读 / 6352 字 / 正在检测是否收录...

前言

回顾上期的内容,编写了effect中的调度器,主要修改了effect.ts文件。在预览之前的代码的时候会发现一些优化的地方。

在vue代码的需求编辑中,会遇到这样一个例子。例如一个人的姓名分为姓和名,那么我希望在页面上打印出这个人的姓+名,而且在姓或者名改变的时候,页面渲染也会改变。那么就用到了vue的computed来进行操作。旧版的vue2中computed是基于watcher实现的。vue3则是基于effect来实现。另外vue3中的computed写法叫组合式API,而vue2是拿data中的属性来编写computed中的属性,这种叫选项式API(option)。具体的vue3写法如下

const { effect, reactive, computed } = VueReactivity
let target = { firstName: '张', lastName: '四' }
const state = reactive(target)
let app = document.getElementById('app')
const fullName = computed(() => {
  console.log('runner')
  return state.firstName + state.lastName
})
effect(() => {
  return (app.innerHTML = fullName.value)
})

setTimeout(() => {
  state.firstName = '王'
}, 1000)

也可以写成如下的方式

const fullName = computed({
    get(){
      console.log('runner')
      return state.firstName + state.lastName
    },
    set(){

    }
})
fullName.value;
fullName.value;
fullName.value;

并且在多次访问value的时候,如果值未改变就不触发运行,也就是说带有一个缓存的功能。

baseHandle功能优化

在给属性包裹一层代理的时候,如果对象的属性还是一个对象之类的属性,那么返回的不应该只是这个值,而应该是这个对象代理之后的值。

// baseHandle.ts get访问器中判断是否是对象,如果是就再代理一层
let res = Reflect.get(target, key, recevier)
if (isObject(res)) {
  return reactive(res) // 深度代理实现
}
return res

编写computed功能

对着上面的官方用法,知道如果只有一个函数的话,那就是这个函数默认为get,还可以有一个对象的写法,那就是将用户的set和get赋值上去。
另外还effect中渲染了computed抛出的值,然后这个值改变了,也触发了effect的run,让他重新渲染,这说明computed是记录了effect的依赖的。
上文说过computed是基于effect,实际上它和effect基本相等。那么就出现了effect包裹了effect这种写法。

48a2d11a721ffa60acaac.png

也就是这样传递着改变的信息,来触发页面渲染。

第一步创建computed.ts基础

判断传递过来的式函数还是对象,然后就获取传递的get和set

export const computed = (getterOrOptions) => {
  let onlyGetter = isFunction(getterOrOptions)
  let getter: Function, setter: Function

  if (onlyGetter) {
    getter = getterOrOptions
    setter = () => {
      console.warn('no set')
    }
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  return new ComputedRefImpl(getter, setter)// 将拿到的getter和setter
}

编写ComputedRefImpl类

这个类主要的操作是
第一个赃值检测,就是value值多次get,然后值还没改变。
第二个是搜集effect,这样值更新通知对应的effect进行用户的函数回调
第三个如果不是赃值,那就运行调度函数,就是用户的操作函数。

class ComputedRefImpl {
  public effect: ReactiveEffect
  public _dirty: boolean = true // ,默认应该取值的时候进行计算
  public __v_isReadonly: boolean = true
  public __v_isRef: boolean = true
  public _value: any
  public dep = new Set()
  constructor(public getter: Function, public setter: Function) {
    // 我们将用户的getter放到effect中,这里面的属性会被这个effect收集起来
    this.effect = new ReactiveEffect(getter, () => {
      //稍后依赖的属性变化会执行这个调度函数

      if (!this._dirty) {
        this._dirty = true

        // 实现一个触发更新
        triggerEffect(this.dep)
      }
    })
  }

  //类的属性访问器, 底层就是Obeject.defineProperty
  get value() {
    // 做依赖收集
    trackEffect(this.dep)
    if (this._dirty) {
      this._dirty = false // 第一次取过之后的的时候才设置为false
      // 说明这个值是赃值
      this._value = this.effect.run()
    }

    return this._value
  }

  set value(newValue) {
    this.setter(newValue)
  }
}

上面代码中的triggerEffect和trackEffect是对之前的effect代码做了一个函数功能分离。

// 这不就是之前代码中的依赖收集和函数回调操作吗
export function trackEffect(dep: any) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect) //一个属性多次依赖同一个effect那么去重
    if (shouldTrack) {
      dep.add(activeEffect)
      activeEffect.deps.push(dep) // 让deps记录住对应的dep,稍后在清理的地方用到
    }
  }
}

export function triggerEffect(effects) {
  effects = new Set(effects)
  effects.forEach((effect) => {
    if (activeEffect !== effect) {
      if (effect.schedule) {
        effect.schedule() // 用户传入schedule的时候,就调用回调
      } else {
        effect.run() // 否则就刷新
      }
    }
    // 如果这里直接就写effect.run(),那么会遇到这种情况,在模版中赋值,那么也会触发这个,
    // 然后又通过了依赖收集的时候,运行它的第一次run()。就会导致循环调用,爆栈,
    //所以这里需要加一个判断是否是当前的effect,如果是的话,就忽略这一次的赋值触发的run();
    //注意目前的代码是不支持异步的
  })
}

结尾

经过上面的代码编写之后就能得到一个自己的computed函数,可以试验下,发现能得到相应的效果

git:[@github/MicroMatrixOrg/vue3-plan/tree/add-computed]
0

评论区