【Vue实战技巧】如何处理多个组件依赖同一个接口数据?__Vue.js__前端
发布于 3 年前 作者 banyungong 716 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

实际场景

可能很多小伙伴都会遇到:A、B、C组件都依赖了同一个接口的数据,每次加载A、B、C都要调一次接口;很显然,虽然这么做能满足需求,但!会存在如下问题:

  • 重复请求
  • 代码冗余,每个组件/页面都要编写从获取数据、赋值、绑定数据的流程
  • 不够懒人

解决方案

方案一 本地缓存 + 获取判断

推荐指数: ⭐⭐

思路:在组件内部,判断是否已有本地缓存数组。是,拿来就用;否,发起一次请求,并且缓存起来。

缺点:冗余!冗余!

//组件A
created() {
    // 判断是否有本地缓存
    if (window.localStorage.getItem('key') {
        this.data = JSON.parse(window.localStorage.getItem('key'))
    } else {
        // 没有缓存,发起请求
        fetch()
            .then(res => {
                this.data = res.data
                // 设置缓存
                window.localStorage.setItem('key', JSON.stringify(res.data))
            })
    }
}

// 组件B
created() {
    // 同组件A一样,复制粘贴
}

方案二 挂载入口获取 + vuex的state(Vue.observable)

推荐指数: ⭐

思路:在页面初始化的时候,逐个异步请求那些多个地方用到的数据,然后数据挂载到state上,用到的地方只需要mapState对应的key值(如果vuex太重,可以使用vue2.6的observable维护一份js数据源)

缺点:性能差,请求数量太多可能会影响页面的加载;不能做到需要数据时再去加载;数据及时性较差

在组件里面直接引入state

// 组件A
computed: {
    listA: () => this.$store.state.listA,
    listB: () => this.$store.state.listB,
}

相关store的准备工作

// main.js 入口文件
this.$store.dispatch('fetchA')
this.$store.dispatch('fetchB')
this.$store.dispatch('fetchC')

// state.js
const store = new Vuex.Store({
  state: {
    listA: [],
    listB: [],
    listC: []
  },
  ...
})

// mutation.js
updateA(state, payload) {
    state.listA = payload
},
updateB(state, payload) {
    state.listB = payload
}

// action.js
fetchA({ commit }) {
    setTimeout(() => {
        commit('updateA', [1, 2, 3])
    }, 1000)
}
fetchB({ commit }) {
    setTimeout(() => {
        commit('updateB', [4, 5, 6])
    }, 1000)
}

方案三 vuex + getter、setter

前面两个方案都能满足需求,但某些场景上不够优雅,比如代码冗余,性能不好等等;如果我们能够在【当某个变量被get的时候,做出处理,如果变量值为空,去异步获取,反之,直接返回该变量值,并且依赖到的地方自动更新呢】

有了,他就是Object.defineProperty

推荐指数: ⭐⭐

思路:通过遍历拦截state每个键的get、set,当第一次get的时候没有数据,去异步获取,反之,直接返回数据;

例子:下面是完整的代码,可以看到当点击显示组件的时候,控制台在1s后发出了request B的log,而且多次切换显示,也不会再发出请求了

注意:只描述大概思路,部分地方不严谨还请原谅

关键代码

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {
  listA: [],
  listB: []
}

const requestMap = {
  listA: (state) => {
    setTimeout(() => {
      state.listA = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }, 3000)
  },
  listB: (state) => {
    setTimeout(() => {
      console.log('request B')
      state.listB = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }, 1000)
  }
}

Object.keys(state).forEach(key => {
  // 处理vue初始化get导致提前执行函数
  let vueInitCount = 0
  const defaultValue = state[key]
  // 避免循环调用,当获取listA,实际上返回的是__listA
  // 设置listA,实际设置的是__listA
  const privateKey = `__${key}`
  Object.defineProperty(state, key, {
    get () {
      if (vueInitCount < 2) {
        vueInitCount++
        return defaultValue
      }
      if (!this[privateKey] || (Array.isArray(this[privateKey]) && this[privateKey].length === 0)) {
        const fun = requestMap[key]
        if (fun) {
          fun(state)
        }
      }
      return this[privateKey] || defaultValue
    },
    set (val) {
      this[privateKey] = val
    }
  })
})

export default new Vuex.Store({
  state,
})

模板代码

// app.vue
<template>
  <div id="app">
    <HelloWorld v-if="show" msg="Welcome to Your Vue.js App"/>
    <div @click="showCom">显示</div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      show: false
    }
  },
  methods: {
    showCom () {
        this.show = !this.show
    }
  },
}
</script>

组件代码

// helloworld.vue
<template>
  <div class="hello">
    <h1>我是其他组件</h1>
    <div>{{listA}}</div>
    <div>{{listB}}</div>
  </div>
</template>
<script>
export default {
  computed: {
    listA () {
      return this.$store.state.listA
    },
    listB () {
      return this.$store.state.listB
    }
  }
}
</script>

另附参考vue的部分代码

image.png

以上,是本文的全部内容,不对/不严谨的地方还请原谅

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: Stefanieiii 原文链接:https://juejin.im/post/6987220741313789989

回到顶部