Vue 实战技巧

hookEvent,原来可以这样监听组件生命周期

内部监听生命周期函数

在 Vue 组件中,可以用过$on,$once 去监听所有的生命周期钩子函数,如监听组件的 updated 钩子函数可以写成 this.$on(‘hook:updated’, () => {})

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
<template>
<div class="echarts"></div>
</template>
<script>
export default {
mounted() {
this.chart = echarts.init(this.$el);
// 请求数据,赋值数据 等等一系列操作...
// 监听窗口发生变化,resize组件
window.addEventListener("resize", this.$_handleResizeChart);
},
updated() {
/*干了一堆活*/
//
},
created() {
/*干了一堆活*/
},
beforeDestroy() {
// 组件销毁时,销毁监听事件
window.removeEventListener("resize", this.$_handleResizeChart);
},
methods: {
$_handleResizeChart() {
this.chart.resize();
}
// 其他一堆方法
}
};
</script>

将监听resize事件与销毁resize事件放到一起,现在两段代码分开而且相隔几百行代码,可读性比较差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default {
mounted() {
this.chart = echarts.init(this.$el);
// 请求数据,赋值数据 等等一系列操作...

// 监听窗口发生变化,resize组件
window.addEventListener("resize", this.$_handleResizeChart);
// 通过hook监听组件销毁钩子函数,并取消监听事件
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("resize", this.$_handleResizeChart);
});
},
updated() {},
created() {},
methods: {
$_handleResizeChart() {
// this.chart.resize()
}
}
};

外部监听生命周期函数

情境:想在外部监听组件的生命周期函数,使用一个第三方组件,需要监听第三方组件数据的变化,但是组件又没有提供 change 事件。
Vue 支持在外部监听组件的生命周期钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<!--通过@hook:updated监听组件的updated生命钩子函数-->
<!--组件的所有生命周期钩子都可以通过@hook:钩子函数名 来监听触发-->
<custom-select @hook:updated="$_handleSelectUpdated" />
</template>
<script>
import CustomSelect from "../components/custom-select";
export default {
components: {
CustomSelect
},
methods: {
$_handleSelectUpdated() {
console.log("custom-select组件的updated钩子函数被触发");
}
}
};
</script>

小项目还用 Vuex?用 Vue.observable 手写一个状态管理吧

在前端项目中,有许多数据需要在各个组件之间进行传递共享,这时候就需要有一个状态管理工具,一般情况下,我们都会使用 Vuex,但对于小型项目来说,就像 Vuex 官网所说:“如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex”。这时候我们就可以使用 Vue2.6 提供的新 API Vue.observable 手动打造一个 Vuex

创建 store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from "vue";

// 通过Vue.observable创建一个可响应的对象
export const store = Vue.observable({
userInfo: {},
roleIds: []
});

// 定义 mutations, 修改属性
export const mutations = {
setUserInfo(userInfo) {
store.userInfo = userInfo;
},
setRoleIds(roleIds) {
store.roleIds = roleIds;
}
};

在组件中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>{{ userInfo.name }}</div>
</template>
<script>
import { store, mutations } from "../store";
export default {
computed: {
userInfo() {
return store.userInfo;
}
},
created() {
mutations.setUserInfo({
name: "***"
});
}
};
</script>

开发全局组件,你可能需要了解一下 Vue.extend

Vue.extend是一个全局 Api,平时我们在开发业务的时候很少会用到它,但有时候我们希望可以开发一些全局组件比如Loading,Notify,Message 等组件时,这时候就可以使用Vue.extend
同学们在使用element-uiloading时,在代码中可能会这样写

1
2
3
4
5
6
7
8
9
10
11
// 显示loading
const loading = this.$loading();
// 关闭loading
loading.close();

// 这样写可能没什么特别的,但是如果你这样写
const loading = this.$loading();
const loading1 = this.$loading();
setTimeout(() => {
loading.close();
}, 1000 * 3);

这时候你会发现,我调用了两次 loading,但是只出现了一个,而且我只关闭了 loading,但是 loading1 也被关闭了。这是怎么实现的呢?我们现在就是用 Vue.extend + 单例模式去实现一个 loading

开发 loading 组件

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
<template>
<transition name="custom-loading-fade">
<!--loading蒙版-->
<div v-show="visible" class="custom-loading-mask">
<!--loading中间的图标-->
<div class="custom-loading-spinner">
<i class="custom-spinner-icon"></i>
<!--loading上面显示的文字-->
<p class="custom-loading-text">{{ text }}</p>
</div>
</div>
</transition>
</template>
<script>
export default {
props: {
// 是否显示loading
visible: {
type: Boolean,
default: false
},
// loading上面的显示文字
text: {
type: String,
default: ""
}
}
};
</script>

开发出来loading组件之后,如果需要直接使用,就要这样去用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="component-code">
<!--其他一堆代码-->
<custom-loading :visible="visible" text="加载中" />
</div>
</template>
<script>
export default {
data() {
return {
visible: false
};
}
};
</script>

但这样使用并不能满足我们的需求

  1. 可以通过 js 直接调用方法来显示关闭
  2. loading 可以将整个页面全部遮罩起来

通过 Vue.extend 将组件转换为全局组件

改造 loading 组件,将组件的 props 改为 data

1
2
3
4
5
6
7
8
export default {
data() {
return {
text: "",
visible: false
};
}
};

通过 Vue.extend 改造组件

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
// loading/index.js
import Vue from "vue";
import LoadingComponent from "./loading.vue";

// 通过Vue.extend将组件包装成一个子类
const LoadingConstructor = Vue.extend(LoadingComponent);

let loading = undefined;

LoadingConstructor.prototype.close = function () {
// 如果loading 有引用,则去掉引用
if (loading) {
loading = undefined;
}
// 先将组件隐藏
this.visible = false;
// 延迟300毫秒,等待loading关闭动画执行完之后销毁组件
setTimeout(() => {
// 移除挂载的dom元素
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
// 调用组件的$destroy方法进行组件销毁
this.$destroy();
}, 300);
};

const Loading = (options = {}) => {
// 如果组件已渲染,则返回即可
if (loading) {
return loading;
}
// 要挂载的元素
const parent = document.body;
// 组件属性
const opts = {
text: "",
...options
};
// 通过构造函数初始化组件 相当于 new Vue()
const instance = new LoadingConstructor({
el: document.createElement("div"),
data: opts
});
// 将loading元素挂在到parent上面
parent.appendChild(instance.$el);
// 显示loading
Vue.nextTick(() => {
instance.visible = true;
});
// 将组件实例赋值给loading
loading = instance;
return instance;
};

export default Loading;

在页面使用 loading

1
2
3
4
5
6
7
8
9
10
import Loading from "./loading/index.js";
export default {
created() {
const loading = Loading({ text: "正在加载。。。" });
// 三秒钟后关闭
setTimeout(() => {
loading.close();
}, 3000);
}
};

通过上面的改造,loading 已经可以在全局使用了,如果需要像 element-ui 一样挂载到 Vue.prototype 上面,通过 this.$loading 调用,还需要改造一下

将组件挂载到 Vue.prototype 上面

1
2
3
4
5
6
Vue.prototype.$loading = Loading;
// 在export之前将Loading方法进行绑定
export default Loading;

// 在组件内使用
this.$loading();