Vue3源码学习(1)--框架设计概览

1,038 阅读5分钟

Vue源码学习

前言

基于大崔哥Github开源mini-vue以及霍春阳大佬的《Vue.js设计和实现》学习Vue3源码,最终实现一个具有happy-path的mini-vue

Vue3框架的设计概览

1、权衡的艺术?

一个框架的实现过程中,各个功能模块之间并不是相互独立的,而是相互关联、相互制约的。因此框架的设计需要全局的把控,以便于后续模块的设计和拆分,而我们作为学习者,在学习框架的时候,也应该从全局的角度对框架的进行学习和认知。

1.1 命令式和声明式的权衡

视图层框架通常分为命令时和声明式,那么什么式命令式、声明式

命令式

命令式最主要的特点:关注过程。例如,我们把下面的这段话翻译成原生代码:

获取 id 为 app 的 div 标签  -------> const eDiv = document.getELementById("#app")

它的文本内容设置为 hello world  -------> eDiv.innerText = "hello world"  

为其绑定点击事件,当点击时弹出提示:ok   -------> eDiv.addEventListen("click",()=>{alert("ok")})

命令式的代码本身就是描述“做事情的过程”,更加符合我们的思维逻辑

声明式

声明式最主要的特点:关注结果

上述自然语言在Vue中的实现

<div id="app" @click="()=>{alert("ok")}>

我们只看到了实现了这个“结果”,但是“结果”的过程,则是由Vuejs帮我们完成。Vuejs帮我们完成了“命令式”这个过程的封装,暴露给我们用户的是声明式的指令

1.2 性能与可维护性的权衡

声明式代码的性能不低于命令式代码

如果我们把直接修改的性能消耗定义为A,找出差异的性能消耗定义为B,那么有:

  • 命令式代码的更新性能消耗 = A
  • 声明式代码的更新性能小号 = A + B

只有在找出差异的性能消耗为0的时候,声明式和命令式代码的性能消耗相同

声明式代码的可维护性高

采用命令式代码开发的时候,我们需要维护实现目标的整个过程,包括要手动的完成DOM元素的创建、更新、删除等。而声明式展示的仅仅是我们要的结果,看上去更加直观

框架设计者要做的就是:在保持可维护性的同时,让性能损失最小

1.3 虚拟DOM

声明式代码的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗,我们要做的就是尽可能减少找出差异的性能消耗。所谓的虚拟DOM就是为了最小化找出差异这一步。

innerHTML和虚拟DOM创建页面是的性能

虚拟DOMinnerHtml
纯JavaScript运算创建JavaScript对象(vnode)渲染HTML字符串
DOM运算新建所有的DOM元素新建所有的DOM元素

innerHTML和虚拟DOM更新页面是的性能

虚拟DOMinnerHtml
纯JavaScript运算创建JavaScript对象(vnode) + Diff渲染HTML字符串
DOM运算必要的DOM更新删除所有旧DOM,新建所有新DOM元素
性能因素与数据变化量相关(JS层面与运算)与模板大小相关)(DOM层面运算)

粗略总结:

性能低 ------------------->性能高

innerHTML < 虚拟DOM < 原生JavaScript方法

同时还与页面大小、变更部分大小都有关,除此之外,与创建页面更新页面也有关系,权衡之后,选择虚拟DOM

1.4 编译时和运行时

纯运行时

假设我们设计的框架,它提供了一个render函数,用户可以为该函数提供一个树型的数据对象,然后Render函数会根据对象递归的处理数据渲染成DOM元素

function render(obj,root){
    const { tag ,children } = obj
    const el = document.createElement(tag)
    if(typeof children === "string"){
        const text = document.createTextNode(children)
        el.appendChild(text)
    }else if (Array.isArray(children)){
        //递归调用
        children.forEach(child=>{
            render(children,el)
        })  
     }
     
     root.appendChildren(el)
}

const obj ={
    tag:"div",
    children:[
        {tag:"span",children:"helloworld"}
    ]
}

render(obj,document.body)

用户在使用render函数渲染内容的时候,必须为render函数提供一个属性的数据结构,这就是一个纯运行时的框架

运行时 + 编译时

如果我们想使用HTML模板,就需要将HTML模板编译成render函数需要的属性结构,就可以继续使用render函数了

为此,编写一个compiler函数

const html = `
    <div>hello world</div>`

const obj = function compiler(html)
render(obj,document.body)
   

这是框架就变成了运行时 + 编译时的框架

编译时

直接将html模板编译成命令式代码,不需要render函数,仅仅使用一个compiler函数,这是框架就是编译时框架

Vue.js3 是一个编译时 + 运行时的框架。

2、框架设计的核心要素

  • 提升用户开发体验

  • 控制框架的代码体积

  • 框架良好的Tree-shaking

  • 框架应该输出怎么的构建产物

  • 特性开关

  • 错误处理

  • 良好的TypeScript类型支持

总结

通过《Vue.js设计和实现》来了解框架的设计的理念,具体内容请大家阅读书籍吧

初始化项目

  • 在项目文件夹下执行yarn init -y命令生成package.json文件

  • 执行yarn add typescript jest @types/jest -D命令安装 typescript、jest 和 @types/jest

  • 执行tsc --init命令生成tsconfig.json文件,并修改以下几项:

    "lib": ["DOM", "ES6"],     // 引入 DOM 和 ES6
    "noImplicitAny": false,    // 允许在表达式和声明中不指定类型,使用隐含的 any
    "types": ["jest"],         // 将 jest 的类型声明文件添加到编译中
    "downlevelIteration": true // 开启 for-of 对可迭代对象的支持
    复制代码
    
  • package.json中配置scripts

    "scripts": {
      "test": "jest"
    }
    复制代码
    
  • 根据 Jest 官网进行配置,以在 Jest 中使用 Babel 和 TypeScript:执行yarn add -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript安装 babel-jest、@babel/core、@babel/preset-env 和 @babel/preset-typescript;创建babel.config.js文件,并写入以下内容:

    module.exports = {
      presets: [
        ['@babel/preset-env', { targets: { node: 'current' } }],
        '@babel/preset-typescript'
      ]
    }
    复制代码
    
  • 执行yarn add ts-jest -D命令安装 ts-jest,之后执行ts-jest命令生成jest.config.js文件

  • 创建src文件夹用于保存项目的代码