Vue 先初始化父组件再初始化子组件的方法(自定义父子组件mounted执行顺序)

  • Vue 先初始化父组件再初始化子组件的方法(自定义父子组件mounted执行顺序)已关闭评论
  • 216 次浏览
  • A+
所属分类:Web前端
摘要

写在前面:本篇内容内容主要讲述了,在使用 Konva 进行开发过程中遇到的一些问题。(既然是组件加载顺序,主要牵扯到的就是,父子组件的关系,父子组件的生命周期)


写在前面:

  • 本篇内容内容主要讲述了,在使用 Konva 进行开发过程中遇到的一些问题。(既然是组件加载顺序,主要牵扯到的就是,父子组件的关系,父子组件的生命周期)

  • 众所周知,Vue中父子组件生命周期的执行顺序为:

    // 挂载阶段 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted  // 更新阶段 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated  // 销毁阶段 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed 
  • 然而,在某些情况下我们有其他需求,例如我们不得不让子组件的初始化在父组件初始化完成之后再进行(一般是针对mounted),下面将进行详细说明

1、引用关系说明

  • 最终目的:使用Konva 库绘制组件,该组件由两个按钮、一个电平表、一个增益控制推杆,这些子组件组合起来构成所需组件,并将其绘制到Stage中的Layer
    • StageLayer只有一个,所以应当写在App.vue中,使用时将Layer传递给子组件(且这个Layer应当是响应式的);且由于要绘制所需组件,因此自然是要引用所需组件,即所需组件是App.vue的子组件
    • 所需组件应当引用各个子组件,它是各个子组件的父亲
  • 综上,是一个三层的继承关系,此外,由于有了StageLayer才能绘制所需组件,有了所需组件才能绘制各个子组件,此时,各个控件的初始化顺序与生命周期刚好相反

2、两层继承关系示例

假如说目前我只有两层继承关系,App.vue 和所需组件 Channel.vue

代码在下方展示,详细的内容我将在代码中使用注释详细说明,请按照注释编号顺序进行阅读和理解

  • App.vue

    要点:

    • layer 依赖注入,使所有的子组件可以获取
    • 父组件的 layer 进行依赖注入时需要使用响应式,以便于父组件知道 layer 的改变(类比C语言的直接传参和指针传参)
    • 需要将所需组件引用、注册并展示到页面上
    <template>   <div id="app">     <div id="frame">       <!-- 7. 将所需子组件展示到页面 -->       <Channel />     </div>   </div> </template>  <script> 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();     },   }, }; </script>  <style scoped>     /* 样式细节不表 */ </style>  
  • Channel.vue

    要点:

    • 接收 layer

    • 使用 this.$nextTick(() => { 初始化代码 }),会使得初始化代码在父组件的初始化完成后再执行

      • this.$nextTick()Vue.js提供的一个方法,用于在DOM更新之后执行回调函数。它的作用是确保在下次DOM更新循环结束之后执行回调函数,以确保操作的准确性和可靠性。

      • Vue.js中,当数据发生改变时,Vue会异步地更新DOM。这意味着在修改数据后立即访问更新后的DOM可能无法得到正确的结果,因为此时DOM可能尚未完成更新。

        通过使用this.$nextTick()方法,我们可以将回调函数延迟到下一次DOM更新循环之后执行。在这个时候,Vue已经完成了所有的异步DOM更新,我们可以放心地操作更新后的DOM元素,确保获取到准确的结果。

    • 绘制完成后更新 layer

    <template>     <div>         <div ref="container"></div>     </div> </template>    <script> 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();         },     }, }; </script>    <style></style>    

3、三层及以上继承关系示例

  • 在上面的内容中,使用this.$nextTick(() => { 回调 })解决了两层继承关系中的反向初始化顺序的问题。
  • 但是这本质上更像是一种小聪明,当到了三层以上继承关系的时候这种方法不能有任何效果,因为子组件和孙子组件如果不同时使用 this.$nextTick(() => { 回调 }) 总会有人在父组件之前初始化,而如果都用了 this.$nextTick(() => { 回调 }) 那么它们两个本身的初始化顺序仍然是先子后父,一定会出问题。所以要使用其他的方式来解决这个问题

代码在下方展示,详细的内容我将在代码中使用注释详细说明,请按照注释编号顺序进行阅读和理解

  • Channel.vue

    要点:

    • 接收 layer 等不再赘述
    • 使用 this.$nextTick(() => { 初始化代码 }),会使得初始化代码在父组件的初始化完成后再执行
    • 设置flag用于判断当前组件初始化是否完成,使用v-if="flag"控制子组件初始化时机
    <template>     <div>         <div ref="container"></div>                  <!-- 3. v-if="flag" 控制子组件的初始化时机 -->                  <SwitchButton :btnNameIndex="0" :x="0" :y="group.height() / 17 + group.height() / 17 / 4" :parent="this.group"             v-if="flag" />         <SwitchButton :btnNameIndex="1" :x="0" :y="group.height() / 17 * 3 - group.height() / 17 / 3" :parent="this.group"             v-if="flag" />                  <!-- 4. :parent="this.group" 将this.group传递给子组件,命名为parent,这种传递方式默认为响应式,无需其他操作 -->                  <LevelMeter :x="0" :y="group.height() / 17 * 4 + group.height() / 17 / 2" :parent="this.group" v-if="flag"             @levelChangeEvent="handleLevelChange" />         <Gain :x="0" :y="group.height() / 17 * 4 + group.height() / 17 / 4" :parent="this.group" v-if="flag"             @dBChangeEvent="handleDBChange" />     </div> </template>    <script> 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) {             // ...         },     }, }; </script>    <style></style>