文章目录
- 目标
- 过程与代码
- 安装相关库
- 封装网络请求相关代码
- 网络请求数据
- 网络请求数据操作封装
- pinia管理数据并封装
- tab栏改为动态数据
- 效果
- 本篇总结
- 总代码
- 修改或新建的文件
- service
- index
- modules的city
- request的config
- request的index
- store
- modules的city
- modules的loading
- city.vue
- 参考
目标
上一篇搭建了搜索框和Tab栏:【前端】Vue项目:旅游App-(7)city:搜索框search和标签页Tabs
本篇目标:
样式不变:
数据改为动态的:
数据从服务器获取:
http://123.207.32.32:1888/api/city/all或http://www.codercba.com:1888/api/city/all
注意将网络请求request和数据管理pinia 封装。
过程与代码
安装相关库
本篇要将网络请求到的数据进行处理,要用到pinia
:
npm install pinia
本篇使用的网络请求库:axios
:
npm install axios
封装网络请求相关代码
相关参考:coderwhy ts封装axios库 除去冗余代码 可直接使用 - 掘金 (juejin.cn)
service文件夹用来提供封装好的各种服务。我们在其中建立一个文件夹request,表示用来提供网络请求服务。
其中的index.js文件:封装好的axios网络请求。
import axios from "axios";
import { useLoadingStore } from "@/store/modules/loading";
import { baseURL, TIMEOUT } from "./config";
const loadingStore = useLoadingStore();
class HYRequest {
constructor(baseURL) {
this.instance = axios.create({
baseURL,
timeout: TIMEOUT,
});
}
request(config) {
loadingStore.changeLoading(true);
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
console.log("request err:", err);
reject(err);
})
.finally(() => {
loadingStore.changeLoading(false);
});
});
}
get(config) {
return this.request({ ...config, method: "get" });
}
post(config) {
return this.request({ ...config, method: "post" });
}
}
export default new HYRequest(baseURL);
其中的config.js用来封装网络请求相关的配置:
const baseURL = "http://123.207.32.32:1888/api";
const TIMEOUT = 5000;
export { baseURL, TIMEOUT };
注意,index.js代码中还有一个useLoadingStore的导入。
store中新建modules文件夹,里面的loading.js文件:
import { defineStore } from "pinia";
export const useLoadingStore = defineStore("loadingStore", {
state: () => {
return {
showLoading: false,
};
},
getters: {
isLoading: (state) => state.showLoading,
},
actions: {
changeLoading(isLoading) {
this.showLoading = !!isLoading;
},
toggleLoading() {
this.showLoading = isLoading;
},
},
});
网络请求数据
我们用封装好的网络请求库来请求数据。
注意:
- request中的index导出的是一个对象(
new HYRequest()
) - HYRequest.get的参数是对象(
...config
)
代码:
import HYRequest from '@/service/request'
function getAllCity() {
// request的index导出的是一个对象
return HYRequest.get({
// 参数也是一个对象
url:'/city/all'
}).then(res => {
console.log(res)
})
}
getAllCity()
效果:
得到了数据。观察一下数据,可知:获取到的数据是一个对象,里面的data属性是我们页面需要的数据。
data中有两个属性,分别对应两个tab的数据。
网络请求数据操作封装
请求到数据之后,我们要进行一些思考。
city.vue写的是显示和选择城市的页面,我们在这个页面里写“网络请求数据”的逻辑是否合适?是否利于维护?答案是否定的。
实际上,我们可以将city页面的所有网络请求的操作都写到一个文件里,city.vue只需要在需要数据时调用即可。简而言之,我们需要对city页面的所有网络请求操作进行封装。
我们在service文件夹中建立modules文件夹,所有页面的网络请求操作都放在这里。modules中建立city.js,所有city相关的网络请求操作都放在这里。
代码:
// 此文件保存所有city页面的网络请求
import HYRequest from '@/service/request'
export default function getAllCity() {
// request的index导出的是一个对象
return HYRequest.get({
// 参数也是一个对象
url: '/city/all'
})
}
city.vue:
import getAllCity from '@/service/modules/city'
getAllCity()
接下来我们再进行一些思考。
我们在city中只需要导入就可以得到数据了。但是,显然city不会只有一次网络请求数据。也就是说,每次网络请求都需要import一次,这样也会让city.vue代码变得复杂。更重要的是,这些import是相似的,我们可以把它们也封装起来。
我们在service文件夹下新建index.js,里面保存所有会被使用的service:导出所有导入的模块。
// 此文件导入并导出所有要使用的service
export * from '@/service/modules/city'
在city.vue中调用:
import { getAllCity } from "@/service";
getAllCity()
效果:
pinia管理数据并封装
接下来我们再进行一些思考。
我们已经把所有的网络请求都封装了,在vue页面只需要调用网络请求获取数据使用就行了。但是,我们获取到的数据是一个对象,对象中的data才是我们需要的数据。
思考:在vue的页面中进行处理数据的逻辑是否合适?是否利于维护?
答案是否定的。显然,我们既然已经完成了网络请求的封装,自然也会想到要完成数据处理和存储的封装。
预想:我们把请求到的数据和对数据的处理封装到一个文件里,把处理好的数据导出。在vue的页面中只需要直接使用处理好的数据即可。
这里就要用到pinia。
在store的modules中新建city.js,city.vue页面所有的进行网络请求和数据都封装到这里。
// city.vue页面所有的进行网络请求和数据都封装到这里
import { getAllCity } from "@/service";
import { defineStore } from "pinia";
const useCityStore = defineStore('city', {
state: () => {
return {
allCity: {}
}
},
actions: {
// 调用网络请求
async fetchAllCity() {
const res = await getAllCity()
this.allCity = res.data
}
}
})
export default useCityStore
在vue中:
const cityStore=useCityStore()
cityStore.fetchAllCity()
// cityStore是响应式的
const { allCity } = storeToRefs(cityStore)
console.log(allCity)
效果:
tab栏改为动态数据
tab栏改为动态数据后,若服务器那边的数据发生了改变(如data有了三个属性),我们这里的代码是不用改的。
<van-tabs v-model:active="TabActive">
<template v-for="(value, key, index) in allCity">
<van-tab :title="value.title"></van-tab>
</template>
</van-tabs>
效果
不变。
本篇总结
本篇写了很多的封装,这里对它们之间的关系进行总结。
city.vue需要的数据都存在store中。
store会进行网络请求得到数据,网络请求的代码在service。
本项目views文件夹中所有页面需要的数据都在store的modules中,store的modules所有存储的数据来源都是网络请求得到的。所有的views页面的网络请求都存在service的modules中,store只需调用就可以得到数据。
service中的request是封装axios库。
总代码
修改或新建的文件
service
存放各种网络请求。
index
把要用的所有service导入并导出。
// 此文件导入并导出所有要使用的service
export * from '@/service/modules/city'
modules的city
封装city页面的所有网络请求。
// 此文件保存所有city页面的网络请求
import HYRequest from '@/service/request'
export function getAllCity() {
// request的index导出的是一个对象
return HYRequest.get({
// 参数也是一个对象
url: '/city/all'
})
}
request的config
封装网络请求相关配置。
const baseURL = "http://123.207.32.32:1888/api";
const TIMEOUT = 5000;
export { baseURL, TIMEOUT };
request的index
封装axios。
import axios from "axios";
import { useLoadingStore } from "@/store/modules/loading";
import { baseURL, TIMEOUT } from "./config";
const loadingStore = useLoadingStore();
class HYRequest {
constructor(baseURL) {
this.instance = axios.create({
baseURL,
timeout: TIMEOUT,
});
}
request(config) {
loadingStore.changeLoading(true);
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then((res) => {
resolve(res.data);
})
.catch((err) => {
console.log("request err:", err);
reject(err);
})
.finally(() => {
loadingStore.changeLoading(false);
});
});
}
get(config) {
return this.request({ ...config, method: "get" });
}
post(config) {
return this.request({ ...config, method: "post" });
}
}
export default new HYRequest(baseURL);
store
封装pinia。
modules的city
city页面的所有数据。
// city.vue页面所有的进行网络请求和数据都封装到这里
import { getAllCity } from "@/service";
import { defineStore } from "pinia";
const useCityStore = defineStore('city', {
state: () => {
return {
allCity: {}
}
},
actions: {
// 调用网络请求
async fetchAllCity() {
const res = await getAllCity()
this.allCity = res.data
}
}
})
export default useCityStore
modules的loading
封装网络请求需要的模块。
import { defineStore } from "pinia";
export const useLoadingStore = defineStore("loadingStore", {
state: () => {
return {
showLoading: false,
};
},
getters: {
isLoading: (state) => state.showLoading,
},
actions: {
changeLoading(isLoading) {
this.showLoading = !!isLoading;
},
toggleLoading() {
this.showLoading = isLoading;
},
},
});
city.vue
将tab的数据改为动态的。
<template>
<div class="city top-page">
<!-- show-action:显示 “取消” -->
<van-search shape="round" v-model="value" show-action placeholder="城市/区域/位置" @search="onSearch"
@cancel="onCancel" />
<van-tabs v-model:active="TabActive">
<template v-for="(value, key, index) in allCity">
<van-tab :title="value.title"></van-tab>
</template>
</van-tabs>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { showToast } from 'vant';
import useCityStore from '@/store/modules/city'
import { storeToRefs } from 'pinia';
const value = ref('');
const TabActive = ref(0);
const onSearch = (val) => showToast(val);
const onCancel = () => {
showToast('取消');
}
// tabs的数据
const cityStore = useCityStore()
cityStore.fetchAllCity()
// cityStore是响应式的
const { allCity } = storeToRefs(cityStore)
</script>
<style lang="less" scoped>
</style>
参考
Cannot read properties ofundefined(reading‘data‘)
coderwhy ts封装axios库 除去冗余代码 可直接使用 - 掘金 (juejin.cn)