Skip to content
微信公众号

mpvue 生命周期源码分析

mpvue 源码调试

通过 mpvue 的 webpack 打包配置我们知道 mpvue 及其他一些库文件被打包入 dist/wx/common/vendor.js 文件中,所以实际调试的是 vendor.js,这里我们有两种调试选择:在代码中加入 debugger 指令或在 vendor.js 中加入断点。

debugger 调试

我们可以在 JS 脚本中加入断点指定 debugger 进入调试流程,然后从 debugger 的位置再跳入 mpvue 的源码,如我们在 main.js 中加入 debugger:

js
/* main.js */
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false
App.mpType = 'app'

debugger
const app = new Vue(App)
app.$mount()

小程序运行后,会在执行 const app = new Vue(App) 之前被中断:

我们可以看到 main.js 被 webpack 编译成了 app.js,并且源码内容也进行了一些变更,造成这些变化的主要原因是 webpack 采用原生 js 的语法实现了模块化编译导致的,这点并不会影响我们调试程序,我们只需要关注自身的源码逻辑即可。此时我们可以按 F11 或点击调试面板中的 step into 按钮进入 Vue 的构造函数代码中:

注:直接点击 step into 会跳入 webpack 的模块化加载方法中,此时继续点击 step into 或 step over 执行即可,直到进入 Vue 的代码逻辑中

进入 Vue 的构造函数后,我们可以看到 Vue 的源码被集成进 vendor.js 中,同时在右侧的 Call Stack 程序执行栈中也可以看到我们从 app.js 跳入了 vendor.js 的 Vue 构造函数中了:

至此我们就可以愉快地调试 mpvue 源码了。

断点调试

我们还可以直接在 vendor.js 中加入断点的方式来调试 mpvue 源码。首先在微信小程序开发工具 Sources 标签下,找到 vendor.js 源码中 Vue 构造函数的位置,然后在代码前加入断点:

这样在所有调用 Vue 实例化的源码处,都会自动中断,我们可以对代码进行调试。

mpvue 生命周期源码分析

下面我们通过调试的方式了解 mpvue 的执行阶段,这样有助于我们进一步了解 mpvue 的生命周期。Vue 的实例化从 _init 方法开始:

js
function Vue$3 (options) {
  if (false
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}

_init 方法完成了整个 Vue 的实例化过程,它的核心源码如下:

js
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');

if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}

整个 mpvue 的生命周期就包含在上述代码中。

beforeCreate

beforeCreate 之前主要指向了一些初始化操作,为 Vue 实例一些必要属性和方法,这部分逻辑较少,值得注意的是 initRender (vm) 方法中对 render 函数中使用的方法进行了定义:

js
function initRender (vm) {
  // ...
  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
  // ...
}

vm.$createElement 是在 Web 开发下 Vue 渲染界面的核心方法,但是在小程序中这一方法并不需要,因为渲染的逻辑方法了改变,小程序通过 wxml 对界面进行渲染。当 beforeCreate 之前的方法被执行完毕后,Vue 会调用我们自定义的 beforeCreate 生命周期函数:callHook(vm, 'beforeCreate'),如果定义了多个 beforeCreate 则会依次进行执行:

created

created 之前会执行 initInjections 和 initProvide 方法,这两个方法是为了初始化 Vue 的组件化通信机制:inject 和 provide 特性,我们可以先不关注,重点是看 initState 方法:

js
function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

initState 方法非常重要,因为它完成了 props、methods、data、computed 和 watch 5 个属性的初始化,这也是为什么在 created 生命周期函数执行时,可以通过如 this.data 或 this.props 获得属性值的原因。在以上三个初始化方法执行后,Vue 会调用我们自定义的 created 生命周期函数。

小程序生命周期

由于我们实例化 Vue 时未传入 el 参数,所以下列代码不会执行:

js
if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}

此时 Vue 实例化已经完成,程序继续执行 main.js 中的 $mount 方法:

js
const app = new Vue(App)
// Vue 已经实例化完成,将执行 $mount 方法
app.$mount()

$mount 是 Vue 原型上的方法,也就是说必须实例化后才能访问,所以我们需要先实例化 Vue 之后才能访问该方法,它的源码如下:

js
Vue$3.prototype.$mount = function (el, hydrating) {
  var this$1 = this;

  var options = this.$options;

  if (options && (options.render || options.mpType)) {
    var mpType = options.mpType; if ( mpType === void 0 ) mpType = 'page';
    return this._initMP(mpType, function () {
      return mountComponent(this$1, undefined, undefined)
    })
  } else {
    return mountComponent(this, undefined, undefined)
  }
}

通过上述源码可以看到,$mount 方法的主要用途判断 mpType 的类型,并且实例化 App 或 Page,这是 mpvue 和 Vue 不同之处,mpvue 将不执行界面的更新动作,都将交由小程序框架程序。实例化 Page 后小程序框架会完成界面的渲染。首先看下面一行代码:

js
var mpType = options.mpType; if ( mpType === void 0 ) mpType = 'page';

它从 options 中获取 mpType 参数,如果 mpType 为 undefined(void 0),则 mpType 的默认值为 page,也就是说 App 类型的 Vue 实例,必须手动指定 mpType,上述 main.js 源码中指定 mpType 为 app,所以它是一个 App 实例:

js
/* main.js */
App.mpType = 'app' // 指定 App.mpType = 'app',表明这是一个 App 实例

const app = new Vue(App)
app.$mount()

明确 mpType 后就执行 _initMp 方法:

js
return this._initMP(mpType, function () {
  return mountComponent(this$1, undefined, undefined)
})

_initMp 方法包含两个参数:mpType 和一个回调函数,它的核心源码如下,由于 _initMp 方法源码较多,所以这里只展示与生命周期相关的内容:

js
function initMP (mpType, next) {
  var rootVueVM = this.$root;
  if (!rootVueVM.$mp) {
    rootVueVM.$mp = {};
  }

  var mp = rootVueVM.$mp;

  mp.mpType = mpType;

  if (mpType === 'app') {
    global.App({
      onLaunch: function onLaunch (options) {
        callHook$1(rootVueVM, 'onLaunch', options);
        next();
      },
      onShow: function onShow (options) {
        callHook$1(rootVueVM, 'onShow', options);
      }
    });
  } else {
    var app = global.getApp();
    global.Page({
      onLoad: function onLoad (query) {
        callHook$1(rootVueVM, 'onLoad', query);
      },
      onShow: function onShow () {
        callHook$1(rootVueVM, 'onShow');
      },
      onReady: function onReady () {
        callHook$1(rootVueVM, 'onReady');
        next();
      },
      // 省略了其他生命周期函数定义
    });
  }
}

上述源码核心有两点:

  • 第一,当 mpType 为 app 时,调用 global.App 方法实例化了一个 App 对象,在 App 构造器对象中执行了在 Vue 实例中定义的 onLaunch 和 onShow 生命周期函数。在 onLaunch 执行完毕后会调用我们传入的回调函数 next,继续完成 Vue 的生命周期。
  • 第二,当 mpType 为 page 时,调用 global.Page 方法实例化了一个 Page 对象,在 Page 构造器对象中执行了在 Vue 实例中定义的 onLoad、onShow、onReady 等生命周期函数。在 onReady 执行完毕后会调用我们传入的回调函数 next,继续完成 Vue 的生命周期。

需要注意的是虽然这些生命周期函数定义在 Vue 中,但执行过程仍有小程序进行控制。

breforeMount

在 next 回调函数执行后,会继续执行 mountComponent 方法:

js
return this._initMP(mpType, function () {
  return mountComponent(this$1, undefined, undefined)
})

mountComponent 方法核心源码如下:

js
function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
  }
  callHook(vm, 'beforeMount');

  var updateComponent = function () {
    vm._update(vm._render(), hydrating);
  };

  vm._watcher = new Watcher(vm, updateComponent, noop);
  hydrating = false;

  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

可以看到在小程序生命周期执行完毕后会进入 mountComponent 方法,随后就会执行 beforeMount 生命周期函数。

mounted

我们继续分析 mountComponent 方法,在 beforeMount 后,Vue 会执行一个关键步骤实例化渲染 Watcher:

js
vm._watcher = new Watcher(vm, updateComponent, noop);

这一步是实现 mpvue 响应式的关键,虽然 mpvue 并不依赖 Vue 进行渲染,但是仍然需要完成响应式功能,即修改 data、props 等对象的属性时,能够动态触发界面变更,这一步仍然需要 Vue 的特性来实现,这样我们才能使用 Vue 中的各种优质特性。实例化渲染 Watcher 后会调用 mounted 生命周期函数。至此整个 mpvue 的实例化加渲染过程完成。

本站总访问量次,本站总访客数人次
Released under the MIT License.