讲到数据存储,我们一般想到的就是vuex,创建一个全局仓库,保存我们的数据。 学习了vue3的思想以后就不想再回到vue2 options方式的写法了,但是又因为vue3的一些兼容性问题,vue官方也提供了vue2模式下composition的类库,
使用方式:
在入口文件下引入composition-api类库
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
在当前开发周期下,正好有一个春运H5项目,H5页面不像CRM系统一样复杂,页面结构相对简单,而之前的CRM开发中为了更方便数据使用,我们使用vuex维护数据状态。既然都使用了composition-api方法,那么我们就可以使用hooks的思想代替vuex复杂(相对)的数据管理模式,将数据和数据操作封装在一起。
整个Hooks封装了页面中需要的数据,接口请求的loading状态,接口请求,接口请求结果
import { ref, reactive, onMounted } from '@vue/composition-api';
import { getMainDataApi } from '@/api/main.api';
import { toast } from '@/utils/native';
export default function useMainData() {
// 这里有一个reactive的大坑,见下方解析
const mainData = reactive({
activeNoticeList: [],
advertising: {
extMsg: {
img: ''
}
},
topicCarouselList: [],
redPackage: {
extMsg: {
rpkAmount: 0
}
},
currentGameMapOrderNo: -1
});
const isLoading = ref(false);
const isEmpty = ref(false);
const getMainData = async () => {
isLoading.value = true;
try {
const result = await getMainDataApi();
isLoading.value = false;
if (result.code == 200) {
Object.assign(mainData, result.data);
return;
}
isEmpty.value = true;
toast('前方做任务的小伙伴在排队,请稍后再试~');
} catch (error) {
console.log(error);
isEmpty.value = true;
isLoading.value = false;
}
};
onMounted(getMainData);
return {
isLoading,
isEmpty,
getMainData
};
}
reactive定义了一个空对象,然后下方数据更新时去改变数据想的是不是很美好,但是事实总是事与愿违😔 组件里面拿到的数据始终没有更新!难道是响应式不管用了?网上搜了很多方法,也没错啊,认真观察了博客上好像都定义了初始值,最后加了以后,还真的管用了!😌 为了加深理解去看了一下composition-api库的源码,源码里面关于reactive方法有这样一个判断,Object.getOwnPropertyDescriptor(),会去获取对象里面的单个属性的描述符,这里我们获取不到property,接下来执行setupAccessControl方法,val是undefined,执行结束,执行proxy方法, 得到的也是undefined所以没有变成响应式。
解决了这个问题那么接下来就可以愉快的在组件里使用useMainData啦
<Chessboard :orderNo="result.currentGameMapOrderNo" />
import useMainData from '@/composables/useMainData';
export default {
setup() {
const { isLoading, mainData } = useMainData();
return { mainData, isLoading }
}
}
理想状态下是不是哪里使用hooks的数据,直接调用这个hooks就好了。
因为useMainData已经封装好了数据和请求了,通过 const { isLoading, mainData } = useMainData(); 那么直接在子组件中 就可以取到了,
但是这样会存在一个问题,就是接口会再次调用,我们仔细看封装的useMainData方法,在onMounted生命周期我们调用了接口,所以还是不能完美处理;
然后给mainData这个数据抽离到hooks方法外面,import { mainData } from '@/composables/useMainData'; 这里就实现了子组件不会在组件构建完成以后再次调用接口了,
但是这样的方式就违背了hooks的思想,所以还是需要去完善,核心问题是我们useHooks要全局唯一,如果调用过一次,那就不需要再次调用了,所以我们只需要判断一下如果这个hooks存在,就直接取数据而不再调用。
这里使用weakMap保存我们定义的hooks,weakMap可以将function作为key进行保存。
基于这些问题,我们可以使用一个useModel来实现复用钩子状态的需求
const map = new WeakMap()
const useModel = (hook) => {
if (!map.get(hook)) {
let ans = hook()
map.set(hook, ans)
}
return map.get(hook)
}
const mainData = () => {
// 上面封装的hooks
}
export default function useMainData() {
return useModel(mainData)
}
这里首页已经完成了,接下来还有几个页面也有类似的功能,然后无脑式进行一些列页面hooks的封装,页面这里都开发完成了。
但是作为一个有追求的前端,能简化代码行数一定要简化呀!每个页面除了一些api接口不一样,其他的逻辑都类似,还有就是要兼容一下对象的数据处理。
import { ref, reactive, onMounted } from '@vue/composition-api';
import { toast } from '@/utils/native';
import config from './config'; // hooks api及初始对象处理
// weakMap只能接受对象作为key,api参数是string类型,改成Map
const map = new Map();
const useModel = (api, notInvokeOnMounted) => {
if (!map.get(api)) {
const ans = mainDataHook(api, notInvokeOnMounted);
map.set(api, ans);
}
return map.get(api);
};
const mainDataHook = (apiKey, notInvokeOnMounted) => {
const loading = ref(false);
const initResult = config[apiKey].initResult;
const result = initResult ? reactive(initResult) : ref(null); //对象的响应式定义和一般数据的定义
const getData = async () => {
loading.value = true;
try {
const res = await config[apiKey].api();
loading.value = false;
if (res.code == 200) {
if (initResult) {
Object.assign(result, res.data);
} else {
result.value = res.data;
}
return;
}
toast('前方做任务的小伙伴在排队,请稍后再试~');
} catch (error) {
console.log(error);
loading.value = false;
}
};
!notInvokeOnMounted && onMounted(getData); // 有些页面不需要在OnMounted生命周期进行数据请求
return {
result,
loading,
finished,
getData
};
};
export default function useStore(api, notInvokeOnMounted) {
return useModel(api, notInvokeOnMounted);
}
这里所有的页面都可以使用useStore这个hooks了,后续再增加页面逻辑也只需要增加一下config配置就可以了。