深入剖析Restful API前端接口模型架构

前端 潘老师 1个月前 (03-20) 23 ℃ (0) 扫码查看

一、前言

之前在前公司接触到一种很有意思的权限校验接口组合方式,在当时的Vue项目里,所有权限校验接口都是在一个Model对象中,借助修饰器来实现的。我觉得挺有趣,就用React模拟了一下这个实现过程,下面就给大家详细讲讲。

二、useMutation实现

先给大家看看我模拟出来的React的useMutation钩子函数,这个钩子函数能让代码更好理解。它的代码在utils/useMutation.ts文件里,具体内容如下:

// utils/useMutation.ts

import { useState } from'react';

// 定义useMutation函数,接收一个包含url、method、variables的配置对象
export function useMutation<T, P>(options: {
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
variables: (params: T) => any;
}) {
// 定义loading状态,用于表示请求是否正在进行,初始值为false
const [loading, setLoading] = useState(false);
// 定义data状态,用于存储请求返回的数据,初始值为null
const [data, setData] = useState

(null);
// 定义error状态,用于存储请求过程中发生的错误,初始值为null
const [error, setError] = useState(null);

// 定义mutate函数,用于发起请求
const mutate = async (params: T) => {
// 请求开始,设置loading为true,清除之前的错误
setLoading(true);
setError(null);

// 根据传入的参数生成查询字符串
const queryParams = new URLSearchParams(options.variables(params)).toString();
// 根据请求方法拼接url,如果是GET请求,将查询字符串拼接到url后面
const url = options.method === 'GET'? `${options.url}?${queryParams}` : options.url;

try {
// 发起fetch请求
const response = await fetch(url, {
method: options.method,
headers: {
'Content-Type': 'application/json',
},
});
// 打印响应信息,方便调试
console.log('Response:', response);

// 如果响应状态码表示成功
if (response.ok) {
// 解析响应数据为JSON格式,并存储到data状态中
const result = await response.json();
setData(result);
} else {
// 如果响应失败,抛出错误
throw new Error('Request failed');
}
} catch (err) {
// 如果请求过程中发生错误,将错误信息存储到error状态中
setError(err);
} finally {
// 请求结束,设置loading为false
setLoading(false);
}
};

// 返回包含mutate函数、loading状态、data状态和error状态的对象
return { mutate, loading, data, error };
}

这里实现的useMutation其实是把从接口定义到函数实现的整个过程都整合到一起了。虽然这么做在一定程度上降低了结耦复用率,不过也让代码更集中处理权限校验接口相关的逻辑。

三、Model的构建与使用

接下来看看Model类,我们在这个类里使用useMutation来实现权限校验接口的装饰。代码如下:

import { useMutation } from "../utils/useMutation";

// 定义User数据类型,包含接口返回的各种信息
interface UserData {
result: number;
err_msg: string;
data: {
id: number; // 用户ID
dep: string; // 部门
Per: string; // 权限
};
}

// 定义List数据类型,包含接口返回的列表相关信息
interface ListData {
result: number;
err_msg: string;
data: Array<{ id: number; name: string; dep: string; Per: string }>;
}

// 定义Model类,在类的构造函数中初始化请求方法
export class Model {
// 初始化用户信息请求方法,传递参数id获取用户数据
user = useMutation<{ id: number }, UserData>({
url: 'http://localhost:3000/user',
method: 'GET',
variables: (p) => ({ id: p.id }),
});

// 初始化列表信息请求方法,传递分页参数获取列表数据
list = useMutation<{ page: number, size: number }, ListData>({
url: 'http://localhost:3000/list',
method: 'GET',
variables: (p) => ({ page: p.page, size: p.size }),
});
}

在这个Model类里,我们挂载了userlist两个属性,分别对应获取用户信息和列表信息的接口。部分类型定义可通通忽略,这里我便在Model身上去挂载了这些属性,打印看看。不过,刚定义完的时候,这些属性里的数据都是空的,因为接口还没有真正发起请求,在浏览器的network面板里也看不到请求记录。

要想让接口发起请求,我们需要在业务层调用它们。这里使用单例模式导出Model对象,这样在全局都能使用同一个对象的属性。下面是业务层的代码示例:

import React, { useEffect, useState } from'react';
import {useApollo} from 'apllo.js';
import { Model } from './api/model';

// 定义一个React组件GameAppModel
const GameAppModel: React.FC = () => {
// 使用useApollo获取Model实例
const model = useApollo(Model);

// 组件挂载时打印model.user和model.list信息
useEffect(()=>{
console.log('model.user:', model.user);
console.log('model.list:', model.list);
})

// 定义用户数据获取状态和列表数据获取状态
const [isUserFetched, setIsUserFetched] = useState(false);
const [isListFetched, setIsListFetched] = useState(false);

// 定义获取用户数据的函数
const handleFetchUser = async () => {
// 重置用户数据获取状态
setIsUserFetched(false);
// 调用model.user的mutate方法获取用户数据,假设传递用户id为1
await model.user.mutate({ id: 1 });
// 设置用户数据获取状态为true
setIsUserFetched(true);
};

// 定义获取列表数据的函数
const handleFetchList = async () => {
// 重置列表数据获取状态
setIsListFetched(false);
// 调用model.list的mutate方法获取列表数据,假设获取第1页,10条数据
await model.list.mutate({ page: 1, size: 10 });
// 设置列表数据获取状态为true
setIsListFetched(true);
};

// 如果正在获取用户数据或列表数据,显示加载中的提示
if (model.user.loading || model.list.loading) {
return
<div>Loading...</div>
;
}

// 如果获取用户数据或列表数据时发生错误,显示错误提示
if (model.user.error || model.list.error) {
return
<div>Error occurred while fetching data.</div>
;
}

return (
<div>
<h2>User Info:</h2>
{/* 定义获取用户信息的按钮,根据loading状态禁用按钮并显示不同文字 */}
<button disabled="disabled">
{model.user.loading? 'Loading User...' : 'Fetch User Info'}
</button>
{/* 如果已经获取到用户数据,显示数据;否则显示未获取到数据的提示 */}
{isUserFetched &amp;&amp; model.user.data? (
<pre>{JSON.stringify(model.user.data, null, 2)}</pre>
) : (

No user data fetched

)}
<h2>Item List:</h2>
{/* 定义获取列表信息的按钮,根据loading状态禁用按钮并显示不同文字 */}
<button disabled="disabled">
{model.list.loading? 'Loading List...' : 'Fetch Item List'}
</button>
{/* 如果已经获取到列表数据,显示数据;否则显示未获取到数据的提示 */}
{isListFetched &amp;&amp; model.list.data? (
<pre>{JSON.stringify(model.list.data, null, 2)}</pre>
) : (

No list data fetched

)}

</div>
);
};

export default GameAppModel;

增删改我就不再细细演示了,下面讲讲resful API 的介绍以及,优势在哪里为什么要这样去做。

四、Restful API介绍

讲完了上面的实现过程,下面来聊聊Restful API。REST可不是什么协议或者标准,它其实是一种架构风格。它有几个比较重要的指导原则:

4.1 统一接口

常见的像GETPOSTPUTDELETE这些,每个接口都有自己明确的用途,比如GET一般用来获取数据,POST用于创建数据等。

4.2 无状态

这就要求客户端给服务器发送的每个请求,都得包含能让服务器理解和完成这个请求的所有必要信息,服务器不会记住客户端之前的请求状态。

4.3 可缓存约束

服务器返回的响应得明确告诉客户端这个响应能不能被缓存起来,这样可以提高性能。

4.4 分层

系统是分层架构的,每个组件只能看到和它直接交互的那一层,不能跨层访问,这样可以降低系统的复杂度。

4.5 客户端-服务器分离

现在大多数项目都是前后端分离的,前端负责展示和用户交互,后端负责处理业务逻辑和数据存储,这一点大家应该都比较熟悉。

五、Restful API在前端模型层的优势

5.1 专注业务层逻辑

使用Restful API设计前端模型层,开发者不用操心接口调用的那些细节,像发起HTTP请求、处理错误这些,都交给Model层或者统一的API管理模块去处理,自己只需要专注于具体的业务逻辑就行。

5.2 数据状态管理

Model层会负责管理接口返回的数据状态,还能把数据缓存起来。这样如果需要再次使用这些数据,就不用重复请求服务器了,性能也就提高了。开发者也能很方便地获取这些数据,进行后续的校验或者判断。

5.3 统一的错误处理

可以把网络错误(比如404、500这些状态码对应的错误)、权限错误(像401、403这些)以及业务错误都集中起来处理。开发者只需要关注和业务相关的错误,其他错误交给统一的处理机制就行。

5.4 解耦接口层与业务层

通过抽象出接口层(也就是API模块或者Model层),可以把HTTP请求的逻辑和具体的业务逻辑隔离开。业务层直接调用Model提供的接口,不用关心底层网络请求是怎么实现的,这样代码的结构更清晰,维护起来也更方便。

以上就是我对Restful API前端接口模型架构的一些理解和分析,希望对大家有所帮助。如果在实际开发中遇到相关问题,欢迎一起讨论!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/16059.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】