如何多次使用VueJS组件但仅执行一次created/mounted函数?
同一页面多次复用Vue组件时,如何仅发起一次AJAX请求获取数据?
我想要创建一个VueJS组件,实现以下功能:
- 在
mounted/created钩子中下载数据(选项列表) - 在Multiselect组件中展示下载的数据
- 用户完成选择后将选中数据传回父组件
初始示例代码如下:
<template> <div> <multiselect v-model="value" :options="options"></multiselect> </div> </template> <script> import Multiselect from 'vue-multiselect' export default { components: { Multiselect }, mounted() { this.getOptions(); }, methods:{ getOptions() { // 执行AJAX请求 // 将响应结果赋值给options } }, data () { return { value: null, options: [] } } } </script> <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
如果页面中仅调用一次该组件,实现起来较为简单。但问题在于我可能需要在同一页面中多次使用该组件,有时甚至达数十次,我不希望多次调用this.getOptions();方法。
有没有办法实现该组件,使其无论在页面中被使用多少次,AJAX请求仅执行一次?
补充说明:我知道可以在父组件中下载数据,再通过props传递给子组件,示例代码如下,但这种方式违背了组件的设计初衷:
props: { optionsPassedByParents: Array }, mounted() { if(this.optionsPassedByParents.length == 0) this.getOptions(); else this.options = this.optionsPassedByParents; },
解决方案
1. 用Promise缓存请求结果
在组件外部维护一个全局Promise缓存,第一次请求发起后,后续组件实例直接复用这个Promise的结果,避免重复请求:
<template> <div> <multiselect v-model="value" :options="options"></multiselect> </div> </template> <script> import Multiselect from 'vue-multiselect' // 全局缓存请求Promise,所有组件实例共享 let optionsPromise = null export default { components: { Multiselect }, data () { return { value: null, options: [] } }, async mounted() { await this.getOptions() }, methods:{ async getOptions() { if (!optionsPromise) { // 替换为你的实际AJAX请求(比如axios.get) optionsPromise = new Promise((resolve) => { // 模拟AJAX请求延迟 setTimeout(() => { const mockOptions = [ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, { label: '选项3', value: 3 } ] resolve(mockOptions) }, 1000) }) } // 等待Promise完成,直接复用结果 this.options = await optionsPromise } }, watch: { value(newVal) { // 将选中值传回父组件 this.$emit('update:modelValue', newVal) } } } </script> <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
2. 使用Vuex管理全局数据
如果项目已使用Vuex,可将数据存入全局状态,组件从Vuex获取数据,仅在数据为空时发起请求:
// store/modules/options.js const state = { options: [], isLoading: false } const mutations = { SET_OPTIONS(state, data) { state.options = data }, SET_LOADING(state, status) { state.isLoading = status } } const actions = { async fetchOptions({ commit, state }) { // 已有数据或正在加载时直接返回 if (state.options.length || state.isLoading) return commit('SET_LOADING', true) // 替换为实际AJAX请求 const response = await fetch('/api/options') const data = await response.json() commit('SET_OPTIONS', data) commit('SET_LOADING', false) } } export default { namespaced: true, state, mutations, actions }
组件中使用:
<template> <div> <multiselect v-model="value" :options="options" :loading="isLoading"></multiselect> </div> </template> <script> import Multiselect from 'vue-multiselect' import { mapState, mapActions } from 'vuex' export default { components: { Multiselect }, data () { return { value: null } }, computed: { ...mapState('options', ['options', 'isLoading']) }, methods: { ...mapActions('options', ['fetchOptions']) }, async mounted() { await this.fetchOptions() }, watch: { value(newVal) { this.$emit('update:modelValue', newVal) } } } </script> <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
3. 使用provide/inject跨组件共享数据
若不想用Vuex,可在根组件或公共父组件中发起请求,通过provide提供数据,子组件用inject接收,同时保留兜底请求逻辑:
// 根组件或公共父组件 export default { data() { return { globalOptions: [], isOptionsLoading: false } }, async created() { this.isOptionsLoading = true // 实际AJAX请求 const response = await fetch('/api/options') this.globalOptions = await response.json() this.isOptionsLoading = false }, provide() { return { getGlobalOptions: () => ({ options: this.globalOptions, isLoading: this.isOptionsLoading }) } } }
组件中使用:
<template> <div> <multiselect v-model="value" :options="options" :loading="isLoading"></multiselect> </div> </template> <script> import Multiselect from 'vue-multiselect' export default { components: { Multiselect }, inject: ['getGlobalOptions'], data () { return { value: null, localOptions: [], localLoading: false } }, computed: { options() { const globalData = this.getGlobalOptions() return globalData.options.length ? globalData.options : this.localOptions }, isLoading() { const globalData = this.getGlobalOptions() return globalData.isLoading || this.localLoading } }, async mounted() { const globalData = this.getGlobalOptions() // 全局无数据且未加载时,自己发起请求兜底 if (!globalData.options.length && !globalData.isLoading) { this.localLoading = true const response = await fetch('/api/options') this.localOptions = await response.json() this.localLoading = false } }, watch: { value(newVal) { this.$emit('update:modelValue', newVal) } } } </script> <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
内容的提问来源于stack exchange,提问作者Zhiyong Li
相关产品推荐
相关产品推荐

