Restful API 前端接口模型架构浅析

前言

在前司接触到一个有意思的权限校验接口组合,所有的权限校验接口都是在一个Model对象中配合修饰器来实现。由于那时一个Vue项目,我使用React模拟实现了一下。

看到这里,回到业务层,我想直接去调用这个节后获取返回权限,但是却没有始终是无效功

scss 复制代码
setIsUserFetched(false); // 重置状态
const response = await model.user({ id: 1 });
setIsUserFetched(true);

回过头去查找这个接口身上的各个属性我得到了答案:在项目中这个校验接口都被一个restMutation的接口修饰了,而这个接口类型长这样:

typescript 复制代码
export interface RestMutationOptions<T extends BaseModel> {
    url: UrlFn<T> | string;
    method?: Method;
    headers?: Headers;
    variables?: MutationVariablesFn<T> | Record<string, any>;
}

export declare function restMutation<T extends BaseModel>(detail:RestMutationOptions<T>): (constructor: any, key: string) => void;

ps :不用过多纠结这些类型,因为这是靠近业务的代码,不是讲解版本。

useMutation

这里我先将react模拟出来的hooks版本贴出,hook更加便于理解:

typescript 复制代码
// utils/useMutation.ts

import { useState } from 'react';

export function useMutation<T, P>(options: {
  url: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  variables: (params: T) => any;
}) {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState<P | null>(null);
  const [error, setError] = useState<any>(null);

  const mutate = async (params: T) => {
    setLoading(true);
    setError(null);

    const queryParams = new URLSearchParams(options.variables(params)).toString();
    const url = options.method === 'GET' ? `${options.url}?${queryParams}` : options.url;

    try {
      const response = await fetch(url, {
        method: options.method,
        headers: {
          'Content-Type': 'application/json',
        },
      });
      console.log('Response:', response);

      if (response.ok) {
        const result = await response.json();
        setData(result);
      } else {
        throw new Error('Request failed');
      }
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  return { mutate, loading, data, error };
}

在这里我做的useMutation其实是将上面的restMutation从接口定义到函数实现全部一步实现了,结耦复用率有所降低不太好在包装项目的牛B程度🤣🤣🤣🤣🤣🤣

这个useMutation将集中处理掌管后面我们的权限校验接口

Model

然后我们再在Model内去需要权限校验的接口使用该hooks来实现装饰实现:

typescript 复制代码
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 }>;
}

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

  list = useMutation<{ page: number, size: number }, ListData>({
    url: 'http://localhost:3000/list',
    method: 'GET',
    variables: (p) => ({ page: p.page, size: p.size }), // 列表请求时传递分页参数
  });
}

部分类型定义可通通忽略,这里我便在Model身上去挂载了这些属性,打印看看

为什么这有些属性会是空的呢?接口没发出去啊,network内都没有请求。我们需要在业务层将接口调用,使用单里模式到处对象,以便在全局使用同一个对象身上的属性。

javascript 复制代码
import React, { useEffect, useState } from 'react';
import {useApollo} from 'apllo.js'
import { Model } from './api/model';

const GameAppModel: React.FC = () => {
  const model = useApollo(Model);

  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); // 重置状态
    await model.user.mutate({ id: 1 }); // 假设传递一个用户 id 为 1
    setIsUserFetched(true);
  };

  // 获取列表数据
  const handleFetchList = async () => {
    setIsListFetched(false); // 重置状态
    await model.list.mutate({ page: 1, size: 10 }); // 假设获取第 1 页,10 条数据
    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>
      <button onClick={handleFetchUser} disabled={model.user.loading}>
        {model.user.loading ? 'Loading User...' : 'Fetch User Info'}
      </button>
      {isUserFetched && model.user.data ? (
        <pre>{JSON.stringify(model.user.data, null, 2)}</pre>
      ) : (
        <p>No user data fetched</p>
      )}

      <h2>Item List:</h2>
      <button onClick={handleFetchList} disabled={model.list.loading}>
        {model.list.loading ? 'Loading List...' : 'Fetch Item List'}
      </button>
      {isListFetched && model.list.data ? (
        <pre>{JSON.stringify(model.list.data, null, 2)}</pre>
      ) : (
        <p>No list data fetched</p>
      )}
    </div>
  );
};

export default GameAppModel;

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

Restful API

REST 不是一种协议或标准,而是一种架构风格。

REST的指导原则

  • 统一接口:GET、POST、PUT、DELETE 等
  • 无状态: 要求客户端向服务器发出的每个请求都必须包含理解和完成请求所需的所有信息。
  • 可缓存约束:响应应隐式或显式地将自己标记为可缓存或不可缓存。
  • 分层:系统通过限制组件行为,允许架构由分层层组成。在分层系统中,每个组件无法看到与其交互的直接层以外的内容。
  • 客户端-服务器分离:这个现在大多数都是前后端分离的项目这个了解下即可

这样做有什么优势呢??这肯定是最现实的问题

RESTful API 在前端模型层的设计理念

  • 专注业务层逻辑

    • 开发者只需关注具体的业务逻辑,不需要关心接口调用的细节,比如 HTTP 请求、错误处理等。
    • 接口调用和数据的管理交由 Model 层 或统一的 API 管理模块处理。
  • 数据状态管理

    • Model 层负责维护和管理接口返回的数据状态,将数据缓存下来,避免重复请求,提高性能。
    • 开发者可以通过简单的状态访问,获取接口返回的数据,进行后续校验或判断。
  • 统一的错误处理

    • 将网络错误(如 404、500)、权限错误(如 401、403)、业务错误等集中处理,开发者只需处理业务相关的错误。
  • 解耦接口层与业务层

    • 通过抽象接口层(API 模块或 Model 层),将 HTTP 请求逻辑与具体的业务逻辑隔离。
    • 业务层直接调用 Model 提供的接口,无需关心底层的网络请求实现。
相关推荐
前端大卫2 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘17 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare18 分钟前
浅浅看一下设计模式
前端
Lee川22 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端