[Vue官方教程笔记]- 尤雨溪手写mini-vue(上)

简介: 这周我看了看了尤大神亲手写的mini版Vue3,笔记如下请大家指正。

一、整体工作流程


网络异常,图片无法展示
|


  1. 编译器将视图模板编译为渲染函数


  1. 数据响应模块将数据对象初始化为响应式数据对象


  1. 视图渲染


  1. RenderPhase : 渲染模块使用渲染函数根据初始化数据生成虚拟Dom


       b. MountPhase  : 利用虚拟Dom创建视图页面Html


       c.PatchPhase:数据模型一旦变化渲染函数将再次被调用生成新的虚拟Dom,然后做Dom Diff更新视图Html


二、三大模块的分工


网络异常,图片无法展示
|


  • 数据响应式模块


  • 编译器


  • 渲染函数


1. 数据响应式模块


提供创建一切数据变化都是可以被监听的响应式对象的方法。


网络异常,图片无法展示
|


2. 编译模块


网络异常,图片无法展示
|


将html模板编译为渲染函数


这个编译过程可以在一下两个时刻执行


  • 浏览器运行时  (runtime)


  • Vue项目打包编译时 (compile time)


3. 渲染函数


渲染函数通过以下三个周期将视图渲染到页面上


网络异常,图片无法展示
|


  • Render Phase


  • Mount Phase


  • Patch Phase


三、MVVM原型(Mock版)


网络异常,图片无法展示
|


MVVM框架其实就是在原先的View和Model之间增加了一个VM层完成以下工作。完成数据与视图的监听。我们这一步先写一个Mock版本。其实就是先针对固定的视图和数据模型实现监听。


1. 接口定义


我们MVVM的框架接口和Vue3一模一样。


初始化需要确定


  • 视图模板


  • 数据模型


  • 模型行为 - 比如我们希望click的时候数据模型的message会会倒序排列。


const App = {
  // 视图
  template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`,
  setup() {
    // 数据劫持
    const state = new Proxy(
      {
        message: "Hello Vue 3!!",
      },
      {
        set(target, key, value, receiver) {
          const ret = Reflect.set(target, key, value, receiver);
          // 触发函数响应
          effective();
          return ret;
        },
      }
    );
    const click = () => {
      state.message = state.message.split("").reverse().join("");
    };
    return { state, click };
  },
};
const { createApp } = Vue;
createApp(App).mount("#app");


2. 程序骨架


程序执行过程大概如图:


网络异常,图片无法展示
|


const Vue = {
  createApp(config) {
    // 编译过程
    const compile = (template) => (content, dom) => {
    };
    // 生成渲染函数
    const render = compile(config.template);
    return {
      mount: function (container) {
        const dom = document.querySelector(container);
        // 实现setup函数
        const setupResult = config.setup();
        // 数据响应更新视图
        effective = () => render(setupResult, dom);
        render(setupResult, dom);
      },
    };
  },
};


3.  编译渲染函数


MVVM框架中的渲染函数是会通过视图模板的编译建立的。


// 编译函数
// 输入值为视图模板
const compile = (template) => {
  //渲染函数
  return (observed, dom) => {
    // 渲染过程
  }
}


简单的说就是对视图模板进行解析并生成渲染函数。


大概要处理以下三件事


  • 确定哪些值需要根据数据模型渲染


// <button>{{message}}</button>
// 将数据渲染到视图
button = document.createElement('button')
button.innerText = observed.message
dom.appendChild(button)


  • 绑定模型事件


// <button @click='click'>{{message}}</button>
// 绑定模型事件
button.addEventListener('click', () => {
  return config.methods.click.apply(observed)
})


  • 确定哪些输入项需要双向绑定


// <input v-model="message"/>
// 创建keyup事件监听输入项修改
input.addEventListener('keyup', function () {
  observed.message = this.value
})


完整的代码


const compile = (template) => (observed, dom) => {
    // 重新渲染
    let input = dom.querySelector('input')
    if (!input) {
        input = document.createElement('input')
        input.setAttribute('value', observed.message)
        input.addEventListener('keyup', function () {
            observed.message = this.value
        })
        dom.appendChild(input)
    }
    let button = dom.querySelector('button')
    if (!button) {
        console.log('create button')
        button = document.createElement('button')
        button.addEventListener('click', () => {
            return config.methods.click.apply(observed)
        })
        dom.appendChild(button)
    }
    button.innerText = observed.message
}


四、数据响应实现


Vue普遍走的就是数据劫持方式。不同的在于使用DefineProperty还是Proxy。也就是一次一个属性劫持还是一次劫持一个对象。当然后者比前者听着就明显有优势。这也就是Vue3的响应式原理。


Proxy/Reflect是在ES2015规范中加入的,Proxy可以更好的拦截对象行为,Reflect可以更优雅的操纵对象。 优势在于


  • 针对整个对象定制 而不是对象的某个属性,所以也就不需要对keys进行遍历。


  • 支持数组,这个DefineProperty不具备。这样就省去了重载数组方法这样的Hack过程。


  • Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富


  • Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法


  • 可以通过递归方便的进行对象嵌套。


说了这么多我们先来一个小例子


var obj = new Proxy({}, {
    get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
    }
})
obj.abc = 132


这样写如果你修改obj中的值,就会打印出来。


也就是说如果对象被修改就会得的被响应。


网络异常,图片无法展示
|


当然我们需要的响应就是重新更新视图也就是重新运行render方法。


首先制造一个抽象的数据响应函数


// 定义响应函数
let effective
observed = new Proxy(config.data(), {
  set(target, key, value, receiver) {
    const ret = Reflect.set(target, key, value, receiver)
    // 触发函数响应
    effective()
    return ret
  },
})


在初始化的时候我们设置响应动作为渲染视图


const dom = document.querySelector(container)
// 设置响应动作为渲染视图
effective = () => render(observed, dom)
render(observed, dom)


1. 视图变化的监听


浏览器视图的变化,主要体现在对输入项变化的监听上,所以只需要通过绑定监听事件就可以了。


document.querySelector('input').addEventListener('keyup', function () {
  data.message = this.value
})


2. 完整的代码


<html lang="en">
  <body>
    <div id="app"></div>
    <script>
      const Vue = {
        createApp(config) {
          // 编译过程
          const compile = (template) => (content, dom) => {
            // 重新渲染
            dom.innerText = "";
            input = document.createElement("input");
            input.addEventListener("keyup", function () {
              content.state.message = this.value;
            });
            input.setAttribute("value", content.state.message);
            dom.appendChild(input);
            let button = dom.querySelector("button");
            button = document.createElement("button");
            button.addEventListener("click", () => {
              return content.click.apply(content.state);
            });
            button.innerText = content.state.message;
            dom.appendChild(button);
          };
          // 生成渲染函数
          const render = compile(config.template);
          return {
            mount: function (container) {
              const dom = document.querySelector(container);
              const setupResult = config.setup();
              effective = () => render(setupResult, dom);
              render(setupResult, dom);
            },
          };
        },
      };
      // 定义响应函数
      let effective;
      const App = {
        // 视图
        template: `
                <input v-model="message"/>
                <button @click='click'>{{message}}</button>
            `,
        setup() {
          // 数据劫持
          const state = new Proxy(
            {
              message: "Hello Vue 3!!",
            },
            {
              set(target, key, value, receiver) {
                const ret = Reflect.set(target, key, value, receiver);
                // 触发函数响应
                effective();
                return ret;
              },
            }
          );
          const click = () => {
            state.message = state.message.split("").reverse().join("");
          };
          return { state, click };
        },
      };
      const { createApp } = Vue;
      createApp(App).mount("#app");
    </script>
  </body>
</html>


五、 视图渲染过程


Dom => virtual DOM => render functions


1. 什么是Dom 、Document Object Model


网络异常,图片无法展示
|


HTML在浏览器中会映射为一些列节点,方便我们去调用。


网络异常,图片无法展示
|


2. 什么是虚拟Dom


Dom中节点众多,直接查询和更新Dom性能较差。


A way of representing the actual DOM with JavaScript Objects. 用JS对象重新表示实际的Dom


网络异常,图片无法展示
|


3. 什么是渲染函数


在Vue中我们通过将视图模板(template)编译为渲染函数(render function)再转化为虚拟Dom


网络异常,图片无法展示
|


4. 通过DomDiff高效更新视图


网络异常,图片无法展示
|


5. 总结


举个栗子🌰 虚拟Dom和Dom就像大楼和大楼设计图之间的关系。


网络异常,图片无法展示
|


假设你要在29层添加一个厨房 ❌ 拆除整个29层,重新建设 ✅先绘制设计图,找出新旧结构不同然后建设


相关文章
|
28天前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
192 0
|
29天前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
1月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
162 17
|
1月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
82 1
|
1月前
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
102 0
|
3月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
371 4
|
2月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
239 77
|
3月前
|
缓存 JavaScript 前端开发
Vue 基础语法介绍
Vue 基础语法介绍
|
1月前
|
监控 JavaScript 前端开发
Vue 文件批量下载组件封装完整使用方法及优化方案解析
本文详细介绍了批量下载功能的技术实现与组件封装方案。主要包括两种实现方式:**前端打包方案(基于file-saver和jszip)** 和 **后端打包方案**。前者通过前端直接将文件打包为ZIP下载,适合小文件场景;后者由后端生成ZIP文件流返回,适用于大文件或大量文件下载。同时,提供了可复用的Vue组件`BatchDownload`,支持进度条、失败提示等功能。此外,还扩展了下载进度监控和断点续传等高级功能,并针对跨域、性能优化及用户体验改进提出了建议。可根据实际需求选择合适方案并快速集成到项目中。
185 17
|
1月前
|
JavaScript 前端开发 UED
Vue 手风琴实现的三种常用方式及长尾关键词解析
手风琴效果是Vue开发中常见的交互组件,可节省页面空间、提升用户体验。本文介绍三种实现方式:1) 原生Vue结合数据绑定与CSS动画;2) 使用Element UI等组件库快速构建;3) 自定义指令操作DOM实现独特效果。每种方式适用于不同场景,可根据项目需求选择。示例包括产品特性页、后台菜单及FAQ展示,灵活满足多样需求。附代码示例与资源链接,助你高效实现手风琴功能。
105 10