最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue3源码系列(一):初探Vue3

    正文概述 掘金(ChuckClose)   2021-08-14   685

    Vue3新特性

    1. composition API。

      不同于Vue2的选项式API,组合式API将组件初始化时使用setup方法,包含所有数据、方法,并统一返回。

      举个例子:

      Vue3 composition API:
      
      <script>
      export default{
      	setup(){
      		const count =ref (0);
              const add=()=>{
              	count.value++;
              }
      		return {
      			count,
      			add
      		}
      	}
      }
      </script>
      
    2. 基于Proxy的响应式。

      Vue2中响应式数据是通过ES5的数据劫持setter/getter 来实现的,使用的是object.defineproperty,但只能对现有的对象属性进行劫持,针对的是对象上的属性,尽管做一些重写也不能完全覆盖数据变更的地方(比如动态向对象上添加Key,或通过下标的方式修改数组。)

      在Vue3中响应式数据使用的是Proxy,针对的是整个对象,直接代理对象,修改代理对象时拦截数据的变化。

      const data={}
      
      const dataProxy=new Proxy(data,{
      	set(target,key,val){
      	//代理对象
      		Reflact.set()
      	}
      	get(target,key,val){
      		//
      		Reflact.get()
      	}
      })
      

      Proxy.revocable()方法可以用来创建一个可撤销的代理对象。

      const target = { name: 'vuejs'}
      const {proxy, revoke} = Proxy.revocable(target, handler)
      proxy.name // 正常取值输出 vuejs
      revoke() // 取值完成对proxy进行封闭,撤消代理
      proxy.name // TypeError: Revoked
      

      但Proxy只有在ES6的环境使用,还无法编译降级。

    3. Tree-shaking的优化。

      在使用Vue3时可以选择按需选择引入相应的模块,而不是一次性引入所有代码,这样打包时Vue3可以将没有引用的源码移除,从而减少体积。

    4. 渲染性能的优化。

      Vue3将静态节点、子树等渲染代码移到渲染函数之外,这样可以避免每次渲染时重新创建这些不会变化的对象。将元素的更新类型进行细分,例如动态绑定的部分如果只涉及到 class,则在对比时只需要对比 class 即可,不需要对比它的内容。此外,Vue3还有不少变化,比如模板中支持了多根节点的组件等,这边就不一一介绍。

    代码结构

    Vue3的代码主要分packages和scripts两个目录,script主要用于代码检查、打包等工程操作,真正的源码位于packages目录下,一共有13个包:

    • compiler-core 模板解析核心,与具体环境无关,主要生成 AST,并根据 AST 生成 render() 方法
    • compiler-dom 浏览器环境中的模板解析逻辑,如处理 HTML 转义、处理 v-model 等指令
    • compiler-sfc 负责解析 Vue 单文件组件,在前面 vue-loader 的解析中有讲解过
    • compiler-ssr 服务端渲染环境中的模板解析逻辑
    • reactivity 响应式数据相关逻辑
    • runtime-core 与平台无关的运行时核心,包括 render
    • runtime-dom 浏览器环境中的运行时核心
    • runtime-test 用于自动化测试的相关配套
    • server-renderer 用于 SSR 服务端渲染的逻辑
    • shared 一些各个包之间共享的公共工具
    • size-check 一个用于测试 tree shaking 后代码大小的示例库
    • template-explorer 用于检查模板编译后的输出,主要用于开发调试
    • vue Vue 3 的主要入口,包括运行时和编译器,包括几个不同的入口(开发版本、runtime 版本、full 版本)

    reactivity

    整体流程

    Vue3源码系列(一):初探Vue3

    ReactiveFlags

    export const enum ReactiveFlags {
      skip = '__v_skip',
      isReactive = '__v_isReactive',
      isReadonly = '__v_isReadonly',
      raw = '__v_raw',
      reactive = '__v_reactive',
      readonly = '__v_readonly'
    }
    
    • 代理对象会通过 ReactiveFlags.raw 引用原始对象
    • 原始对象会通过 ReactiveFlags.reactiveReactiveFlags.readonly 引用代理对象
    • 代理对象根据它是 reactivereadonly 的, 将 ReactiveFlags.isReactiveReactiveFlags.isReadonly 属性值设置为 true

    Track与Trigger

    track()trigger() 是依赖收集的核心,track() 用来跟踪收集依赖(收集 effect),trigger() 用来触发响应(执行 effect),它们需要配合 effect() 函数使用

    const obj = { foo: 1 }
    effect(() => {
      console.log(obj.foo)
      track(obj, TrackOpTypes.GET, 'foo')
    })
    
    obj.foo = 2
    trigger(obj, TriggerOpTypes.SET, 'foo')
    

    track与trigger函数接受三个参数:

    • target:要跟踪的目标对象,这里就是 obj
    • 跟踪操作的类型:obj.foo 是读取对象的值,因此是 'get'
    • key:要跟踪目标对象的 key,我们读取的是 foo,因此 keyfoo

    本质上建立一种数据结构:

    // 伪代码
    map : {
        [target]: {
            [key]: [effect1, effect2....]
        }
    }
    

    简单的理解,effect 与对象和具体操作的 key,是以这种映射关系建立关联的:

    [target]`---->`key1`---->`[effect1, effect2...]
    [target]`---->`key2`---->`[effect1, effect3...]
    [target2]`---->`key1`---->`[effect5, effect6...]
    

    既然 effect 与目标对象 target 已经建立了联系,那么当然就可以想办法通过 target ----> key 进而取到 effect ,然后执行它们,而这就是 trigger() 函数做的事情,所以在调用 trigger 函数时我们要指定目标对象和相应的key值。

    toRef

    使用reactive 声明响应式对象有时出现对象的二次引用,造成响应丢失(对象解构返回渲染环境也会丢失响应)。

    const obj = reactive({ foo: 1 }) // obj 是响应式数据
    const obj2 = { foo: obj.foo }
    
    effect(() => {
      console.log(obj2.foo) // 这里读取 obj2.foo
    })
    
    obj.foo = 2  // 设置 obj.foo 显然无效
    

    解决问题可以使用toRef() 函数把响应式对象的某个Key值转换成ref。它的实现就直接set、get返回,因为target本身就是响应式的,所以无需track和trigger。

    function toRef(target, key) {
        return {
            isRef: true,
            get value() {
                return target[key]
            },
            set value(newVal){
                target[key] = newVal
            }
        }
    }
    

    但是ref的值需要.value访问值才行。如果不想要.value来取值,我们可以直接再包一层reactive。

    const obj = reactive({ foo: 1 })
    // const obj2 = { foo: toRef(obj, 'foo') }
    const obj2 = reactive({ ...toRefs(obj) })  // 让 obj2 也是 reactive
    
    effect(() => {
      console.log(obj2.foo)  // 即使 obj2.foo 是 ref,我们也不需要 .value 来取值
    })
    
    obj.foo = 2 // 有效
    

    它的实现,发现值如果是ref,则返回.value。但这对于ref组成的数组,仍然需要.value来访问。

    if (isRef(res)) {
          // ref unwrapping - does not apply for Array + integer key.
          const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
          return shouldUnwrap ? res.value : res
        }
    

    通常使用ref()函数时,我们是为了引用原始数据类型,但引用非基本类型也是可以,比如:

    const refObj=ref({foo:1})
    

    refObj.value是一个对象,这对象是响应式的,修改refObj.value.foo会触发响应。而shallowRef是浅代理,只代理ref对象本身,也就是.value是被代理的,而.value所引用的对象并没有被代理,修改refObj.value.foo不会触发响应。那如果也要触发响应呢?Vue3提供triggerRef函数,可以让我们强制触发响应。

    triggerRef(refObj) 
    

    Vue3 Diff

    Vue3源码系列(一):初探Vue3

    大体流程:

    1. 从头对比找到有相同的节点 patch ,发现不同,立即跳出。

    2. 如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环。

    3. 如果新的节点大于老的节点数 ,对于剩下的节点全部以新的vnode处理( 这种情况说明已经patch完相同的vnode )。

    4. 对于老的节点大于新的节点的情况 , 对于超出的节点全部卸载 ( 这种情况说明已经patch完相同的vnode )。

    5. 不确定的元素( 这种情况说明没有patch完相同的vnode ) 与 3 ,4对立关系。如下情况:

           // [i ... e1 + 1]: a b [c d e] f g
      
           // [i ... e2 + 1]: a b [e d c h] f g
      
           // i = 2, e1 = 4, e2 = 5
      
      
    • 把没有比较过的新的vnode节点,通过map保存,记录已经patch的新节点的数量 patched,没有经过 path 新的节点的数量 toBePatched

    • 建立一个数组newIndexToOldIndexMap,每个子元素都是[ 0, 0, 0, 0, 0, 0, ] 里面的数字记录老节点的索引 ,数组索引就是新节点的索引

    • 遍历老节点:

      • 如果 toBePatched新的节点数量为0 ,那么统一卸载老的节点
      • 如果,老节点的key存在 ,通过key找到对应的index
      • 如果,老节点的key不存在:遍历剩下的所有新节点,如果找到与当前老节点对应的新节点那么 ,将新节点的索引,赋值给newIndex
      • 没有找到与老节点对应的新节点,卸载当前老节点
      • 如果找到与老节点对应的新节点,把老节点的索引,记录在存放新节点的数组中
    • 如果发生移动:

    1. 根据 newIndexToOldIndexMap 新老节点索引列表找到最长稳定序列
    2. 对于 newIndexToOldIndexMap[i] =0 证明不存在老节点 ,从新形成新的vnode
    3. 对移动的节点进行移动处理。

    Diff算法对比:

    • React:遍历新节点序列在旧节点序列出现的位置,如果位置递增,则新节点不需要移动,否则节点后移。
    • Vue 2:双端比较。分别用新、旧节点序列的start跟end相互比较,查找复用,直到指针重合,退出循环。若四次对比都没找到复用节点,只能拿新序列的节点去旧序列依次对比。
    • Vue 3:最长递增子序列。生成List代表新节点序列不需要移动的index数组,从后向前遍历,若List[j]==index 说明节点不需要移动,否则节点插入队尾。

    起源地下载网 » Vue3源码系列(一):初探Vue3

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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