Skip to content

源码笔记(一):前言及总流程概览

前言

Vue 是一套用于构建用户界面的渐进式框架,被设计为可以自底向上逐层应用。本系列不会刻意梳理讲解 APIVue 的用法,官方文档已经有清晰的讲解。

本系列文章作为笔记,用于记录 vue2.x 的构建(含组件)主流程。

概览

根据 vue 构建 demo 的生命周期及其数据变化的更新作为流程线及相关,本系列文章一共分为以下章节:

  1. 前言及总流程概览
  2. 源码结构及调试相关介绍
  3. beforeCreate 阶段
  4. created 阶段
  5. beforeMount 阶段
  6. mount 阶段之生成 vnode
  7. mount 阶段之生成 dom
  8. update 阶段(上)
  9. update 阶段(下)
  10. destroyed 阶段

准备工作

npm 安装

bash
yarn add vue
yarn add vue-loader vue-style-loader vue-template-compiler css-loader -D
yarn add @babel/core babel-loader webpack-cli webpack webpack-dev-server html-webpack-plugin -D

代码环境

json
  "dependencies": {
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@babel/core": "^7.9.0",
    "babel-loader": "^8.1.0",
    "css-loader": "^3.4.2",
    "html-webpack-plugin": "^4.0.4",
    "vue-loader": "^15.9.1",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.42.1",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  }

版本不同,源码略微有差异。

demo 文件

本项目 demo 开源在 github,欢迎交流学习。

index.html

html
<div id="main">
  <Bpp></Bpp>
  <div v-on:click="plus">a:{{a}},计算属性:{{compute}}</div>
  <App name="one" v-bind:num="a"></App>
  <div v-on:click="hide">====点击让第二个App组件卸载====</div>
  <App name="two" v-if="isShow"></App>
</div>

index.js

js
import Vue from 'vue';
import App from './app.vue';
new Vue({
  el: '#main',
  components: {
    App,
    Bpp: () => import('./bpp.vue'),
  },
  data: {
    info: { name: 'korey', age: 28 },
    isShow: true,
  },
  computed: {
    compute() {
      return this.info.age + 1;
    },
  },
  methods: {
    plus() {
      this.info.age++;
    },
    hide() {
      this.isShow = false;
    },
  },
  watch: {
    info: {
      handler(val, oldVal) {
        console.log('变量info变化了:', val, oldVal);
      },
      deep: true,
    },
  },
  beforeCreate() {
    console.log(`vue beforeCreate`);
  },
  created() {
    console.log(`vue created`);
  },
  beforeMount() {
    console.log(`vue beforeMount`);
  },
  mounted() {
    console.log(`vue mounted`);
  },
  beforeUpdate() {
    console.log('vue beforeUpdate');
  },
  updated() {
    console.log('vue updated');
  },
});

app.vue

vue
<template>
  <div id="app">
    <div>{{ num ? `num:${num}` : `` }}</div>
    <Child class="example"></Child>
  </div>
</template>

<script>
import Child from './child.vue';
export default {
  name: 'app',
  components: {
    Child,
  },
  props: {
    num: Number,
    name: String,
  },
  beforeCreate() {
    console.log(`App beforeCreate`);
  },
  created() {
    console.log(`App ${this.name} created`);
  },
  beforeMount() {
    console.log(`App ${this.name} beforeMount`);
  },
  mounted() {
    console.log(`App ${this.name} mounted`);
  },
  beforeUpdate() {
    console.log(`App ${this.name} beforeUpdate`);
  },
  updated() {
    console.log(`App ${this.name} updated`);
  },
  beforeDestroy() {
    console.log(`App ${this.name} beforeDestroy`);
  },
  destroyed() {
    console.log(`App ${this.name} destroyed`);
  },
};
</script>

<style>
.example {
  color: red;
}
</style>

bpp.vue

vue
<template>
  <div id="bpp">
    <div>{{ message }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '异步Bpp组件',
    };
  },
  beforeCreate() {
    console.log('async Bpp beforeCreate');
  },
  created() {
    console.log('async Bpp created');
  },
  beforeMount() {
    console.log('async Bpp beforeMount');
  },
  mounted() {
    console.log('async Bpp mounted');
  },
  beforeDestroy() {
    console.log('async Bpp beforeDestroy');
  },
  destroyed() {
    console.log('async Bpp destroyed');
  },
};
</script>

child.vue

vue
<template>
  <div id="child">
    <div>Child组件</div>
  </div>
</template>

<script>
export default {
  beforeCreate() {
    console.log('Child beforeCreate');
  },
  created() {
    console.log('Child created');
  },
  beforeMount() {
    console.log('Child beforeMount');
  },
  mounted() {
    console.log('Child mounted');
  },
  beforeDestroy() {
    console.log('Child beforeDestroy');
  },
  destroyed() {
    console.log('Child destroyed');
  },
};
</script>

webpack.config.js

js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'development',
  devtool: 'hidden-source-map',
  entry: {
    main: path.resolve(__dirname, './src/index.js'),
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js',
  },
  resolve: {
    extensions: ['.js', '.vue'],
    alias: {
      vue$: 'vue/dist/vue.esm.js',
    },
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: ['vue-loader'],
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
    }),
  ],
};

为什么要写这个系列

vue 过去的 1.x 到现在主流的 2.x 到即将发布的 3.x 版本,网上的各种相关教程多不胜数。但纸上得来终觉浅,只有自己亲自来梳理一遍,才更能对 vue 有更深刻的认知。

在这之前曾自己手撸了一个类 vue 使用 proxy 实现一个简单完整的 MVVM 库,如今整体对比 vue 来看,无论是功能、性能、还是框架设计都远远不如,从中学到的东西很多很多。