[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层,重新建设 ✅先绘制设计图,找出新旧结构不同然后建设


相关文章
|
3天前
|
JavaScript
vue页面加载时同时请求两个接口
vue页面加载时同时请求两个接口
|
3天前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式
12 0
|
3天前
|
JavaScript
vue打印v-model 的值
vue打印v-model 的值
|
3天前
|
JavaScript
Vue实战-组件通信
Vue实战-组件通信
5 0
|
3天前
|
JavaScript
Vue实战-将通用组件注册为全局组件
Vue实战-将通用组件注册为全局组件
5 0
|
3天前
|
JavaScript 前端开发
vue的论坛管理模块-文章评论02
vue的论坛管理模块-文章评论02
|
3天前
|
JavaScript Java
vue的论坛管理模块-文章查看-01
vue的论坛管理模块-文章查看-01
|
4天前
|
JavaScript
VUE里的find与filter使用与区别
VUE里的find与filter使用与区别
13 0
|
3天前
|
移动开发 JavaScript 应用服务中间件
vue打包部署问题
Vue项目`vue.config.js`中,`publicPath`设定为&quot;/h5/party/pc/&quot;,在线环境基于打包后的`dist`目录,而非Linux的`/root`。Nginx代理配置位于`/usr/local/nginx/nginx-1.13.7/conf`,包含两个相关配置图。
vue打包部署问题
|
3天前
|
JavaScript 前端开发
iconfont 图标在vue里的使用
iconfont 图标在vue里的使用
15 0