composition-api方式下的数据管理

828 阅读4分钟

讲到数据存储,我们一般想到的就是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配置就可以了。