Skip to content

v3.9.5

Choose a tag to compare

@Ahoo-Wang Ahoo-Wang released this 27 Dec 05:39
· 66 commits to main since this release

What's Changed

🎉 New Features

  • feat(react-api): add query API hooks and utilities for React by @Ahoo-Wang in #987

createQueryApiHooks

🚀 自动类型安全查询 API Hooks 生成 - 从 API 对象自动生成完全类型化的 React 查询 hooks,具有自动查询状态管理、自动执行和高级执行控制。

createQueryApiHooks 函数自动发现 API 对象中的查询方法,并创建相应的扩展 useQuery 的 React hooks。每个生成的 hook 都提供自动查询参数管理、状态管理和对具有类型安全查询访问的自定义执行回调的支持。

主要特性:

  • 自动方法发现:遍历对象属性和原型链
  • 类型安全查询 Hooks:查询参数和返回类型的完整 TypeScript 推断
  • 查询状态管理:内置 setQuerygetQuery 进行参数管理
  • 自动执行:查询参数更改时可选的自动执行
  • 执行控制onBeforeExecute 回调用于查询检查/修改和中止控制器访问
  • 自定义错误类型:支持指定超出默认 FetcherError 的错误类型
import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';
import { api, get, post, patch, path, body, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';

// 使用装饰器定义您的 API 服务
@api('/users')
class UserApi {
  @get('')
  getUsers(query: UserListQuery, attributes?: Record<string, any>): Promise<User[]> {
    throw autoGeneratedError(query, attributes);
  }

  @get('/{id}')
  getUser(query: { id: string }, attributes?: Record<string, any>): Promise<User> {
    throw autoGeneratedError(query, attributes);
  }

  @post('')
  createUser(query: { name: string; email: string }, attributes?: Record<string, any>): Promise<User> {
    throw autoGeneratedError(query, attributes);
  }
}

const apiHooks = createQueryApiHooks({ api: new UserApi() });

function UserListComponent() {
  const { loading, result, error, execute, setQuery, getQuery } = apiHooks.useGetUsers({
    initialQuery: { page: 1, limit: 10 },
    autoExecute: true,
    onBeforeExecute: (abortController, query) => {
      // query 是完全类型化的 UserListQuery
      console.log('正在执行查询:', query);
      // 如果需要,可以就地修改查询参数
      query.page = Math.max(1, query.page);
    },
  });

  const handlePageChange = (page: number) => {
    // 自动更新查询并触发执行(如果 autoExecute: true)
    setQuery({ ...getQuery(), page });
  };

  if (loading) return <div>正在加载...</div>;
  if (error) return <div>错误: {error.message}</div>;

  return (
    <div>
      <button onClick={() => handlePageChange(2)}>转到第2页</button>
      {result?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

function UserDetailComponent() {
  const { result: user, execute } = apiHooks.useGetUser({
    initialQuery: { id: '123' },
  });

  return (
    <div>
      <button onClick={execute}>加载用户</button>
      {user && <div>用户: {user.name}</div>}
    </div>
  );
}

自定义错误类型:

import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';

// 定义自定义错误类型
class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string,
  ) {
    super(message);
  }
}

// 使用自定义错误类型生成查询 hooks
@api('/data')
class DataApi {
  @get('/{id}')
  getData(
    query: { id: string },
    attributes?: Record<string, any>,
  ): Promise<Data> {
    throw autoGeneratedError(query, attributes);
  }
}

const apiHooks = createQueryApiHooks<
  {
    getData: (
      query: { id: string },
      attributes?: Record<string, any>,
    ) => Promise<Data>;
  },
  ApiError
>({
  api: new DataApi(),
  errorType: ApiError,
});

function MyComponent() {
  const { error, execute } = apiHooks.useGetData();

  // error 现在类型化为 ApiError | undefined
  if (error) {
    console.log('状态码:', error.statusCode); // TypeScript 知道 statusCode
  }
}

高级用法与手动查询管理:

import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';

const apiHooks = createQueryApiHooks({ api: userApi });

function SearchComponent() {
  const { loading, result, setQuery, getQuery } = apiHooks.useGetUsers({
    initialQuery: { search: '', page: 1 },
    autoExecute: false, // 手动执行控制
  });

  const handleSearch = (searchTerm: string) => {
    // 更新查询而不自动执行
    setQuery({ search: searchTerm, page: 1 });
  };

  const handleSearchSubmit = () => {
    // 使用当前查询手动执行
    apiHooks.useGetUsers().execute();
  };

  const currentQuery = getQuery(); // 访问当前查询参数

  return (
    <div>
      <input
        value={currentQuery.search}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="搜索用户..."
      />
      <button onClick={handleSearchSubmit} disabled={loading}>
        {loading ? '搜索中...' : '搜索'}
      </button>
      {result?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Full Changelog: v3.9.3...v3.9.5