最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入理解Vue 3.0 Reactive

    正文概述 掘金(式溪)   2020-12-20   304

    因为Vue 3.0刚推出,本身打算只是简单了解下,但了解Vue 3.0的新特性后,如monorepo代码管理,源码偏向函数性编程,还有Composition Api设计,拍手叫绝,决定认真学习下,刚好前段时间拉勾教育搞特惠,用便宜的价钱买了Vue 3.0源码解析,结合最近在拉勾前端训练营学到手撕Reactive Api,整理一篇Vue 3.0响应式原理笔记。

    代理

    深入了解Vue 3.0前,必须先了解Javascript的代理 (Proxy),因为Vue 3.0的响应式数据是基于代理实现的。在Vue 2.0时代,响应式数据是基于 Object.defineProperty实现,可以用下图概括它的实现:

    深入理解Vue 3.0 Reactive

    Object.defineProperty 的好处是兼容性好,可以操控对象属性的细节,然而也有相应的问题出现,一是直接修改对象本身,二是在数据劫持上性能劣于Proxy,三是由于是针对属性,所以如果属性上有任何变动,无法处理,四是无法处理数组。

    上述的问题在Vue 3.0中通过 Proxy解决。

    代理模式

    代理不是JS独有的对象,JS的Proxy是基于代理模式所设计,所以了解代理模式,有助我们更好理解代理。 代理模式指的是间接操控对象的设计,用一张图概括说明:

    深入理解Vue 3.0 Reactive

    我们平时在桌面上的捷径其实就是代理模式的实现,用户不是直接打开应用软件,而是通过桌面的捷径打开。

    Proxy

    Javascript的Proxy就是基于上述的代理模式设计的,可以通过Proxy间接操控对象或数组。

    下面用代码示例简单介绍它的用法:

    const target = {
    foo: 'bar'
    };
    
    const handler = {
    get() {
    return 'handler override';
    }
    };
    const proxy = new Proxy(target, handler);
    console.log (proxy.foo)  // handler override 
    console.log (target.foo) // bar
    

    Proxy接收两个参数,第一个是需要代理的对象,第二个是捕获器 (handler),它是一个对象,里面有Proxy指定的捕获方法,如 get, set, delete等,用于操作代理对象时,触发不同的捕获器。注意,与Object.defineProperty不同的是,它是以整个对象为单位,而不是属性。用户可以通过操作 Proxy创建的实例去间接操作对象本身。 上述的示例中,添加get处理器,重载对象的获取。

    get接收3个参数: trapTarget, property, receiver。trapTarget是捕获对象,property是属性,receiver是代理对象本身。有了这些参数,就可以重建被捕获方法的原始行为:

    const target = {
    foo: 'bar'
    };
    
    const handler = {
    get(trapTarget, property, receiver) {
      console.log (receiver === proxy) // true
    return trapTarget[property];
    }
    };
    
    const proxy = new Proxy(target, handler);
    console.log(proxy.foo); // true  bar
    console.log(target.foo); // bar
    

    处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。因此,使用反射API 也可以 像下面这样定义出空代理对象:

    const target = {
      foo: 'bar'
    };
    
    const handler = {
      get() {
        return Reflect.get(...arguments);
      }
    };
    
    const proxy = new Proxy(target, handler);
    console.log(proxy.foo); // bar
    console.log(target.foo); // bar
    

    Proxy的简单介绍到此为止,详细可查阅 MSDN或 Javascript高级程序设计 (第四版)。

    Reactive 简单实现

    Vue 2.0 的响应式数据创建是 在"黑盒" 进行,即创建Vue实例时,根据传入的参数来创建响应式数据。而在 Vue 3.0 则是可以显式创建响应式数据:

    <template>
      <div>
        <p>{{ state.msg }}</p>
        <button @click="random">Random msg</button>
      </div>
    </template>
    <script>
      import { reactive } from 'vue'
      export default {
        setup() {
          const state = reactive({
            msg: 'msg reactive'
          })
    
          const random = function() {
            state.msg = Math.random()
          }
    
          return {
            random,
            state
          }
        }
      }
    </script>
    

    上述的例子导入reactive函数,通过reactive显式创建响应式数据。

    在阅读reactive源码前,先简单实现一个reactive来理解它。

    先看下官方文档reactive是干什麽的:

    简单来说,就是reactive接收一个对象,返回一个响应式副本,它是一个Proxy实例,里面的属性,包括嵌套对象都是响应式的。

    根据上述得知,函数首先需要判定参数是否为对象,返回一个Proxy实例:

    // 因为null的typeof也是object,所以要另外增加对它的判定
    const isObject = val => val !== null && typeof val === 'object'
    
    function reactive (target) {
      if (!isObject (target)) {
        return target
      }
      ...
      return new Proxy(target, handler)
    }
    

    现在可以实现捕获器,需要注意要对嵌套情况的处理:

    const isObject = val => val !== null && typeof val === 'object'
    const convert = target => isObject(target) ? reactive(target) : target
    
    function reactive (target) {
      if (!isObject(target)) return target
    
      const handler = {
        get (target, key, receiver) {
          const result = Reflect.get(target, key, receiver)
          return convert(result)
        },
        set (target, key, value, receiver) {
          const oldValue = Reflect.get(target, key, receiver)
          let result = true
          if (oldValue !== value) {
            result = Reflect.set(target, key, value, receiver)
          }
          return result
        },
        deleteProperty (target, key) {
          const result = Reflect.deleteProperty(target, key)
          return result
        }
        
        return new Proxy(target, handler)
      }
    

    Vue 2.0对嵌套对象情况,是创建实例时直接递归转变为响应式数据,而在Vue 3.0则是当获取相应属性时处理,判断是否是嵌套对象,是则递归创建响应式数据,从而优化性能。

    现在有一个大概的实现,不过最关键的响应式部分还没有实现。Vue 3.0的响应式设计与Vue 2.0 类似,也是使用观察者模式,所以可以参考 Vue 2.0的简单实现,有助于理解Vue 3.0的实现。

    Vue 3.0会存有一个全局的TargetMap,用来存放收集依赖,它的键为被依赖的对象,值也是一个Map,键为被依赖的属性,值是属性发生改变时,需要调用的函数。因此我们需要track和trigger函数,前者收集依赖,后者当属性发生改变,调用函数。

    let targetMap = new WeakMap()  // 全局变量,存放依赖
    
    function track (target, key) {
      if (!activeEffect) return
      let depsMap = targetMap.get(target)  // 获取被依赖对象的Map,没有则创建
      if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
      }
      let dep = depsMap.get(key) // 根据对象属性,获取需调用的函数,没有则创建
      if (!dep) {
        depsMap.set(key, (dep = new Set()))
      }
      dep.add(activeEffect)
    }
    
    function trigger (target, key) {
      const depsMap = targetMap.get(target)
      if (!depsMap) return
      const dep = depsMap.get(key)
      if (dep) {
        dep.forEach(effect => {
          effect()
        })
      }
    }
    

    剩下最关键的effect还没有实现。实现之前,先看一个使用例子:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script type="module">
        import { reactive, effect } from './reactivity/index.js'
    
        const product = reactive({
          name: 'iPhone',
          price: 5000,
          count: 3
        })
        let total = 0 
        effect(() => {
          total = product.price * product.count
        })
        console.log(total)  // 15000 
    
        product.price = 4000
        console.log(total) // 12000
    
        product.count = 1
        console.log(total) // 4000
    
      </script>
    </body>
    </html>
    

    上述的例子可得知,当调用effect函数时,它会执行一次传入的函数,如果之后传入函数里的值发生变更,会调用之前effect里传入的函数。问题是Vue是怎样知道要调用的函数?答案是当调用effect函数时,Vue已经在获取相应的值时,收集依赖。先看effect的实现:

    let activeEffect = null  // 全局指针指向最近传入effect的函数
    function effect (callback) {
      activeEffect = callback
      callback() // 访问响应式对象属性,去收集依赖
      activeEffect = null
    }
    

    通过上述的例子解释effect的执行过程。我们先命名例子传入的函数为 totalSum,当调用effect时,activeEffect指向totalSum,然后调用totalSum,它会分别获取 product.price 和 product.count,就在此时,触发了代理对象的get捕获器,因此触发了track函数,收集依赖。再看一次track吧:

    let targetMap = new WeakMap()  // 全局变量,存放依赖
    
    function track (target, key) {
      if (!activeEffect) return
      let depsMap = targetMap.get(target)  // 获取被依赖对象的Map,没有则创建
      if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
      }
      let dep = depsMap.get(key) // 根据对象属性,获取需调用的函数,没有则创建
      if (!dep) {
        depsMap.set(key, (dep = new Set()))
      }
      dep.add(activeEffect)  // 收集依赖
    }
    

    最后把track和trigger放进代理对象proxy里就完成reactive函数了:

    const isObject = val => val !== null && typeof val === 'object'
    const convert = target => isObject(target) ? reactive(target) : target
    const hasOwnProperty = Object.prototype.hasOwnProperty
    const hasOwn = (target, key) => hasOwnProperty.call(target, key)
    
    function reactive (target) {
      if (!isObject(target)) return target
    
      const handler = {
        get (target, key, receiver) {
          // 收集依赖
          track(target, key)
          const result = Reflect.get(target, key, receiver)
          return convert(result)
        },
        set (target, key, value, receiver) {
          const oldValue = Reflect.get(target, key, receiver)
          let result = true
          if (oldValue !== value) {
            result = Reflect.set(target, key, value, receiver)
            // 触发更新
            trigger(target, key)
          }
          return result
        },
        deleteProperty (target, key) {
          const hadKey = hasOwn(target, key)
          const result = Reflect.deleteProperty(target, key)
          if (hadKey && result) {
            // 触发更新
            trigger(target, key)
          }
          return result
        }
      }
    
      return new Proxy(target, handler)
    }
    

    用网上找到一个图概括Vue 3.0的响应式原理:

    深入理解Vue 3.0 Reactive

    源码阅读

    有了简单实现的基础后,可以阅读源码了。

    Reactive函数的源代码位于源码路径 packages/reactivity/src/reactive.ts。

    function reactive (target) {
       // 如果尝试把一个 readonly proxy 变成响应式,直接返回这个 readonly proxy
      if (target && target.__v_isReadonly) {
         return target
      } 
    
      return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
    }
    
    // isReadonly指定target是否唯读,baseHandlers是基本数据类型的代理捕获器,collectionHandlers则是集合的
    function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
    
      if (!isObject(target)) {
        // 目标必须是对象或数组类型
        if ((process.env.NODE_ENV !== 'production')) {
          console.warn(`value cannot be made reactive: ${String(target)}`)
        }
    
        return target
      }
    
      if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
        // target 已经是 Proxy 对象,直接返回
        // 有个例外,如果是 readonly 作用于一个响应式对象,则继续
        return target
    
      }
    
      if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
    
        // target 已经有对应的 Proxy 了
        return isReadonly ? target.__v_readonly : target.__v_reactive
      }
    
      // 只有在白名单里的数据类型才能变成响应式
    
      if (!canObserve(target)) {
        return target
      }
    
      // 利用 Proxy 创建响应式
      const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
    
      // 给原始数据打个标识,说明它已经变成响应式,并且有对应的 Proxy 了
      def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
    
      return observed
    }
    

    以上是reactive基本构建过程,与之前实现差不多,只是源码考虑更多。isReadonly是一个布尔值,用于表示代理对象是否唯读。Proxy比Object.defineProperty好的一点是,它可以处理数组。

    canObserve函数针对target作进一步限制:

    const canObserve = (value) => {
      return (!value.__v_skip &&
       isObservableType(toRawType(value)) &&
       !Object.isFrozen(value))
    }
    
    const isObservableType = /*#__PURE__*/ makeMap('Object,Array,Map,Set,WeakMap,WeakSet')
    

    带有 __v_skip 属性的对象、被冻结的对象,以及不在白名单内的对象是不能变成响应式的。

    const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers) 创建代理对象,根据target的构造函数,如果是基本数据类型,则返回baseHandlers,它的值是mutableHandlers。

    const mutableHandlers = {
      get,
      set,
      deleteProperty,
      has,
      ownKeys
    }
    

    无论命中哪个处理器函数,它都会做依赖收集和派发通知这两件事其中的一个。

    依赖收集:get 函数

    看下创造get捕获器的源码:

    function createGetter(isReadonly = false) {
    
      return function get(target, key, receiver) {
        // 根据不同属性返回不同结果
        if (key === "__v_isReactive" /* isReactive */) {
          // 代理 observed.__v_isReactive
          return !isReadonly
        }
        else if (key === "__v_isReadonly" /* isReadonly */) {
          // 代理 observed.__v_isReadonly
          return isReadonly;
        }
        else if (key === "__v_raw" /* raw */) {
          // 代理 observed.__v_raw
          return target
        }
    
        const targetIsArray = isArray(target) 
        
        // 如果target是数组而且属性包含于arrayInstrumentations
        // arrayInstrumentations 包含对数组一些方法修改的函数
        if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)
        }
    
        // 求值
        const res = Reflect.get(target, key, receiver)
    
        // 内置 Symbol key 不需要依赖收集
        if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
          return res
        }
    
        // 依赖收集
        !isReadonly && track(target, "get" /* GET */, key)
        
        return isObject(res)
          ? isReadonly
            ?
            readonly(res)
            // 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式
            : reactive(res)
          : res
      }
    }
    

    看一下arrayInstrumentations,它是对代理数组的一些方法,调用所包含的方法时,收集依赖:

    const arrayInstrumentations = {}
    ['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
      arrayInstrumentations[key] = function (...args) {
        // toRaw 可以把响应式对象转成原始数据
        const arr = toRaw(this) // this是数组本身
        for (let i = 0, l = this.length; i < l; i++) {
          // 依赖收集
          track(arr, "get" /* GET */, i + '')
        }
    
        // 先尝试用参数本身,可能是响应式数据
        const res = arr[key](...args)
        if (res === -1 || res === false) {
          // 如果失败,再尝试把参数转成原始数据
          return arr[key](...args.map(toRaw))
        }
        else {
          return res
        }
      }
    })
    

    为什么要修改这几个方法?因为当修改数组数据时,这几个方法获得的值可能不同,所以每次调用它们,都需要重新收集依赖。

    看track函数之前,看一下get的最后,最后根据结果作出不同行动,如果是基本数据类型,直接返回值,否则递归变成响应式数据,这是与Vue 2.0不同之处。vue 2.0是创建时直接递归处理,3则是当获取属性时才判断是否处理,延时定义子对象响应式的实现,在性能上会有较大的提升。

    最后看get的最核心函数track:

    // 是否应该收集依赖
    let shouldTrack = true
    
    // 当前激活的 effect
    let activeEffect
    
    // 原始数据对象 map
    const targetMap = new WeakMap()
    
    function track(target, type, key) {
      if (!shouldTrack || activeEffect === undefined) {
        return
      }
    
      let depsMap = targetMap.get(target)
      
      if (!depsMap) {
        // 每个 target 对应一个 depsMap
        targetMap.set(target, (depsMap = new Map()))
      }
    
      let dep = depsMap.get(key)
      if (!dep) {
    
        // 每个 key 对应一个 dep 集合
        depsMap.set(key, (dep = new Set()))
      }
    
      if (!dep.has(activeEffect)) {
        // 收集当前激活的 effect 作为依赖
        dep.add(activeEffect)
       // 当前激活的 effect 收集 dep 集合作为依赖
        activeEffect.deps.push(dep)
      }
    }
    

    基本实现与之前的简单版相同,只是现在激活的effect也要收集dep作为依赖。

    派发通知:set 函数

    派发通知发生在数据更新的阶段 ,由于我们用 Proxy API 劫持了数据对象,所以当这个响应式对象属性更新的时候就会执行 set 函数。我们来看一下 set 函数的实现,它是执行 createSetter 函数的返回值:

    function createSetter() {
      return function set(target, key, value, receiver) {
        const oldValue = target[key]
        value = toRaw(value)
        const hadKey = hasOwn(target, key)
        // 使用Reflect修改
        const result = Reflect.set(target, key, value, receiver)
    
        // 如果目标的原型链也是一个 proxy,通过 Reflect.set 修改原型链上的属性会再次触发 setter,这种情况下就没必要触发两次 trigger 了
        if (target === toRaw(receiver)) {
          if (!hadKey) {
          没有属性而增加则触发增加类型
            trigger(target, "add" /* ADD */, key, value)
          }
          else if (hasChanged(value, oldValue)) {
          有属性而修改则触发修改类型
            trigger(target, "set" /* SET */, key, value, oldValue)
          }
        }
        return result
      }
    }
    

    set的逻辑很简单,重点是trigger函数,它的作用是派发通知。

    
    // 原始数据对象 map WeakMap的特点是键为引用
    const targetMap = new WeakMap()
    
    function trigger(target, type, key, newValue) {
      // 通过 targetMap 拿到 target 对应的依赖集合
      const depsMap = targetMap.get(target)
    
      if (!depsMap) {
        // 没有依赖,直接返回
        return
      }
    
      // 创建运行的 effects 集合
      const effects = new Set()
    
      // 添加 effects 的函数
      const add = (effectsToAdd) => {
        if (effectsToAdd) {
          effectsToAdd.forEach(effect => {
            effects.add(effect)
          })
        }
      }
    
      // SET | ADD | DELETE 操作之一,添加对应的 effects
      if (key !== void 0) {
        add(depsMap.get(key))
      }
    
      const run = (effect) => {
        // 调度执行
        if (effect.options.scheduler) {
          effect.options.scheduler(effect)
        }
        else {
          // 直接运行
          effect()
        }
      }
      
      // 遍历执行 effects
      effects.forEach(run)
    }
    

    派发通知与之前的实现相似,就是获取相应代理对象属性收集的依赖,然后派发通知。

    副作用函数: effect

    重点需要关注的是 activeEffect (当前激活副作用函数),这部分的实现比之前的简易版複杂多了,它是整个Vue 3.0响应式的重点。

    // 全局 effect 栈
    const effectStack = []
    
    // 当前激活的 effect
    let activeEffect
    
    function effect(fn, options = EMPTY_OBJ) {
      if (isEffect(fn)) {
        // 如果 fn 已经是一个 effect 函数了,则指向原始函数
        fn = fn.raw
      }
    
      // 创建一个 wrapper,把传入的函数封装,它是一个响应式的副作用的函数
      const effect = createReactiveEffect(fn, options)
    
      if (!options.lazy) {
        // lazy 配置,计算属性会用到,非 lazy 则直接执行一次
        effect()
      }
    
      return effect
    }
    

    与之前简单让activeEffect指向最近使用的effect函数不同,源码还封装effect函数,接下来看它是怎样封装:

    function createReactiveEffect(fn, options) {
      const effect = function reactiveEffect(...args) {
        if (!effect.active) {
          // 非激活状态,则判断如果非调度执行,则直接执行原始函数。
          return options.scheduler ? undefined : fn(...args)
        }
    
        if (!effectStack.includes(effect)) {
          // 清空 effect 引用的依赖
          cleanup(effect)
    
          try {
            // 开启全局 shouldTrack,允许依赖收集
            enableTracking()
            
            // 压栈
            effectStack.push(effect)
            activeEffect = effect
            
            // 执行原始函数
            return fn(...args)
          }
          finally {
            // 出栈
            effectStack.pop()
    
            // 恢复 shouldTrack 开启之前的状态
            resetTracking()
    
            // 指向栈最后一个 effect
            activeEffect = effectStack[effectStack.length - 1]
          }
        }
      }
    
      // 给effect一个id
      effect.id = uid++
    
      // 标识是一个 effect 函数
      effect._isEffect = true
    
      // effect 自身的状态
      effect.active = true
    
      // 包装的原始函数
      effect.raw = fn
    
      // effect 对应的依赖,双向指针,依赖包含对 effect 的引用,effect 也包含对依赖的引用
      effect.deps = []
    
      // effect 的相关配置
      effect.options = options
    
      return effect
    }
    

    createReactiveEffect最后返回一个带属性的effect函数。封装effect函数的过程中,做了两件事:

    1. 设定activeEffect的指向
    2. 负责effectStack的出入栈

    第1点在之前的简易版了解,为什么要有 effectStack?因为要处理嵌套场景,考虑以下场景:

    import { reactive} from 'vue' 
    import { effect } from '@vue/reactivity' 
    
    const counter = reactive({ 
    num: 0, 
    num2: 0 
    }) 
    
    function logCount() { 
      effect(logCount2) 
      console.log('num:', counter.num) 
    } 
    
    function count() { 
      counter.num++ 
    } 
    
    function logCount2() { 
      console.log('num2:', counter.num2) 
    } 
    
    effect(logCount) 
    count()
    

    我们希望的是当 counter.num发生变化时,触发logCount函数,但如果没有栈,只有activeEffect指针,当调用 effect(logCount),activeEffect指向的是 logCount2,而不是logCount,所以最后结果是:

    num2: 0 
    
    num: 0 
    
    num2: 0
    

    而不是:

    num2: 0 
    
    num: 0 
    
    num2: 0 
    
    num: 1
    

    因此我们需要一个栈来存下外层的effect函数,以让activeEffect指针之后指向外层effect。不妨重看源码相关部分:

    function createReactiveEffect(fn, options) {
      ...
      try {
    		...
            // 压栈
            effectStack.push(effect)
            activeEffect = effect
            
            // 执行原始函数
            return fn(...args)
          }
          finally {
            // 执行完结后,出栈
            effectStack.pop()
    
            // 恢复 shouldTrack 开启之前的状态
            resetTracking()
    
            // 指向栈最后一个 effect
            activeEffect = effectStack[effectStack.length - 1]
          }
        }
      }
    ...
      return effect
    }
    

    当effect函数调用logCount时,把logCount压入effectStack栈中,然后在logCount里,又有一个effect函数调用logCount2,把logCount2压入effectStack栈中。logCount2获取counter.num2的值,这时activeEffect指向logCount2,counter.num2收集logCount2 (activeEffect)为依赖,然后effect执行finally区域的代码,把logCount2出栈,activeEffect指向栈的尾部,即logCount,现在logCount继续执行,获取counter.num,counter.num把logCount收集为依赖,因为activeEffect指向logCount。

    counter.num发生变化,则会执行logCount。

    最后还有一个cleanUp函数没有解释,它会把effect函数的依赖删除:

    function cleanup(effect) {
    
      const { deps } = effect
      if (deps.length) {
        for (let i = 0; i < deps.length; i++) {
          deps[i].delete(effect)
        }
    
        deps.length = 0
      }
    }
    

    在执行 track 函数的时候,除了收集当前激活的 effect 作为依赖,还通过 activeEffect.deps.push(dep) 把 dep 作为 activeEffect 的依赖,这样在 cleanup 的时候我们就可以找到 effect 对应的 dep 了,然后把 effect 从这些 dep 中删除。

    为什么需要 cleanup 呢?因为要考虑组件的渲染函数也是副作用函数,以下面的场景为例:

    <template>
      <div v-if="state.showMsg">
        {{ state.msg }}
      </div>
      <div v-else>
        {{ Math.random()}}
      </div>
      <button @click="toggle">Toggle Msg</button>
      <button @click="switchView">Switch View</button>
    </template>
    <script>
      import { reactive } from 'vue'
    
      export default {
    
        setup() {
          const state = reactive({
            msg: 'Hello World',
            showMsg: true
          })
    
          function toggle() {
            state.msg = state.msg === 'Hello World' ? 'Hello Vue' : 'Hello World'
          }
    
          function switchView() {
            state.showMsg = !state.showMsg
          }
    
          return {
            toggle,
            switchView,
            state
          }
        }
      }
    </script>
    

    这是黄轶老师在Vue 3.0举的例子,他的解释如下:

    有点複杂,不过读多几次就明白cleanUp的作用。

    以上就是reactive的主要内容。


    起源地 » 深入理解Vue 3.0 Reactive

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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