最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue3的响应数据简易实现-Composition API(reactive, ref, toRef...)

    正文概述 掘金(小桂summer)   2021-02-24   889

    前言

    • vue3的reactivity源码地址
    • reactivity 响应式系统
    • 实现的composition API有:
    • reactive, shallowReactive, shallowReadonly, readonly
    • ref, shallowRef
    • toRef, toRefs
    • effect:
    • reactivity内部方法, 不暴露在外面
    • 当数据变化时去执行effect函数

    shared

    • vue3的shared源码地址
    • shared: 多个包之间共享的内容
    // 是不是个对象
    export const isObject = (value) => typeof value == 'object' && value !== null
    // 合并对象
    export const extend = Object.assign
    // 是不是数组
    export const isArray = Array.isArray
    // 是不是函数
    export const isFunction = (value) => typeof value == 'function'
    // 是不是数字
    export const isNumber = (value) => typeof value == 'number'
    // 是不是字符
    export const isString = (value) => typeof value === 'string'
    // 是不是正整数
    export const isIntegerKey = (key) => parseInt(key) + '' === key
    
    
    // 是不是自己的属性
    let hasOwnpRroperty = Object.prototype.hasOwnProperty
    export const hasOwn = (target, key) => hasOwnpRroperty.call(target, key)
    
    // 是不是同一个值
    export const hasChanged = (oldValue,value) => oldValue !== value
    
    

    reactive

    • reactive: 对数据进行Proxy代理(重点)
    • shallowReactive: 对数据进行浅代理(就是只关注第一层)
    • shallowReadonly: 对数据进行浅代理, 并且只能读取, 无法修改(不收集track)
    • readonly: 只能读取, 无法修改(不收集track)
    • 大致流程(以下面例子为准):
    • 执行effect函数
    • state.arr.length state.son.name state.arr[3]会走proxy的get
    • 通过track收集当前的effect函数
    • 当数据发生变化时会走proxy的set
    • 通过trigger会去再次执行当前的effect函数
    • 只有在effect传递的函数中有的变量 才会去收集effect函数(相当于vue2中的watcher)

    示例

    <div id="app"></div>
    <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
    <script>
      let { effect, reactive } = VueReactivity
      let state = reactive({
        name: 'tom',
        age: 38,
        son: {name: 'Bob', age: 18},
        arr: [1,2,3,4,5]
      })
    
      effect(() => {
          // app.innerHTML = state.name + state.name
          app.innerHTML = `${state.arr.length}-${state.son.name}-${state.arr[3]}`
      })
    
      setTimeout(() => {
        // state.arr.push(100)
        state.son.name = 'Pretty'
        state.arr.length = 1
      }, 2000)
    
    </script>
    
    +---------------------+    +----------------------+
    |                     |    |                      |
    |       5-Bob-4       +--->|  1-Pretty-undefined  +
    |                     |    |                      |
    +---------------------+    +----------------------+
    

    reactive.ts

    import { isObject } from "@vue/shared/src"
    import { mutableHandlers, shallowReactiveHandlers, readonlyHandlers, shallowReadonlyHandlers } from './baseHandlers'
    
    /**
     * @description 拦截数据
     */
    export function reactive(target) {
      return createReactiveObject(target, false, mutableHandlers)
    }
    
    /**
     * @description 拦截第一层数据(浅响应)
     */
    export function shallowReactive(target) {
      return createReactiveObject(target, false, shallowReactiveHandlers)
    }
    
    /**
     * @description 只读数据
     */
    export function readonly(target) {
      return createReactiveObject(target, true, readonlyHandlers)
    }
    
    /**
     * @description 浅的只读数据
     */
    export function shallowReadonly(target){
      return createReactiveObject(target, true, shallowReadonlyHandlers)
    }
    
    // 创建WeakMap, 响应还是只读
    // 方便查找, 存储的key必须是对象
    // 会自动垃圾回收, 不会造成内存泄漏
    const reactiveMap = new WeakMap()
    const readonlyMap = new WeakMap()
    
    /**
     * @description 创建数据代理
     * @param target        要拦截的目标(数组或者对象)
     * @param isReadonly    是不是只读
     * @param baseHandlers  Proxy第二个参数对象
     */
    export function createReactiveObject(target, isReadonly, baseHandlers) {
      if (!isObject) { return target }
    
      const proxyMap = isReadonly ? readonlyMap : reactiveMap
      const existProxy = proxyMap.get(target)
      if (existProxy) { return existProxy }
    
      const proxy = new Proxy(target, baseHandlers)
      proxyMap.set(target, proxy)
    
      return proxy
    }
    
    

    operators.ts

    /**
     * @description 收集effect时 枚举
     */
    export const enum TrackOpTypes {
      GET
    }
    
    /**
     * @description 发布effect时 枚举
     */
    export const enum TriggerOrTypes {
      ADD,
      SET
    }
    
    

    baseHandlers.ts

    import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared/src"
    import { track, trigger } from "./effect"
    import { TrackOpTypes, TriggerOrTypes } from "./operators"
    import { reactive, readonly } from "./reactive"
    
    /**
     * @description 创建get
     * @param isReadonly  是不是只读
     * @param shallow     是不是浅拦截
     */
    function createGetter(isReadonly = false, shallow = false) {
      return function get(target, key, receiver) {
        // Reflect.get 具备返回值(target[key])
        // 目标值类型不是Object 则抛出一个TypeError
        const res = Reflect.get(target, key, receiver)
    
        if (!isReadonly) {
          // 收集effect
          track(target, TrackOpTypes.GET, key)
          
        }
    
        if (shallow) { return res }
    
        if (isObject(res)) {
          return isReadonly ? readonly(res) : reactive(res)
        }
    
        return res
      }
    }
    
    /**
     * @description 创建set 只针对不是只读的数据
     * @param shallow 是不是浅拦截
     */
    function createSetter(shallow = false) {
      return function set(target, key, value, receiver) {
        const oldValue = target[key]
        
        let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key)
        
        // Reflect.set 具备返回值(boolean)
        const result = Reflect.set(target, key, value, receiver)
    
        if (!hadKey) {
          // 新增
          trigger(target, TriggerOrTypes.ADD, key, value)
        } else {
          // 修改
          trigger(target, TriggerOrTypes.SET, key, value, oldValue)
        }
    
        return result
      }
    }
    
    const get = createGetter()
    const shallowGet = createGetter(false, true)
    const readonlyGet = createGetter(true)
    const showllowReadonlyGet = createGetter(true, true)
    
    const set = createSetter()
    const shallowSet = createSetter(true)
    
    export const mutableHandlers = {
      get,
      set
    }
    
    export const shallowReactiveHandlers = {
      get: shallowGet,
      set: shallowSet
    }
    
    // 只读 set时发出警告
    let readonlyObj = {
      set: (target, key) => {
          console.warn(`set on key ${key} falied, 当前是只读属性`)
      }
    }
    
    export const readonlyHandlers = extend({
      get: readonlyGet,
    }, readonlyObj)
    
    export const shallowReadonlyHandlers = extend({
      get: showllowReadonlyGet,
    }, readonlyObj)
    
    

    effect.ts

    import { isArray, isIntegerKey } from "@vue/shared/src"
    import { TriggerOrTypes } from "./operators"
    
    /**
     * @description uid           effect唯一标识
     * @description activeEffect  指向当前的effect
     * @description effectStack   存储effect的栈, 结束一个去除一个
     */
    let uid = 0
    let activeEffect;
    const effectStack = []
    
    function createReactiveEffect(fn, options) {
      const effect = function reactiveEffect() {
        // 判断是为了防止 如: fn里 -> state.age++ 不停的更新 造成死循环 
        if (!effectStack.includes(effect)) {
          try {
            effectStack.push(effect)
            activeEffect = effect
            return fn()
          } finally {
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
          }
    
        }
      }
    
      effect.id = uid++
      effect._isEffect = true   // 标识是响应式的effect
      effect.raw = fn           // 传入的原始函数
      effect.options = options
    
      return effect
    }
    
    /**
     * @description   数据在effect传的函数中调用 当数据发生变化trigger会再次发布
     * @param fn      传的函数
     * @param options 选项 如是不是懒的(computed 默认不执行)
     */
    export function effect(fn, options: any = {}) {
      const effect = createReactiveEffect(fn, options)
      if (!options.lazy) {
        effect()
      }
    
      return effect
    }
    
    
    /**
     * @description effect存储的变量 track收集的effect
     * @description targetMap 结构
     * {name: 'xxx', age: 000} => {
     *    name => [effect effect],
     *    age  => [effect effect]
     * },
     * [1,2,3] => {
     *    1 => [effect, efffect]
     * }
     */
    const targetMap = new WeakMap()
    
    /**
     * @description 让某个对象中的属性 收集当前对应的effect函数
     * @description 只有在执行effect时, 并且在effect里的变量才会去收集
     */
    export function track(target, type, key) {
      // activeEffect 指向当前执行的effect
      if (activeEffect === undefined) { return }
      
      let depsMap = targetMap.get(target)
      if (!depsMap) {
        targetMap.set(target, (depsMap = new Map))
      }
      
      let dep = depsMap.get(key)
      if (!dep) {
        depsMap.set(key, (dep = new Set))
      }
      
      if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
      }
    
    }
    
    
    /**
     * @description 寻找属性对应的effect 让其执行(这里只有数组和对象)
     * @param target    目标
     * @param type      类型 新增 或者 修改
     * @param key       key
     * @param newValue  新值
     * @param oldValue  老值
     */
    export function trigger(target, type, key?, newValue?, oldValue?) {
    
      const depsMap = targetMap.get(target)
      if (!depsMap) return
      
      // 要发布的effect去重
      // 将所有的要执行的effect 全部存到一个新的集合中 最终一起执行
      const effects = new Set()
      const add = (effectsToAdd) => {
        if (effectsToAdd) {
          effectsToAdd.forEach(effect => effects.add(effect))
        }
      }
    
      // 数组 修改长度 traget.length = newValue
      // 如 [1,2,3,4,5] => [1,2,3,4,5].length = 1
      if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
          if (key === 'length' || key > newValue) {
            add(dep)
          }
        })
      } else {
        // 可能是对象
        // 这里肯定是修改 不能是新增
        // 如果是新增 depsMap.get(key) -> undefined
        if (key !== undefined) {
          add(depsMap.get(key))
        }
    
        // 如果修改数组中的某个索引
        // 如 arr=[1,2,3] -> arr[100]=1
        switch (type) {
          case TriggerOrTypes.ADD:
            if (isArray(target) && isIntegerKey(key)) {
              add(depsMap.get('length'))
            }
            break;
        }
      }
    
      // 发布
      effects.forEach((effect: any) => effect())
    
    }
    

    ref 和 toRef

    ref

    • ref大部分情况下 只针对单一变量let name = ref('tom') 其实内部用的是Object.defineProperty(这里用的是class类属性访问器get和set)
    • 如果let state = ref({})是对象, 将会去用reactive({})API
    • shallowRef 只做第一层的Object.defineProperty, 不会reactive({})

    示例

    <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
    <div id="app"></div>
    <script>
      const { ref, shallowRef, effect } = VueReactivity
      let name = ref('Tom')
      // let state = ref({a: 'a1', b: 'b1'})
    
      effect(() => {
          app.innerHTML = name.value
          // app.innerHTML = state.value.a
      })
    
      setTimeout(() => {
          name.value = 'Bob'
          // state.value.a = 'a2'
      }, 1000)
    </script>
    
    +---------------------+    +----------------------+
    |                     |    |                      |
    |         Tom         +--->|          Bob         +
    |                     |    |                      |
    +---------------------+    +----------------------+
    

    toRef

    • 调用proxy的变量, 做了一层代理
    • toRefs只是循环调用toRef, 做了一层代理

    示例

    <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
    <div id="app"></div>
    <script>
      const { effect, reactive, toRef, toRefs } = VueReactivity
      let proxy = reactive({name:'Tom', age: 100})
      // let r1 = toRef(proxy, 'name')
      // let r2 = toRef(proxy, 'age')
      const { name, age } = toRefs(proxy)
      effect(()=>{
          app.innerHTML = name.value + '-' + age.value
      })
      setTimeout(() => {
          proxy.name = 'Bob'
      }, 2000)
    </script>
    
    +---------------------+    +----------------------+
    |                     |    |                      |
    |       Tom-100       +--->|        Bob-100       +
    |                     |    |                      |
    +---------------------+    +----------------------+
    

    ref.ts

    import { hasChanged, isArray, isObject } from "@vue/shared/src"
    import { track, trigger } from "./effect"
    import { TrackOpTypes, TriggerOrTypes } from "./operators"
    import { reactive } from "./reactive"
    
    /**
     * @description ref shallowRef
     * @description reactive内部采用proxy ref中内部使用的是defineProperty
     */
    export function ref(value) {
      return createRef(value)
    }
    
    export function shallowRef(value) {
      return createRef(value, true)
    }
    
    // 传进来的值 如果是对象 用reactive代理
    const convert = (val) => isObject(val) ? reactive(val) : val
    
    class RefImpl {
      public _value;
      public __v_isRef = true // 表示是一个ref属性
      constructor(public rawValue, public shallow) {
        this._value = shallow ? rawValue : convert(rawValue)
      }
    
      get value() {
        // 收集effect
        track(this, TrackOpTypes.GET, 'value')
        return this._value
      }
    
      set value(newValue) {
        if (hasChanged(newValue, this.rawValue)) {
          this.rawValue = newValue
          this._value = this.shallow ? newValue : convert(newValue)
          // 发布 effect
          trigger(this, TriggerOrTypes.SET, 'value', newValue)
        }
      }
    
    }
    
    function createRef(rawValue, shallow = false) {
      return new RefImpl(rawValue, shallow)
    }
    
    /**
     * @description toRef toRefs
     * @description 将一个对象转换成ref类型 就是做了一层代理
     */
    class ObjectRefImpl {
      public __v_isRef = true
      constructor(public target, public key) {}
    
      // 如果原对象是响应式的就会track依赖收集
      get value(){
        return this.target[this.key]
      }
    
      // 如果原来对象是响应式的 就会trigger触发更新
      set value(newValue){
        this.target[this.key] = newValue
      }
    }
    
    export function toRef(target, key) {
      return new ObjectRefImpl(target, key)
    }
    
    export function toRefs(object) {
      const ret = isArray(object) ? new Array(object.length) : {}
      for (const key in object) {
        ret[key] = toRef(object, key)
      }
    
      return ret
    }
    
    


    起源地下载网 » Vue3的响应数据简易实现-Composition API(reactive, ref, toRef...)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元