了解vue的设计概念

285 阅读6分钟

Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

权衡的艺术

命令式和声明式

  • 命令式代码严格按照顺序,从上往下,按照每一次的命令执行,重过程
  • 声明式代码已经内部封装好了,我们可直接调用,重结果
 //01 - 获取 id 为 app 的 div 标签
 //02 - 它的文本内容为 hello world
 //03 - 为其绑定点击事件
 //04 - 当点击时弹出提示:ok
 
  //命令式
  const div = document.querySelector('#app') // 获取 div
  div.innerText = 'hello world' // 设置文本内容
  div.addEventListener('click', () => { alert('ok') }) // 绑定点击事件
  
  // 声明式
  <div @click="() => alert('ok')">hello world</div>

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

当我们需要修改其中的某一个字段,hello world-> hello vue3

命令式:直接可以再对应的命令中进行修改

div.innerText = 'hello vue3'

声明式:需要先找到更新的位置,再更新修改部分

<div @click="() => alert('ok')">hello vue3</div>

所以vue的声明式就多个查找的过程。但如果有许多的dom的操作,命令式就需要把所有的的代码操作都罗列出来,不易维护。而为了把性能损失降到最小,就是使用到了虚拟dom


虚拟dom的性能

  • 虚拟DOM是通过对象渲染创建所有DOM元素
  • innerHTML是通过渲染HTML字符串创建所有DOM元素
  • 可以通过表格的方式更直观的对比出虚拟DOMinnerHTML的创建页面性能
虚拟DOMinnerHTML
js创建方式创建javaScript对象渲染HTML字符串
DOM渲染创建创建所有DOM元素创建所有DOM元素
更新DOM创建新的js对象,对比新旧对象,更新差异销毁所有DOM元素,再重新创建
性能影响因素与数据的变化量相关(数据变化的越多,进行计算的越多)与模板的大小相关(页面DOM越多,进行销毁,再创建性能越差)

运行时和编译时

  • 纯运行时: 直接通过虚拟dom树render函数操作创建、更新和删除dom
    • 没有编译过程,每次都需要手写dom树形结构,不能直观的表达html标签
  • 纯编译时:将HTML标签编译成js的dom形式进行操作,纯命令式
    • 有失灵活性,只能根据提供的内容,编译之后才能使用
  • 运行时+编译时:把HTML标签先编译成虚拟dom树,再通过render函数操作创建、更新和删除dom
    • 尽管会对性能产生一定的开销,但是能更直观,更灵活的表达

综合一下虚拟dom的通过可维护性,性能,开发(创建,更新,修改,删除dom元素)这些方面,相对而言是最优的选择


框架设计的核心要素

提升用户体验

  • 对一个框架来说,对开发者有更好的体验是重中之重。
createApp(App).mount('#not-exist')

当我们创建一个vue,挂载到一个不存在的DOM节点时,就会在控制台收到一条警告信息,表示无法找到相对应的DOM元素

  • 友好的警告信息不仅能够帮助用户快速定位问题,还能节省用户的时间

Snipaste_2023-03-27_16-49-05.png

控制框架代码体积

控制代码的体积。在实现同样功能的情况下,当然是用的代码越少越好,这样体积就会越小,最后浏览器加载资源的时间也就越少,而需要良好的开发体验,就需要完善的警告信息,这需要编写大量的警告代码,,这不是与控制代码体积相悖吗?没错,所以我们要想办法解决这个问题

  • Vue.js 在输出资源的时候,会输出两个版本,其中一个用于开发环境,如 vue.global.js,另一个用于生产环境,如 vue.global.prod.js
  • 当把__DEV__ = true时,下面这段代码在开发环境中就存在,反之,生产环境就不存在,这段永远不会 执行的代码称为dead code,它不会出现在最终产物中,在构建资源的时候就会被移除
  • 这样在开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积
 if (__DEV__ && !res) {
 warn(
 `Failed to mount app: mount target selector "${container}"turned null.`
  )
 }

良好的Tree-Shaking支持

在vue内部有许多的组件,如果在我们所创建的项目中没有使用一些组件,那就在打包的时候就不需要,那就用到了Tree-Shaking

  • Tree-Shaking 指的就是消除那些永远不会被执行的代码,也就是排除 dead code,现在无论是 rollup.js 还是 webpack,都支持Tree-Shaking
  • 想要实现Tree-Shaking,必须满足一个条件,即模块必须是ESM(ES Module),因为Tree-Shaking依赖 ESM 的静态结构

特性开关

可以灵活选择使用某些特性,对于用户关闭的特性,我们可以利用Tree-Shaking机制让其不包含在最终的资源中

  • 在一个v3的项目中,如果不需要v2的特性,就可以配置一个特性开关,v2这部分代码就不会包含在最终的资源中,从而减小资源体积
// webpack.DefinePlugin 插件配置
 new webpack.DefinePlugin({
 __VUE_OPTIONS_API__: JSON.stringify(false) // 关闭特性
 })

vue.js3的设计思路

vue.js通过编译器将模板编译成渲染函数(虚拟DOM),再通过渲染器将渲染函数(虚拟DOM)渲染成真实DOM

初识渲染器

  • 渲染器是将虚拟DOM渲染成真实DOM
// 虚拟dom
 let obj = {
      tag: "div",
      props: {
        onclick: () => alert("hello"),
      },
      children: "click me",
    };
    
 // 渲染函数,一个简单的dom渲染器
    function render(vnode, container) {
      // 使用 vnode.tag 作为标签名称创建 DOM 元素
      const el = document.createElement(vnode.tag);
      // 遍历 vnode.props,将属性、事件添加到 DOM 元素
      for (const key in vnode.props) {
        if (/^on/.test(key)) {
          // 如果 key 以 on 开头,说明它是事件
          el.addEventListener(
            // toLowerCase()将字母都变成小写
            key.substr(2).toLowerCase(), // 事件名称
            vnode.props[key] // 事件处理函数
          );
        }
      }
      // 处理 children
      if (typeof vnode.children === "string") {
        // 如果 children 是字符串,说明它是元素的文本子节点
        el.appendChild(document.createTextNode(vnode.children));
      } else if (Array.isArray(vnode.children)) {
        // 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
        vnode.children.forEach((child) => renderer(child, el));
      }
      // 将元素添加到挂载点下
      container.appendChild(el);
    }
 // 执行渲染函数
 render(obj, document.body);

编译器

编译器是将模板编译成渲染函数,也就是一个虚拟DOM

// 模板
<div @click="handler">click me</div>

// 编译后的渲染函数
 render() {
 return h('div', { onClick: handler }, 'click me')
 }

组件就是一组 DOM 元素的封装,通过编译器编译成一个渲染函数


所以,无论是使用模板还是直接手写渲染函数,对于一个组件来说,它要渲染的内容最终都是通过渲染函数产生的,然后渲染器再把渲染函数返回的虚拟 DOM 渲染为真实 DOM,这就是模板的工作原理,也是 Vue.js 渲染页面的流程