【手写 Vue2.x 源码】第二十七篇 - Vue 生命周期的实现

news2024/11/15 15:38:27

一,前言

上篇,主要介绍了数组依赖收集的实现

本篇,Vue 生命周期的实现


二,Vue.mixin 介绍

1,mixin 简介

Vue2 中可以通过 Vue.mixin 为 vue 进行功能扩展
开发中,经常使用 mixin 来为所有组件增加一些生命周期

2,mixin 使用

vue 初始化时,使用 beforeCreate 生命周期钩子
再通过 Vue.mixin 扩展对 beforeCreate 进行功能扩展
这样在实际执行时,多个 beforeCreate 会进行合并

3,生命周期的用法

// 使用 Vue.mixin 做全局扩展
Vue.mixin({
  beforeCreate(){
    console.log("全局:mixin-beforeCreate")
  }
})
let vm = new Vue({
  el: '#app',
  // 用法一:
  // beforeCreate(){},
  // 用法二:数组写法:逻辑较多需进行分类时吗,可拆分为多个函数
  beforeCreate:[
    function(){
      console.log("局部:new Vue-beforeCreate 1") // A 模块初始化
    },
    function(){
      console.log("局部:new Vue-beforeCreate 2") // B 模块初始化
    }
  ]
});

三,Vue 的 Global API

1,全局 api 和 实例 api 的使用

// 全局 api:对所有组件生效
Vue.component()
// 实例 api:仅对当前组件生效
new Vue({
  component:{}
})

2,全局 api 的实现原理

new Vue 组件初始化时:

  1. 通过 options 使用实例 api 声明,仅对当前组件生效;
  2. 通过 Vue.component 全局声明的属性将被合并到每一个组件中,全局生效;

四,Vue.mixin 实现

1,添加 mixin 方法

创建 Vue 全局 api 模块:src/global-api;

新建 src/global-api/index.js,为 Vue 添加 mixi 静态方法:

//src/global-api/index.js

export function initGlobalAPI(Vue) {
  Vue.mixin = function (options) {
    
  }
}

在 src/index.js 中调用,进行 vue global api 的初始化:

// src/index.js
import { initGlobalAPI } from "./global-api";
import { initMixin } from "./init";
import { lifeCycleMixin } from "./lifecycle";
import { renderMixin } from "./render";

function Vue(options){
  this._init(options);
}

initMixin(Vue)
renderMixin(Vue)
lifeCycleMixin(Vue)
initGlobalAPI(Vue) // 初始化 global Api

export default Vue;

2,实现 Global API

在全局属性Vue.options中存放属性,供全局使用:

// src/global-api/index.js

export function initGlobalAPI(Vue) {
  // 全局属性:Vue.options
  // 功能:存放 mixin, component, filte, directive 属性
  Vue.options = {}; 
  Vue.mixin = function (options) {
    
  }
  Vue.component = function (options) {}
  Vue.filte = function (options) {}
  Vue.directive = function (options) {}
}

3,多个Vue.mixin的合并策略

全局 mixin 也可以被多次调用:

Vue.mixin({
  beforeCreate(){
    console.log("全局:mixin-beforeCreate 1")
  }
})
Vue.mixin({
  beforeCreate(){
    console.log("全局:mixin-beforeCreate 2")
  }
})

此时,需对全局声明进行合并:

Vue.mixin = function (options) {
    // 需将多次传入的 options 与全局属性 Vue.options 进行合并
}

合并策略:

第一次合并:
parentVal:{}
childVal:{ beforeCreate:fn1 }
合并结果:{ beforeCreate:[fn1] }

第二次合并:
parentVal:{ beforeCreate:[fn1] }
childVal:{ beforeCreate:fn2 }
合并结果:{ beforeCreate:[fn1,fn2] }

所以,每次合并需要循环父亲(老值)和儿子(新值)依次进行合并
当新值存在,老值不存在时:添加到老值中

在 src/utils.js 添加工具方法 mergeOptions:

// src/utils.js

/**
 * 对象合并:将childVal合并到parentVal中
 * @param {*} parentVal   父值-老值
 * @param {*} childVal    子值-新值
 */
export function mergeOptions(parentVal, childVal) {
  let options = {};
  for(let key in parentVal){
    mergeFiled(key);
  }
  for(let key in childVal){
    // 当新值存在,老值不存在时:添加到老值中
    if(!parentVal.hasOwnProperty(key)){
      mergeFiled(key);
    }
  }
  function mergeFiled(key) {
    // 默认合并方法:优先使用新值覆盖老值
    options[key] = childVal[key] || parentVal[key]
  }
  return options;
}

4,生命周期的合并策略

策略模式:将不同生命周期的合并使用不同的策略做区分

// src/utils.js

let strats = {};  // 存放所有策略
let lifeCycle = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted'
];
lifeCycle.forEach(hook => {
  // 创建生命周期的合并策略
  strats[hook] = function (parentVal, childVal) {
    if(childVal){ // 儿子有值,需要进行合并
      if(parentVal){
        // 父亲儿子都有值:父亲一定是数组,将儿子合入父亲
        return parentVal.concat(childVal);  
      }else{
        // 儿子有值,父亲没有值:儿子放入新数组中
        // 注意:如果传入的生命周期函数是数组,已经是数组无需再包成数组
        if(Array.isArray(childVal)){
          return childVal;
        }else{
          return [childVal];
        }
      }
    }else{  // 儿子没有值,无需合并,直接返回父亲即可
      return parentVal;
    }
  }
})
// src/global-api/index.js

export function initGlobalAPI(Vue) {
  // 全局属性:Vue.options
  // 功能:存放 mixin, component, filte, directive 属性
  Vue.options = {}; 
  Vue.mixin = function (options) {
    this.options = mergeOptions(this.options, options);
    console.log("打印mixin合并后的options", this.options);
    return this;  // 返回this,提供链式调用
  }
  Vue.component = function (options) {}
  Vue.filte = function (options) {}
  Vue.directive = function (options) {}
}

5,测试

测试 Vue.mixin 中的生命周期合并结果:

image.png


五,全局与实例的生命周期合并

全局生命周期合并完成后,还要在和 new Vuechu 初始化中的局部声明再进行合并

new Vue 初始化时,会进入 _init 原型方法:

// src/init.js#initMixin

Vue.prototype._init = function (options) {
    const vm = this;
    // 此时需使用 options 与 mixin 合并后的全局 options 再进行一次合并
    vm.$options = mergeOptions(vm.constructor.options, options);
    
    ...
}

打印 vm.$options 查看合并后的结果:

image.png

问题:vm.constructor.options 和 Vue.options的区别?

此处的 vm 有可能是 vm 的子类:
Vue 的子类对 Vue 可能做了增强;子组件可能会继承 Vue;

Vue.options 就是指 Vue;而 vm.constructor 指子类(子组件)的构造函数;

六,生命周期的实现

1,创建生命周期执行函数

在src/lifecycle.js生命周期模块中,创建执行生命周期钩子函数 callHook:

// src/lifecycle.js

/**
 * 执行生命周期钩子
 *    从$options取对应的生命周期函数数组并执行
 * @param {*} vm    vue实例
 * @param {*} hook  生命周期
 */
export function callHook(vm, hook){
  // 获取生命周期对应函数数组
  let handlers = vm.$options[hook];
  if(handlers){
    handlers.forEach(fn => {
      fn.call(vm);  // 生命周期中的 this 指向 vm 实例
    })
  }
}

2,添加生命周期钩子

  • 当视图渲染前,调用钩子: beforeCreate
  • 视图更新后,调用钩子: created
  • 当视图挂载完成,调用钩子: mounted
// src/lifecycle.js

export function mountComponent(vm) {
  // vm._render():调用 render 方法
  // vm._update:将虚拟节点更新到页面上
  // 初始化流程
  // vm._update(vm._render());  
  // 改造
  let updateComponent = ()=>{
    vm._update(vm._render());  
  }

  // 当视图渲染前,调用钩子: beforeCreate
  callHook(vm, 'beforeCreate');

  // 渲染 watcher :每个组件都有一个 watcher
  new Watcher(vm, updateComponent, ()=>{
    console.log('Watcher-update')
    // 视图更新后,调用钩子: created
    callHook(vm, 'created');
  },true)

   // 当视图挂载完成,调用钩子: mounted
   callHook(vm, 'mounted');
}
  • watcher做视图更新前,调用钩子: beforeUpdate
  • 视图更新完成后,调用钩子: updated
// src/scheduler.js

/**
 * 刷新队列:执行所有 watcher.run 并将队列清空;
 */
function flushschedulerQueue() {
  // 更新前,执行生命周期:beforeUpdate
  queue.forEach(watcher => watcher.run()) // 依次触发视图更新
  queue = [];       // reset
  has = {};         // reset
  pending = false;  // reset
  // 更新完成,执行生命周期:updated
}

3,测试生命周期执行流程

Vue.mixin 中的 2 个 beforeCreate 钩子;
new Vue 中的 2 个 beforeCreate 钩子;
按照合并后的顺序依次执行完成;

image.png


七,结尾

本篇,Vue 生命周期的实现,主要涉及以下几点:

  • Vue.mixin 介绍
  • Vue 的 Global API
  • Vue.mixin 实现

下篇,diff 算法的流程分析


维护日志:

  • 20210708:修复“四-4,生命周期的合并策略”,当生命周期函数为数组时,无需二次包装
  • 20210806:修复排版问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习的相关软件框架下载安装

文章目录一、Anaconda1. Anaconda 的下载2. Anaconda 的安装3. Anaconda Navigator 打不开问题(不适用所有)二、PyTorch-CPU1. PyTorch 环境创建2. PyTorch 下载3. Jupyter 中使用 PyTorch三、Python 版本升级与包的维护1. 更新 Anaconda2. 查看与更新 p…

回归预测 | MATLAB实现SSA-LSSVM麻雀算法优化最小二乘支持向量机多输入单输出

回归预测 | MATLAB实现SSA-LSSVM麻雀算法优化最小二乘支持向量机多输入单输出 目录回归预测 | MATLAB实现SSA-LSSVM麻雀算法优化最小二乘支持向量机多输入单输出预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现SSA-LSSVM麻雀算法优化最小二乘支持向量机…

【MAUI】自动更新功能的安卓App

自动更新功能的安卓App自动更新主要下面4个步骤更新服务测试页面:MainPage.xaml测试自动更新主要下面4个步骤 1、获取最新版本号 2、提示用户发现更新,等待用户确认更新 3、下载最新的apk包 4、安装apk包 更新服务 为简单示例:直接在android平台文件夹…

Spring资源管理,Spring资源管理源码分析

文章目录一、Java标准资源管理1、Java 标准资源定位2、Java URL 协议扩展基于 java.net.URLStreamHandlerFactory基于 java.net.URLStreamHandler3、Java 标准资源管理扩展的步骤4、Spring为什么不用Java标准的资源管理二、Spring资源接口与实现1、Spring基本资源接口InputStre…

C++ 简单实现RPC网络通讯

RPC是远程调用系统简称,它允许程序调用运行在另一台计算机上的过程,就像调用本地的过程一样。RPC 实现了网络编程的“过程调用”模型,让程序员可以像调用本地函数一样调用远程函数。最近在做的也是远程调用过程,所以通过重新梳理R…

项目管理平台,如何助力CMMI3-5级高效落地?

近日CoCode旗下Co-ProjectV3.0智能项目管理平台全面升级,CoCode产品4大版本全新发布,用户不限版本30天免费试用;平台全面支持CMMI3-5级,助力CMMI高效落地。 一、4大版本全新发布 不限版本30天免费试用 Co-Project V3.0智能项目管理…

FPGA:组合逻辑电路的设计

文章目录组合逻辑电路的设计组合逻辑电路的设计步骤组合逻辑电路的设计举例例1例2组合逻辑电路的设计 根据实际逻辑问题,求出所要求逻辑功能的最简单逻辑电路。 组合逻辑电路的设计步骤 1.逻辑抽象:根据实际逻辑问题的因果关系确定输入、输出变量&…

【寒假每日一题】DAY8 倒置字符串

牛客网链接:传送门 【❤️温馨提示】自己做一遍,再看解析效果更佳哟 描述 将一句话的单词进行倒置,标点不倒置。输入描述: 每个测试输入包含1个测试用例: I like beijing. 输入用例长度不超过100输出描述&#xff1a…

Open3D 点云投影至指定平面(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 假设给定的平面为 a x + b y + c z + 1 = 0 ax+by+cz+1=0

Python基础(二十四):面向对象核心知识

文章目录 面向对象核心知识 一、面向对象三大特性 1、封装 2、继承 3、多态

音频音量调整中的ramp up down

在日常生活中不管是打电话还是听音乐,都会遇到音量不合适而去调整音量的情况。如果音量调整软件处理不好,就会听到pop noise。产生pop noise的原因是音量直接从当前值骤变到目标值,而不是缓慢的变。如果缓慢的变就不会有pop noise了。图1显示…

select for update是行锁还是表锁,还真得看情况

背景 看到许多写select for update是行锁还是表锁的文章,但每篇文章的结论好像都不太一样。同时,是行锁还是表锁的问题直接影响着系统的性能,所以特意为大家调研一番,也就有了本篇文章,一共为大家汇总验证了20个场景下…

MES系统选型攻略,优秀MES系统应具备哪些性质

在众多MES系统中,企业怎样才能找到最适合自己的产品?那么,一套高质量的MES系统,究竟有什么特点?随着全球经济一体化的发展,中美两国之间的贸易战争日趋白热化,中国作为一个生产大国,…

行为型模式 - 迭代器模式iterator

模式的定义与特点 迭代器模式(iterator Pattern),为的提是可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个…

ffmpeg源码编译vs2013版本

完整版安装ffmpeg 一、安装choco 1.Set-ExecutionPolicy AllSigned 2.Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.N…

链队基本操作(笔记版)

本节主要针对链栈的基本操作进行解析。 # coding:___utf-8___ # author:Guoxuan Sun time:2023/1/12 #链栈的基本操作 #链栈的创建与顺序栈的区别就是每个结点都有一个指针域 #同时链栈也是有两个指针front和rear #链栈中的front指针指向的结点是第一个结点,不是空…

关于MySQL中的存储引擎

存储引擎:(了解内容) 1、什么是存储引擎,有什么用? 存储引擎是mysql中特有的一个术语,其他数据库中没有。 存储引擎就是一个表存储/组织数据的方式。不同的存储引擎,表存储数据的方式不同。 目前…

基于Androidstudio的宠物交友app

需求信息: 客户端: 1:登录注册:用户可以通过自己的信息进行账号的注册 2:宠物信息:列表显示发布的宠物想,可以通过条件对宠物信息进行筛选,以及沟通意向点亮 3:宠物圈&am…

java调用python文件的几种方式

java调用python的契机来自于一个项目需要用到算法,但是算法工程师们写的python,于是就有了java后端调用python脚本的需求,中间遇到了许多问题,特此记录整理了一次。1、java调用python的方式有哪几种1.1 方法一:jpython…

选择排序.

一、简单选择排序 void select_sort(int a[], int len){ //len为数组长度for (int i 0; i < len-1; i){//n个数需要比较n-1趟int min i;//记录最小值的位置for (int j i1; j < len-1; j){if (a[j] < a[min]) min j;//更新最小值的位置} if (min ! i) swap(a[i], …