美文网首页
老生常谈响应式之 reactive & ref(下)

老生常谈响应式之 reactive & ref(下)

作者: 羊驼驼驼驼 | 来源:发表于2024-02-20 00:05 被阅读0次

    🌬🌬🌬 前言:由于篇幅有点长,所以分成了两节来分别介绍,上节我们构建了简易版的 reactive,并通过分析实践了解到 reactive 只能用于复杂数据类型,下面我们来分析一下 ref,刚刚开工,各位打工人进入状态了吗🌝🌝🌝,废话不多说,开整🌪️🌪️🌪️

    一、构建基础列表

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

    二、开整

    ref 目标:构建 ref 函数,分析为什么用 .value 去访问数据,ref 是如何分别处理复杂数据类型和简单数据类型的?
    1. 创建 packages/reactivity/src/ref.ts 模块
    import { createDep, Dep } from './dep'
    import { activeEffect, trackEffects, triggerEffects } from './effect'
    import { toReactive } from './reactive'
    
    export interface Ref<T = any> { value: T }
    
    /**
    * ref 函数
    * @param value unknown
    */
    export function ref(value?: unknown) {
      return createRef(value, false)
    }
    
    /**
    * 创建 RefImpl 实例
    * @param rawValue 原始数据
    * @param shallow boolean 形数据,表示《浅层的响应性(即:只有 .value 是响应性的)》
    */
    function createRef(rawValue: unknown, shallow: boolean) {
      if(isRef(rawValue)) {
        return rawValue
      }
      return new RefImpl(rawValue, shallow)
    }
    
    class RefImpl<T> {
      // 私有属性
      private _value: T
      private _rawValue: T
      
      // 共有属性
      public dep?: Dep = undefined
      
      // 标记是否为 ref 类型
      public readonly __v_isRef = true
      
      constructor(value: T, public readonly __v_isShallow: boolean) {
        // https://cn.vuejs.org/api/reactivity-advanced.html#shallowref
        // 如果:__v_isShallow:true,则:value 不会被转化为 reactive 数据。
        // 如果:当前的 value 是复杂数据类型的话,则:会失去响应性
        this._value = __v_isShallow ? true : toReactive(value)
        
        // 原始数据
        this._rawValue = value
      }
      
      /**
      * get:将对象属性绑定到查询该属性时调用的函数
      * 🌰:xxx.value 触发该函数
      */
      get value() {
        trackRefValue(this)
        return this._value
      }
      /**
      * set:更新属性值
      * newValue:新数据 
      * this._rawValue: 原始数据(老数据)
      * hasChanged:对比数据是否发生了变化
      */
      set value(newValue) {
        // 更新数据
        this._rawValue = newValue
        
        // 更新 .value 的值
        this._value = toReactive(newValue)
        
        // 触发依赖
        triggerRefValue(this)
      }
    }
    
    /**
    * 为 ref 的 value 进行依赖收集工作
    */
    export function trackRefValue(ref) {
      if(activeEffect) {
       // 收集所有依赖
       trackEffects(ref.dep || (ref.dep = createDep()))
      }
    }
    
    /**
    * 为 ref 的 value 进行依赖触发工作
    */
    export function triggerRefValue(ref) {
      if(ref.dep) {
        triggerEffects(ref.dep)
      }
    }
    
    /**
    * 指定数据是否为 RefImpl 类型
    */
    export function isRef(r: any) r is Ref {
      return !!(r && r.__v_isRef === true)
    }
    
    packages/reactivity/src/reactive.ts
    /**
    * 将指定数据变为 reactive 类型
    */
    export const toReactive = < T extends unknown>(value: T): T => 
    isObject(value) ? reactive(value as object) : value
    
    packages/shared/src/index.ts
    /**
    * 判断是否为一个对象
    */
    export const isObject = (val: unknown) => val !== null && typeof val === 'object'
    
    
    /**
    * 对比两个数据是否发生了改变
    */
    export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
    
    2. packages/vue/examples/reactivity/ref.html 小试牛刀
    <body>
      <div id="app"></div>
      <script>
        const { ref, effect } = Vue
        
        const obj = ref('张三')
        
        effect(() => {
          document.querySelector('#app').innerText = obj.value
        })
        
        setTimeout(() => {
          obj.value = '李四'
        }, 2000)
      </script>
    </body>
    

    ✨✨✨ 至此,我们的ref函数构建完成,本质上是生成了一个 RefImpl 类型的实例对象,通过 getset 标记处理了 value 函数,并且 ref 是通过 toReactive 对简单数据类型和复杂数据类型做了区分处理。

    • 简单数据类型:触发 set value 属性调用来更新数据,xxx.value 其实是触发 get value 属性调用,所以我们需要通过 .value 进行触发达到类似数据的响应。
    • 复杂数据类型:转化为 reactive 返回的 proxy 实例。value.xxx = 'xxx' 实际触发的是 proxysetter
    都给你讲啦.png

    相关文章

      网友评论

          本文标题:老生常谈响应式之 reactive & ref(下)

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