Vue2.x(含组件)主流程源码笔记(一):前言及总流程概览

前言

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

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

概览

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

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

准备工作

npm 安装

1
2
3
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

代码环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"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

1
2
3
4
5
6
7
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 来看,无论是功能、性能、还是框架设计都远远不如,从中学到的东西很多很多。

---- 本文结束,感谢您的阅读 ----