MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue生命周期钩子 mounted阶段的初始化操作技巧

2022-04-245.7k 阅读

Vue 生命周期之 mounted 阶段概述

在 Vue 应用的生命周期中,mounted 阶段是一个关键的时期。当 Vue 实例被创建并挂载到 DOM 上之后,mounted 钩子函数就会被调用。这个阶段标志着组件已经成功渲染到页面,并且相关的 DOM 元素已经可以被访问和操作。

生命周期钩子的调用顺序

在深入了解 mounted 阶段之前,有必要回顾一下 Vue 生命周期钩子的总体调用顺序。从创建 Vue 实例开始,依次会调用 beforeCreatecreatedbeforeMount,然后才是 mountedbeforeCreate 阶段,Vue 实例的初始化刚刚开始,数据观测和事件配置等尚未就绪;created 阶段,数据观测和事件配置已经完成,但此时还未开始挂载到 DOM;beforeMount 阶段,Vue 已经开始编译模板,即将把编译好的虚拟 DOM 挂载到真实 DOM 上,但此时真实 DOM 还未被替换;而到了 mounted 阶段,虚拟 DOM 已经成功渲染为真实 DOM 并挂载到页面中。

mounted 的触发时机

对于一个简单的 Vue 组件,当组件模板中的所有数据绑定、指令等都解析完成,并且组件的 DOM 元素已经被插入到父级 DOM 中时,mounted 钩子就会被触发。例如,下面是一个简单的 Vue 组件:

<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  mounted() {
    console.log('Component has been mounted.');
  }
};
</script>

在这个例子中,当 div#app 及其内部的 p 元素成功渲染到页面后,mounted 钩子中的 console.log 语句就会执行,输出 Component has been mounted.

初始化操作技巧 - DOM 操作

获取 DOM 元素

mounted 阶段,最常见的操作之一就是获取 DOM 元素。由于此时组件已经挂载到页面,所以可以安全地使用原生 JavaScript 的 DOM 操作方法来获取元素。例如,假设组件模板中有一个按钮,我们想在组件挂载后获取该按钮元素并添加一个点击事件:

<template>
  <div id="app">
    <button id="myButton">Click me</button>
  </div>
</template>

<script>
export default {
  mounted() {
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
      console.log('Button clicked!');
    });
  }
};
</script>

在上述代码中,mounted 钩子函数使用 document.getElementById 获取了按钮元素,并为其添加了一个点击事件监听器。

操作 DOM 元素样式

除了获取 DOM 元素,还可以在 mounted 阶段操作元素的样式。例如,我们想在组件挂载后为某个元素添加一个特定的 CSS 类:

<template>
  <div id="app">
    <div id="targetDiv">This is a div</div>
  </div>
</template>

<script>
export default {
  mounted() {
    const targetDiv = document.getElementById('targetDiv');
    targetDiv.classList.add('highlight');
  }
};
</script>

<style>
.highlight {
  background-color: yellow;
}
</style>

在这个例子中,mounted 钩子获取了 targetDiv 元素,并为其添加了 highlight 类,从而改变了该元素的背景颜色。

使用 ref 获取 DOM 元素

Vue 提供了 ref 特性,使得获取 DOM 元素更加方便和 Vue 风格。在模板中给元素添加 ref 属性,然后在 mounted 钩子中通过 this.$refs 来获取对应的元素。例如:

<template>
  <div id="app">
    <input type="text" ref="inputField">
  </div>
</template>

<script>
export default {
  mounted() {
    const input = this.$refs.inputField;
    input.focus();
  }
};
</script>

在上述代码中,通过 ref 特性为输入框元素命名,然后在 mounted 钩子中使用 this.$refs.inputField 直接获取该元素,并调用 focus 方法使其获取焦点。这种方式避免了直接使用原生 JavaScript 的 document.getElementById 等方法,使代码更具 Vue 组件化的特点。

初始化操作技巧 - 第三方库集成

集成 Chart.js

很多时候,我们需要在 Vue 组件中集成第三方图表库,如 Chart.js。在 mounted 阶段进行图表的初始化是一个很好的时机。首先,确保已经安装了 Chart.js:

npm install chart.js --save

然后,在 Vue 组件中使用:

<template>
  <div id="app">
    <canvas ref="chartCanvas"></canvas>
  </div>
</template>

<script>
import Chart from 'chart.js';

export default {
  data() {
    return {
      chartData: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June'],
        datasets: [
          {
            label: 'My First Dataset',
            data: [65, 59, 80, 81, 56, 55],
            fill: false,
            borderColor: 'rgb(75, 192, 192)',
            tension: 0.1
          }
        ]
      }
    };
  },
  mounted() {
    const ctx = this.$refs.chartCanvas.getContext('2d');
    new Chart(ctx, {
      type: 'line',
      data: this.chartData,
      options: {}
    });
  }
};
</script>

在上述代码中,在 mounted 钩子中获取到 canvas 元素的 2D 上下文,然后使用 Chart.js 创建了一个折线图。由于 mounted 阶段组件已经挂载,canvas 元素已经存在于 DOM 中,可以安全地进行图表初始化。

集成地图库(以 Leaflet 为例)

如果要集成地图库,如 Leaflet,同样可以在 mounted 阶段进行初始化。先安装 Leaflet:

npm install leaflet --save

然后在 Vue 组件中使用:

<template>
  <div id="app">
    <div id="map" ref="map"></div>
  </div>
</template>

<script>
import L from 'leaflet';

export default {
  mounted() {
    const map = L.map(this.$refs.map).setView([51.505, -0.09], 13);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap contributors'
    }).addTo(map);
  }
};
</script>

<style>
#map {
  height: 400px;
}
</style>

在这个例子中,mounted 钩子函数通过 this.$refs.map 获取到地图容器元素,然后使用 Leaflet 创建了一个地图实例,并添加了一个瓦片图层。

初始化操作技巧 - 数据请求与处理

发送 AJAX 请求

在很多实际应用中,组件在挂载后需要从服务器获取数据。Vue 中可以使用 axios 库来发送 AJAX 请求。首先安装 axios

npm install axios --save

然后在 Vue 组件中:

<template>
  <div id="app">
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      items: []
    };
  },
  mounted() {
    axios.get('/api/items')
    .then(response => {
        this.items = response.data;
      })
    .catch(error => {
        console.error('Error fetching data:', error);
      });
  }
};
</script>

在上述代码中,mounted 钩子函数使用 axios.get 发送了一个 GET 请求到 /api/items 端点,当请求成功时,将返回的数据赋值给 items 数组,从而在模板中进行渲染。如果请求失败,会在控制台输出错误信息。

处理复杂数据结构

有时候从服务器获取的数据可能是复杂的嵌套结构,需要在 mounted 阶段进行处理。例如,假设我们获取到的数据是一个包含多层嵌套数组和对象的数据结构,并且需要从中提取特定信息并整理成适合渲染的格式:

<template>
  <div id="app">
    <ul>
      <li v-for="subItem in processedItems" :key="subItem.id">{{ subItem.title }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      rawData: null,
      processedItems: []
    };
  },
  mounted() {
    axios.get('/api/complexData')
    .then(response => {
        this.rawData = response.data;
        this.processedItems = [];
        if (this.rawData && Array.isArray(this.rawData.mainArray)) {
          this.rawData.mainArray.forEach(mainItem => {
            if (Array.isArray(mainItem.subArray)) {
              mainItem.subArray.forEach(subItem => {
                this.processedItems.push({
                  id: subItem.id,
                  title: subItem.title
                });
              });
            }
          });
        }
      })
    .catch(error => {
        console.error('Error fetching data:', error);
      });
  }
};
</script>

在这个例子中,mounted 钩子函数首先获取数据,然后对复杂的 rawData 进行处理,提取出所需的 idtitle 信息,整理成 processedItems 数组用于模板渲染。

初始化操作技巧 - 事件绑定与解绑定

全局事件绑定

在某些情况下,可能需要在 mounted 阶段绑定全局事件。例如,监听窗口大小变化事件:

<template>
  <div id="app">
    <p>Window width: {{ windowWidth }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      windowWidth: window.innerWidth
    };
  },
  mounted() {
    window.addEventListener('resize', this.handleResize);
  },
  methods: {
    handleResize() {
      this.windowWidth = window.innerWidth;
    }
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize);
  }
};
</script>

mounted 钩子中,使用 window.addEventListener 绑定了 resize 事件到 handleResize 方法,当窗口大小改变时,handleResize 方法会更新 windowWidth 的值。同时,为了避免内存泄漏,在 beforeDestroy 钩子中解绑了该事件。

自定义事件绑定

在组件之间通信时,自定义事件绑定也是常见的操作。假设我们有一个父组件和一个子组件,父组件在 mounted 阶段监听子组件触发的自定义事件:

<!-- ParentComponent.vue -->
<template>
  <div id="parent">
    <ChildComponent @custom-event="handleCustomEvent"></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  methods: {
    handleCustomEvent(data) {
      console.log('Received custom event data:', data);
    }
  },
  mounted() {
    // 这里实际上不需要额外操作,因为 @custom-event 绑定在模板中已经完成
    // 但为了完整性说明在mounted阶段相关情况
    // 例如如果有条件绑定事件,可以在这里进行逻辑判断后绑定
  }
};
</script>

<!-- ChildComponent.vue -->
<template>
  <div id="child">
    <button @click="emitCustomEvent">Emit Event</button>
  </div>
</template>

<script>
export default {
  methods: {
    emitCustomEvent() {
      this.$emit('custom-event', { message: 'Hello from child' });
    }
  }
};
</script>

在这个例子中,父组件在模板中绑定了子组件的 custom-eventhandleCustomEvent 方法。虽然在 mounted 阶段这里没有额外的绑定操作,但如果有条件绑定事件的逻辑,可以在 mounted 中进行判断和绑定。

初始化操作技巧 - 性能优化相关

避免不必要的 DOM 操作

mounted 阶段进行 DOM 操作时,要注意避免不必要的操作,以提高性能。例如,如果需要对多个 DOM 元素进行样式修改,尽量批量操作而不是逐个操作。假设我们有一个列表,需要为每个列表项添加相同的样式:

<template>
  <div id="app">
    <ul>
      <li v-for="(item, index) in items" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: ['Item 1', 'Item 2', 'Item 3']
    };
  },
  mounted() {
    const listItems = document.querySelectorAll('li');
    const fragment = document.createDocumentFragment();
    listItems.forEach(item => {
      item.classList.add('highlight');
      fragment.appendChild(item);
    });
    document.querySelector('ul').appendChild(fragment);
  }
};
</script>

<style>
.highlight {
  color: red;
}
</style>

在上述代码中,通过创建一个文档片段 fragment,先将所有列表项添加到片段中并修改样式,最后一次性将片段添加到 ul 元素中,而不是逐个将列表项添加到 ul 并修改样式,这样可以减少浏览器重排和重绘的次数,提高性能。

延迟加载与按需初始化

对于一些资源密集型的初始化操作,如加载大型第三方库或复杂的图表,可以考虑延迟加载或按需初始化。例如,我们可以使用 setTimeout 来延迟初始化操作:

<template>
  <div id="app">
    <button @click="initChart">Initialize Chart</button>
  </div>
</template>

<script>
import Chart from 'chart.js';

export default {
  data() {
    return {
      chartInitialized: false
    };
  },
  methods: {
    initChart() {
      if (!this.chartInitialized) {
        setTimeout(() => {
          const ctx = document.createElement('canvas').getContext('2d');
          new Chart(ctx, {
            type: 'bar',
            data: {
              labels: ['A', 'B', 'C'],
              datasets: [
                {
                  label: 'Data',
                  data: [10, 20, 30]
                }
              ]
            },
            options: {}
          });
          this.chartInitialized = true;
        }, 1000);
      }
    }
  },
  mounted() {
    // 这里可以做一些其他初始化操作
    // 而将图表初始化延迟到用户点击按钮或其他合适时机
  }
};
</script>

在这个例子中,mounted 阶段不立即初始化图表,而是在用户点击按钮后,通过 setTimeout 延迟 1 秒进行图表初始化,这样可以避免在页面加载时就进行资源密集型操作,提高页面的加载性能。

特殊场景下的 mounted 初始化操作

动态组件中的 mounted

在使用动态组件时,mounted 钩子的行为有一些特殊之处。动态组件通过 is 指令来切换不同的组件。例如:

<template>
  <div id="app">
    <button @click="toggleComponent">Toggle Component</button>
    <component :is="currentComponent"></component>
  </div>
</template>

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

export default {
  components: {
    ComponentA,
    ComponentB
  },
  data() {
    return {
      currentComponent: 'ComponentA'
    };
  },
  methods: {
    toggleComponent() {
      this.currentComponent = this.currentComponent === 'ComponentA'? 'ComponentB' : 'ComponentA';
    }
  }
};
</script>

ComponentA.vueComponentB.vue 中分别有自己的 mounted 钩子:

<!-- ComponentA.vue -->
<template>
  <div id="componentA">
    <p>This is Component A</p>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log('Component A has been mounted.');
  }
};
</script>

<!-- ComponentB.vue -->
<template>
  <div id="componentB">
    <p>This is Component B</p>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log('Component B has been mounted.');
  }
};
</script>

当通过按钮切换组件时,每次新组件被挂载都会触发其 mounted 钩子。这意味着在动态组件场景下,要注意 mounted 钩子中初始化操作的重复执行问题。例如,如果在 mounted 中进行了数据请求,可能需要考虑缓存数据,避免重复请求。

服务端渲染(SSR)下的 mounted

在服务端渲染的 Vue 应用中,mounted 钩子的执行环境有所不同。在服务端,mounted 钩子不会被执行,因为服务端没有 DOM 环境。而在客户端,当 hydration(将服务端渲染的 HTML 与客户端 Vue 实例进行关联)完成后,mounted 钩子会被触发。

例如,对于一个使用 SSR 的 Vue 应用,假设我们有一个组件需要在客户端挂载后获取 DOM 元素:

<template>
  <div id="app">
    <p ref="message">This is a message</p>
  </div>
</template>

<script>
export default {
  mounted() {
    if (typeof window!== 'undefined') {
      const message = this.$refs.message;
      console.log('Message element:', message.textContent);
    }
  }
};
</script>

在上述代码中,通过 typeof window!== 'undefined' 来判断当前是否在客户端环境,只有在客户端环境下才进行 DOM 操作,这样可以避免在服务端渲染时由于没有 DOM 而导致的错误。同时,在 SSR 场景下,mounted 中的数据请求等操作可能需要与服务端的数据预取机制相结合,以确保客户端和服务端数据的一致性。

通过对 Vue 生命周期钩子 mounted 阶段各种初始化操作技巧的深入探讨,我们可以更好地利用这个阶段,为 Vue 应用的开发提供更高效、更健壮的实现方式。无论是 DOM 操作、第三方库集成、数据请求处理还是性能优化等方面,都有许多细节和技巧需要我们在实际开发中不断积累和应用。