写在前面:
-
本篇内容内容主要讲述了,在使用 Konva
进行开发过程中遇到的一些问题。(既然是组件加载顺序,主要牵扯到的就是,父子组件的关系,父子组件的生命周期)
-
众所周知,Vue
中父子组件生命周期的执行顺序为:
// 挂载阶段
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
// 更新阶段
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
// 销毁阶段
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
-
然而,在某些情况下我们有其他需求,例如我们不得不让子组件的初始化在父组件初始化完成之后再进行(一般是针对mounted
),下面将进行详细说明
1、引用关系说明
- 最终目的:使用
Konva
库绘制组件,该组件由两个按钮、一个电平表、一个增益控制推杆,这些子组件组合起来构成所需组件,并将其绘制到Stage
中的Layer
上
-
Stage
和Layer
只有一个,所以应当写在App.vue中,使用时将Layer
传递给子组件(且这个Layer应当是响应式的);且由于要绘制所需组件,因此自然是要引用所需组件,即所需组件是App.vue的子组件
- 所需组件应当引用各个子组件,它是各个子组件的父亲
- 综上,是一个三层的继承关系,此外,由于有了
Stage
和Layer
才能绘制所需组件,有了所需组件才能绘制各个子组件,此时,各个控件的初始化顺序与生命周期刚好相反。
2、两层继承关系示例
假如说目前我只有两层继承关系,App.vue
和所需组件 Channel.vue
代码在下方展示,详细的内容我将在代码中使用注释详细说明,请按照注释编号顺序进行阅读和理解
-
App.vue
要点:
-
layer
依赖注入,使所有的子组件可以获取
-
父组件的
layer
进行依赖注入时需要使用响应式,以便于父组件知道 layer
的改变(类比C语言的直接传参和指针传参)
- 需要将所需组件引用、注册并展示到页面上
import Konva from 'konva';
import { computed } from 'vue';
// 5. 引入所需组件用于绘制和页面展示
import Channel from './components/Channel.vue';
export default {
// 2. 父组件将 layer 传递给子组件,子组件没有 layer 就无法绘制组件
provide() { // 依赖注入,所有子组件可获取
return {
// 3. 传递给子组件的 layer应当是响应式的,否则对子组件的修改无法同步到父组件的layer
layer: computed(() => this.layer), // 4. 响应式的区别,类比C语言的直接传参和指针传参
}
},
components: {
// 6. 注册子组件
Channel,
},
mounted() {
// 0.初始化组件
this.initializeKonva();
window.addEventListener("resize", this.handleResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
data() {
return {
stage: null,
layer: null,
};
},
methods: {
initializeKonva() {
this.stage = new Konva.Stage({
container: "frame",
width: window.innerWidth,
height: window.innerHeight,
});
// 1. 这里为了解耦和效率,全局使用一个layer
this.layer = new Konva.Layer();
this.stage.add(this.layer);
},
handleResize() {
this.stage.width(window.innerWidth);
this.stage.height(window.innerHeight);
this.stage.batchDraw();
},
},
};
/* 样式细节不表 */
-
Channel.vue
要点:
import Konva from 'konva';
export default {
// 1. 接收父组件依赖注入的 layer
inject: ['layer'],
components: {},
data() {return {};},
mounted() {
// 2. 使用 this.$nextTick(() => {}),在DOM更新之后执行回调函数
this.$nextTick(() => {
// 3. 初始化
this.initializeKonva();
});
},
methods: {
initializeKonva() {
this.group = new Konva.Group({
// ...
});
const backgroundRect = new Konva.Rect({
// ...
});
const textTop = new Konva.Text({
// ...
});
this.textLevel = new Konva.Text({
// ...
});
this.textGain = new Konva.Text({
// ...
});
const textBottom = new Konva.Text({
// ...
});
const line1 = new Konva.Line({
// ...
});
const line2 = new Konva.Line({
// ...
});
const line3 = new Konva.Line({
// ...
});
this.group.add(backgroundRect, textTop, this.textLevel, this.textGain, textBottom, line1, line2, line3);
// 4. layer 是通过依赖注入传递,inject接收的,使用 this 访问
this.layer.add(this.group);
// 5. 更新 layer
this.layer.draw();
},
},
};
3、三层及以上继承关系示例
- 在上面的内容中,使用
this.$nextTick(() => { 回调 })
解决了两层继承关系中的反向初始化顺序的问题。
- 但是这本质上更像是一种小聪明,当到了三层以上继承关系的时候这种方法不能有任何效果,因为子组件和孙子组件如果不同时使用
this.$nextTick(() => { 回调 })
总会有人在父组件之前初始化,而如果都用了 this.$nextTick(() => { 回调 })
那么它们两个本身的初始化顺序仍然是先子后父,一定会出问题。所以要使用其他的方式来解决这个问题
代码在下方展示,详细的内容我将在代码中使用注释详细说明,请按照注释编号顺序进行阅读和理解
-
Channel.vue
要点:
- 接收
layer
等不再赘述
- 使用
this.$nextTick(() => { 初始化代码 })
,会使得初始化代码在父组件的初始化完成后再执行
- 设置
flag
用于判断当前组件初始化是否完成,使用v-if="flag"
控制子组件初始化时机
import Konva from 'konva';
import SwitchButton from './SwitchButton.vue';
import LevelMeter from './LevelMeter.vue';
import Gain from './Gain.vue';
export default {
inject: ['layer'],
components: {
SwitchButton,
LevelMeter,
Gain,
},
data() {
return {
// ...
// 0. 准备一个flag用于确认初始化时机
flag: false,
group: null,
};
},
mounted() {
// 1. 存在父亲,切需要使用父亲中的 layer ,等待父组件初始化完成
this.$nextTick(() => {
this.initializeKonva();
// 2. 使用flag判断是否已经初始化完成
this.flag = true;
});
},
methods: {
initializeKonva() {
// ...
this.layer.add(this.group);
this.layer.draw();
},
handleDBChange(newDB) {
// ...
},
handleLevelChange(newLevel) {
// ...
},
},
};
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net