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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<body>
<div id="app">
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
// el: "#app", // 和下面的 vm.$mount是一样的
// 如图,初始化时:当前这个实例 他的父亲是谁 儿子是谁(当有父子组件关系的时候可以看到 parent|children ) 有一套发布订阅 $on $emit
beforeCreate(){ // 回调函数
// 创建之前
console.log(this); // 打印的this依然是这个实例
// debugger;
// 混合 希望在每个组件中增加一些特定的属性,可以采用这个钩子, 但是这里不能取到data、methods、watch,基本上业务逻辑是不需要他的
console.log('before create');
},
created(){
// 当前这个组件实例 已经实现了数据劫持(data里面的数据已经加了getter、setter),把方法、计算属性也都挂载到了实例上,但是不能获取到真实的dom元素
// 这里从 beforeCreate()=>create()是依次执行的,如果beforeCreate()里面有异步操作 不会等待,会接着走create()
console.log('created'); // 创建完成 这里可以放 ajax,并且不会阻塞渲染的过程,当ajax回调时可以将数据绑定到实例上,缺点是不能操作dom
console.log(this);
// debugger;
},
data:{
a: 1,
},
beforeMount(){ // 调用render 但是一般不会增加业务逻辑
// 1、如果这里没有写 el: "#app" | vm.$mount("#app"); 不会执行到这里
// 2、 如果 vm.$mount(""); 为空,没有指定节点,那么默认会渲染到一个内存中的节点。 运行 报错:vue.js:634 [Vue warn]: Failed to mount component: template or render function not defined. (found in <Root>) 但是还是打印了'挂载之前',走到了这个钩子中
console.log('挂载之前');
},
// 针对beforeMount() 中的问题,下面来添加render() 或者 template
// 可以看到加了render之后 vm.$mount("");没有写挂载节点,页面没有报错; 如果有了render,就不会使用template,因为内部会把template渲染成render,在beforeMount()挂载之前调用render方法,优先级的话有render就不会调用template
// render(){
// console.log('render');
// },
// template: '<div>hello</div>',
// **********************************这个过程中会渲染子组件 像之前的那个切洋葱一样 的执行顺序
// 父 beforeMount => 子 beforeMount => 子 Mounted => 父 Mounted
mounted(){ // 一般会把ajax的操作放在mounted中,ajax是异步的,不会阻塞组件渲染
console.log('当前组件挂载完成');
console.log(vm.$el);
},
beforeUpdate(){ // 这个数据是应用在视图上的
console.log("数据更新之前"); //可以在这里增加一些数据更新,不会导致视图多次更新(用的很少)
},
// 组件化的好处:方便复用,比较好维护,减少不必要的渲染
// vue的更新方式是组件级别的
updated(){ // 这里不要再去更新数据,可能发生死循环
console.log("更新完成");
},
beforeDestroy(){ // 可以在这里做事件的移除 清空定时器
console.log("销毁前");
},
destroyed(){
console.log("销毁后");
}
})
// $mount 可以指定一个元素(放置真实节点的id),不指定元素的话,内部会默认渲染到一个内存中的节点
// vm.$mount("");
vm.$mount("#app");

console.log(vm.$el); // 这个el指 真实挂载的dom节点 现在页面中是没有内容的,浏览器打印:<div>hello</div>
// 使用$mount挂载而非el:'#app',优点是 可以自己将渲染好的元素插入到自己想放的节点中
// document.body.appendChild(vm.$el); // 这里自己手动将渲染后的结果放到页面中


// 默认是不会销毁的 方式有:手动移除组件、路由切换
vm.$destroy(); // 移除所有的观察者,移除监听事件 这时console.log中做改变数据测试,数据没有变化


// 每个组件都有这套流程

// 每一个生命周期、每一个钩子函数
// 钩子函数:当代码执行到特定阶段的时候会调用的函数,这个函数就叫做钩子函数,也可以说是回调函数

</script>
</body>

Vue中的生命周期

  • beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
  • created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  • updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

钩子函数中该做的事情

  • created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。
  • mounted 实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
  • destroyed 可以执行一些优化操作,清空定时器,解除绑定事件

组件的应用

父子组件挂载顺序

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
<body>
<div id="app">
<my></my>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 组件: 复用、方便维护、减少渲染
// 全局组件、局部组件 指令
// 如果把vue 在html中使用 注意1、html不支持自定义自闭和标签(<my/> 不行);2、标签名不要和原生的一样;3、如果组件名有大写的情况(<Aa></Aa>),因为html没有大写的标签,内部会全部转成小写aa,全部采用小写 + 短横线方式
// 组件特点:独立,每个组件间应该是不相关的,单向数据流
let obj = {};
Vue.component('my',{
template: '<div>my组件</div>',
data(){
return obj;
},
beforeMount(){
console.log('挂载前1');
},
mounted(){
console.log('挂载后1');
}
})

let vm = new Vue({
el: '#app',
beforeMount(){
console.log('挂载前');
},
mounted(){
console.log('挂载后');
}
})
// 执行顺序: 挂载前 挂载前1 挂载后1 挂载后
</script>
</body>

组件的注册

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
<!DOCTYPE html>
<body>
<div id="app">
<my></my>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 每个组件都应该有三部分 1)html 2)js 3) 样式
let component = {
template: `<div>儿子</div>`,
data(){
return {m:1}
},
beforeCreate(){ // 这里可以解释生命周期为beforeCreate时可以知道,它的父亲是谁 儿子是谁 这一说明
console.log(this);
console.log(this.$parent.$children[0] === this); // true
debugger;
}
};
// 全局注册
// Vue.component('my',component);

let vm = new Vue({
el: '#app',
components:{ // 局部注册 这个vm实例上注册组件
my:component
}
})
// 组件的使用三部: 1)导入一个组件 2) 注册 3)使用:在当前组件定义的模板中使用 如上面这个my组件只能在id=app的这个模板中使用
</script>
</body>

组件的通讯

将父组件的数据 通过儿子的属性传入
单向数据流 父组件将数据传递给儿子

入手示例

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
<body>

<div id="app">
<!-- 将父组件的数据 通过儿子的属性传入 -->
<my :mny="mny"></my>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 单向数据流 父组件将数据传递给儿子
let component = {
template: `<div>儿子{{mny}} <button @click="change">更改</button></div>`,
// props:['mny'], // 相当于在实例上添加了一个mny属性 this.mny = 100;
// 如果要对传入的值做校验,用如下写法:
props: {
mny: {
// 普通类型直接写默认值即可,如果是对象或者数组,必须写成函数返回值的效果
type: Number, // 类型校验 Object Array
default: 100, // 默认值检验 ()=>({a:1}) ()=>[1,2,3]
// required: true, // 必填校验
}
},
data(){
return {m:1}
},
methods:{
change(){
this.mny = 200; // 会报错,值是变了。子组件不应该去更改父组件的数据
}
},
beforeCreate(){
console.log(this.$parent.$children[0] === this); // true
}
};

let vm = new Vue({
el: '#app',
data:{
mny: 100
},
components:{
my:component
}
})

</script>
</body>
  • 组件: 复用、方便维护、减少渲染
  • 组件特点:独立,每个组件间应该是不相关的,单向数据流