美文网首页
计算属性 computed

计算属性 computed

作者: 羊驼驼驼驼 | 来源:发表于2024-02-21 21:38 被阅读0次

    🌬🌬🌬 前言: 今天想和大家分享一下计算属性 computed,使用过的童鞋 大致都了解 computed 会缓存,并且在其依赖的响应式数据发生变化的时候进行重新计算更新数据,那为什么 computed 会进行缓存呢,内部又是怎么实现的呢,下面我们来一起一探究竟😊😊😊

    一、构建基础列表

    大致目录结构
    ---| packages
    ---|---| reactivity // 响应性模块
    ---|---|---| src
    ---|---|---|---| index.ts 出口文件
    ---|---|---|---| ref.ts
    ---|---|---|---| reactive.ts
    ---|---|---|---| computed.ts
    ---|---|---|---| effect.ts
    ---|---|---|---| dep.ts
    ---|---|---|---| baseHandlers.ts
    ---|---| shared // 公共方法模块
    ---|---|---| src
    ---|---|---|---| index.ts 出口文件
    ---|---|---|---| shapeFlags.ts
    ---|---| vue // 打包、测试实例、项目整体入口模块
    ---|---|---| dist
    ---|---|---| examples
    ---|---|---| src
    ---|---|---|---| index.ts 出口文件
    

    二、开整

    computed 目标:构建 computed 函数,分析为什么 computed 会进行数据缓存,什么场景适用 computed?
    1. 创建 packages/reactivity/src/computed.ts 模块
    import { isFunction } from '@vue/shared'
    import { Dep } from './dep'
    import { ReactiveEffect } from './effect'
    import { trackRefValue, triggerRefValue } from './ref'
    
    /**
    * 计算属性
    */
    export function computed(getterOrOptions) {
      let getter
      // 判断传入的参数是否为一个函数
      const onlyGetter = isFunction(getterOrOptions)
      
      // 如果:为函数, 则: 赋值给 getter
      if(onlyGetter) {
        getter = getterOrOptions
      }
      // new 一个计算属性
      const cRef = new ComputedRefImpl(getter)
      return cRef as any
    }
    
    /**
    * 计算属性类
    */
    export class ComputedRefImpl<T> {
     public dep?: Dep = undefined // 引用当前 computed 的 effect 的 set
     public readonly effect: ReactiveEffect<T> // effect 对象
     public readonly __v_isRef = true // ref标识
     
     private _value!: T // 缓存值
     
     constructor(getter) {
       this.effect = new ReactiveEffect(getter)
       // effect 实例 被挂载了一个新的属性 computed 为当前的 ComputedRefImpl 的实例
       this.effect.computed = this
     }
     
     /**
     * get value 在ref 中 似曾相识的 操作
     */
     get value() {
       // 依赖收集 (this 本质上就是 ComputedRefImpl 的实例)
       trackRefValue(this)
       // 执行 run 函数
       this._value = this.effect.run()!
       // 返回计算之后的真实值
       return this._value
       
     }
    }
    
    packages/shared/src/index.ts 增加 isFunction 工具类
    /**
    * 判断是否是一个 function
    */
    export function isFunction = (val: unknown): val is Function => typeof val === 'function'
    
    packages/reactivity/src/effect.ts 增加 computed 属性
    /**
    * 响应数据触发依赖时的执行类
    */
    export class ReactiveEffect<T = any> {
      // 存在该属性,则表示当前 effect 为计算属性的 effect
      computed?: ComputedRefImpl<T>
      ...
    }
    

    至此,我们已经通过 get value() 完成了依赖收集,下面我们来完善一下依赖触发操作


    2. packages/reactivity/src/computed.ts 模块 关于脏状态 _dirty 和调度器 scheduler

    在依赖触发模块,我们需要先了解一下脏状态和调度器大致指的是什么,作用是什么,下面是简单的介绍。

    • 脏状态(_dirty)即是数据脏了,是控制缓存的终极幕后,具体怎么控制,下面通过代码描述

      • false 使用缓存值,免于计算
      • true 触发run方法更新数据,重新计算
    • 调度器(scheduler)简单描述是回调函数 fn,在当前computed上下文中,只要 effect 中存在 scheduler,就会执行该函数。

    export class ComputedRefImpl<T> {
      public _dirty = true // 脏状态
      
      constructor(getter) {
        this.effect = new ReactiveEffect(getter, () => {
          // 判断当前脏数据的状态,false:触发依赖
          if(!this._dirty) {
           // 将脏数据改为 true
           this._dirty = true
           // 触发依赖
           triggerRefValue(this)
          }
        })
        this.effect.computed = this
       }
     }
    
     get value() {
       // 收集依赖
       trackRefValue(this)
       // 判断当前脏数据的状态,_dirty: true,表示需要重新执行 run,获取最新数据
       if(this._dirty) {
         this._dirty = false
         // 执行 run 函数
         this._value = this.effect.run()!
       }
       // 返回计算后的数据
       return this._value
     }
    }
    
    packages/reactivity/src/effect.ts 新增调度器相关内容
    export type EffectScheduler = (...args:any[]) => any
    
    
    /**
    * 响应数据触发依赖时的执行类
    */
    export class ReactiveEffect<T = any> {
      // 存在该属性,则表示当前 effect 为计算属性的 effect
      computed?: ComputedRefImpl<T>
      
      constructor(
       public fn: () => T,
       public scheduler: EffectScheduler | null = null
      ){}
      ...
    }
    
    
    /**
    * 触发指定依赖
    */
    export function triggerEffect(effect: ReactiveEffect) {
     // 存在调度器则执行调度函数
     if(effect.scheduler) {
       effect.scheduler()
     }
     // 否则直接执行 run 函数
     else {
      effect.run()
     }
    }
    

    至此,我们的computed的五脏六腑已都实现,下面来小试牛刀一下,看看是否可以正常运行😎

    3. 创建 packages/vue/examples/reactivity/computed.html
    <body>
      <div id="app"></div>
      <script>
        const { reactive, effect, computed } = Vue
        const obj = reactive({
         name: '张三'
        })
        const computedObj = computed(() => {
          return '姓名:' + obj.name
        })
        effect(() => {
          // 校验:多次触发,computed由于缓存原因应该只会计算一次
          document.querySelector('#app').innerHTML = computedObj.value
          document.querySelector('#app').innerHTML = computedObj.value
        })
        
        setTimeout(() => {
          obj.name = '李四'
        }, 2000)
      </script>
    </body>
    

    运行一下以上实例,computed是否真的会只进行一次计算呢?还是会出其他的状况呢,各位看官,下回我们再细细道来~~~~

    学习使我快乐.mmp

    相关文章

      网友评论

          本文标题:计算属性 computed

          本文链接:https://www.haomeiwen.com/subject/icdwadtx.html