基于 vuestic-ui 实战教程 - 获取动态数据篇

1. axios安装

原始的quick start版本并没有用到axios所以应该是没有安装,保险起见我们就先安装个

npm install axios

2. request.ts 的编写

下载好了axios的依赖,那我们可以直接用啦,相信做过vue-element-admin的小伙伴肯定熟悉axios与js的整合写法,那换成ts又有什么需要注意的点呢,个人觉得最大的区别就是类型系统

JavaScript是一种动态类型语言,变量的类型是在运行时确定的,这带来了灵活性但也可能引发运行时错误。TypeScript则是一种静态类型语言,它在编译阶段就确定了变量的类型,并提供了强大的类型系统,包括基础类型、联合类型、交叉类型等,这有助于在编写代码时及早发现潜在的错误。

简单来说就是要指定变量的类型啦,为了方便后续开发我就把一些简单的前置知识铺垫一下

2.1 前置知识

2.1.1 typescript的基本类型

  • Boolean:最基本的数据类型,表示true或false值。在TypeScript中,布尔类型被称为 boolean。例如:let isDone: boolean = false;。
  • Number:所有数字在TypeScript中都是浮点数,类型为 number。TypeScript不仅支持十进制和十六进制字面量,还支持ECMAScript 2015中引入的二进制和八进制字面量。
  • String:字符串类型在TypeScript中用 string 表示。
  • Enum:枚举类型是TypeScript特有的,它允许我们定义一个集合的常量列表。
  • Any:表示任意类型的特殊类型,这在处理不确定类型的对象时非常有用。
  • Void:表示没有任何类型的特殊类型,通常用于没有返回值的函数。
  • NullUndefined:分别表示值为null和undefined的类型。
  • Never:表示永远不会有返回值的函数返回类型。

除了上述基本类型,TypeScript还支持Object类型以及Array类型等复杂类型,并且可以通过接口、类和泛型等高级特性来创建复杂的自定义类型。

2.1.2 方法(类/接口)语法

export class Axios {
  // ...
  request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
}

学会了编写方法的语法,后续自定义就方便很多,就以Axios源码中定义的方法为教学例子,可以看到

2.1.2.1 泛型
  • 在方法名前的T 、R、D就是泛型(不懂的去查一查)
  • 第一个T的类型是any,就代表我可以接受任意类型的T。而第二个泛型R的类型是AxiosResponse,这很简单吧,就是把T做为data封装成一个AxiosResponse对象做为返回值 , 这里点进去看源码就可以知道(如下就看data就好啦)
export interface AxiosResponse<T = any, D = any>  {
    data: T; // T做为data封装
 	status: number;
 	statusText: string;
 	headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
 	config: AxiosRequestConfig<D>;
 	request?: any;
} 
  • 有了一个点的分析第三个泛型D就很容易知道啦,其类型也是any
2.1.2.2 参数

跟函数的一般写法一样,在括号里面的就是方法的参数,可以有也可以没有 (config: AxiosRequestConfig<D>)
需要注意的是,ts中 a : b 放在前面的a是参数名字,方法后面的b是参数类型,切记不要搞反了

这里的参数就是接受一个AxiosRequestConfig类型的参数,其名字叫做config,而泛型D就不用我多说了吧,留给各位自行推导
2.1.2.2 返回值

不同于其他语言,ts的返回值是写在方法声明的最后,以:的形式 : Promise<R>
这个:后面定义的就是返回值的类型,具体的参数名不用写,这样方法就可以知道你返回的参数是什么(类型推断)

2.2 整合axios与ts实现

好啦,讲完一些我认为比较重要的前置语法知识,就可以进入实际的开发啦,大家要跟上👍
首先在src下新建/utils/request.ts

2.2.1 同一返回结果

后端开发的小伙伴肯定不陌生这个是什么东东,就是为了前端接收数据的规范化,为了能更好的解析数据,这块就根据你后端编写的统一返回有什么参数来写

/* 服务器返回数据的的类型,根据后端接口文档确定 */
export interface Result<T=any> {
    code: string, //状态码
    message: string, //状态信息
    data: T, //封装返回的数据
    timestamp: number //时间戳,为了方便微服务debug找问题,加上会方便很多
}

2.2.2 对axios示例的初始化

import axios from 'axios'
import type { AxiosError, InternalAxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'
import { message as Message } from 'ant-design-vue' //做消息提示
/* 初始化 */
const service: AxiosInstance = axios.create({
  baseURL: '/api', //后面做跨域请求映射
  timeout: 10000,  //超时访问时间

  headers: {
  	// 由于我们传的对象都是通过json的格式封装在请求体中,所以需要定义content-type
    'content-type': 'application/json; charset=utf-8'
  }
})

注意这里引入了一个新的依赖,所以需要下载一下,不然会报错找不到

npm install ant-design-vue

2.2.3 请求拦截器

发送前先对请求做一些操作,比如在请求头中加入一些东西

/* 请求拦截器 */
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
//   if (token) {
//     后续有需要登录权限认证再加
//   }
  return config
}, (error: AxiosError) => {
  Message.error(error.message);
  return Promise.reject(error)
})

2.2.4 响应拦截器

响应返回来了,我们一般都需要对其进行提取,返回给调用方法的应该就其中的data,所以需要拦截器进行判断提取

/* 响应拦截器 */
service.interceptors.response.use((response: AxiosResponse) => {
  const { code, message, data } = response.data
 
  // 根据自定义错误码判断请求是否成功
  if (code === 200) {
    // 将组件用的数据返回
    return data
  } else { //非200则出错了
    Message.error(message)
    return Promise.reject(new Error(message))
  }
}, (error: AxiosError) => {
  // 处理 HTTP 网络错误
  let message = ''
  // HTTP 状态码
  const status = error.response?.status
  switch (status) {
    case 401:
      message = 'token 失效,请重新登录'
      // 这里可以触发退出的 action
      break;
    case 403:
      message = '拒绝访问'
      break;
    case 404:
      message = '请求地址错误'
      break;
    case 500:
      message = '服务器故障'
      break;
    default:
      message = '网络连接故障'
  }
  
  Message.error(message)
  return Promise.reject(error)
})

2.2.5 外部调用的请求方法

前面写的类方法都是在当前文件有效,那么我们需要加入一个可导出的方法供外部调用嘛(前面加上export关键字)包含最常用的四个方法get post put delete , 当然像在js中写的method: 'post'这种方式在调用的时候在设定也是可以的,但是不够方便简洁,所以推荐用下面的方法导出外部方法

/* 导出封装的请求方法 */
export const http = {
    get<T=any>(url: string, config?: InternalAxiosRequestConfig) : Promise<T> {
      return service.get(url, config)
    },
   
    post<T=any>(url: string, data?: object, config?: InternalAxiosRequestConfig) :Promise<T> {
      return service.post(url, data, config)
    },
   
    put<T=any>(url: string, data?: object, config?: InternalAxiosRequestConfig) :Promise<T> {
      return service.put(url, data, config)
    },
   
    delete<T=any>(url: string, config?: InternalAxiosRequestConfig) : Promise<T> {
      return service.delete(url, config)
    }
  }

到这里就完成了ts对axios的基本封装,下面的工作就是根据后端提供的接口文档来封装对其的访问,在这里就讲解两种情况,post和get其他的就给各位读者自行推广啦

3. api实现

首先在src下新建/api/… 根据后端的接口来定义文件夹,比如我想做一个创建订单,那我就建立/api/Order/order.ts 文件,在其中定义对后端访问的接口(大家可以举一反三,以为每个人的需求不同嘛😁)

import { http } from '@/utils/request'
import type { OrderData , BuyRes } from './types'
 
/**
 * 创建订单业务
 */
export function buy(data: OrderData) {
	return http.post<string>('/business/gateway/order/buy', data);
}

/**
 * 订单业务
 */
export function getAll() {
	return http.get<OrderData[]>('/business/gateway/order/getAll');
}
 

通过调用我们刚刚封装好的http.post/get向后端异步发送请求,填入我们的url(省去 localhost:xxxx 后面的),和需要传递的数据
看到代码的时候肯定有读者会疑惑,http是我们刚才定义好的导出方法需要引入没错,那OrderData这些又是什么呢?
根据我们的方法逻辑需要以json的形式传入一个订单,后端就可以接收到数据并插入数据库然后返回插入结果(成功还是失败)

前面我们说过ts是有类型判断的语言,所以不能像js那样直接写,而是需要先定义好数据结构,这就是问什么需要定义这些的原因
首先在src下新建/api/…/types.ts,比如我这个例子中建立/api/Order/types.ts 文件如下所示,我根据我数据库中t_order的表项来添加属性(id是自增的所以一般不用添加)而返回结果用ts中数组类型来封装,对应着后端的List<Order>

/* order参数类型 */
export interface OrderData {
	userId: string,
	productId: string,
	count: number,
	money: number,
}

/* 使用类来定义默认值,相当于定义一个class实现上面的interface */
export class OrderDataDefaults {
	userId = "1";
	productId = "1";
	count = 10;
	money = 10;
}

3.1 bug解析 Cannot find module ‘@/utils/request’

由于原始的版本没有引入相应的简写依赖,所以不能够找到@ - …/src的映射关系。具体修改步骤如下

npm add @types/node
  1. 在 vite.config.js 中配置别名
//注意是在 export default defineConfig 中定义,不要写到外面去
resolve: {
   // Vite路径别名配置
   alias: {
     '@': path.resolve(__dirname,'./src') //就是文件路径名+/src = @ 
   }
 },
  1. 在 tsconfig.json 中新增path的定义(就是上面用到的path)
"baseUrl": "./",
"paths":{
  "@": ["src"],
  "@/*": ["src/*"],
}

然后刷新刷新应该就没有问题啦😁

4. 在对应vue页面中使用api

import { OrderData , OrderDataDefaults } from '@/api/order/types'
import { buy , getAll } from '@/api/order/order'

//直接引入写死了,搞个表单填入数据的工作就交给各位自己动手实现啦
const queryOrder : OrderData = new OrderDataDefaults();
//添加例子
const createOrder = async (orderData: OrderData) => {
  await buy(queryOrder)
  notify({ //友好提示
    message: `添加成功`,
    color: 'success',
  })
}

//查询例子
let list : OrderData[] = [] //跟getAll定义的返回值一样,初始化为一个空的数组

const getAllOrder = async () => {
  await getAll().then(data => {   //跟在js中写法一样通过 .then(..)的方式对返回到的数据做操作
    list = data;
    console.log(">>>>>",list);
  })
  notify({
    message: `添加成功`,
    color: 'success',
  })
}

而在下面的vue template中绑定上面的点击事件,每当点击按钮的时候就会调用后端方法创建订单

<VaButton @click="createOrder">Creat Order</VaButton>
<VaButton @click="getAllOrder">Get All Order</VaButton>

然鹅在测试的时候发现报错,后端debug跑一跑,发现根本没进来(os:你这也不行啊讲了一大堆最后还报错🙃)哈哈哈确实前面的代码是没问题的,各位读者不用怀疑,那么问题出在哪里呢?
不知各位使用前后端分离开发的时候遇到的第一个问题是不是请求跨域的问题,假设我后端请求9001,前端端口5173,那么就会产生跨域,而默认是不支持跨域访问的所以会导致我们的测试没有效果,那么下面就跟着我一起来解决这最后一个小问题吧

5. bug修复 请求跨域问题

在 vite.config.ts 文件中添加 proxy 配置,在这里替换我们前面baseURL定义的"/api"成真正的映射地址

server: {
    proxy: {
      "/api": { // “/api” 以及前置字符串会被替换为真正域名
        target: "http://localhost:9001/", // 请求后端的访问端口
        secure: false, // 请求是否为https
        changeOrigin: true, // 是否跨域
        //rewrite: (path) => path.replace(/^\/api/, "") //重写封装请求发送 增加复杂性,由后端gateway做拦截和rewrite
      }
    }
  }

能看到这里恭喜你完成了获取动态数据教程的学习,有疑问或者觉得讲的不对的地方欢迎评论区留言,大家一起讨论讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值