实际场景
可能很多小伙伴都会遇到: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的部分代码
以上,是本文的全部内容,不对/不严谨的地方还请原谅
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: Stefanieiii 原文链接:https://juejin.im/post/6987220741313789989