如果您花了一些时间使用 React,您可能会偶然发现 React Query 的 queryOptions() 函数。它的实现看起来非常简单:
export function queryOptions(options: unknown) { return options }
然而,真正的魔力在于它的重载函数签名。那么,它有什么特别之处呢?
如果你不确定什么是重载函数,可以查看这篇文章:函数重载:如何处理多个函数签名
受 React Query 方法的启发,我组合了一个辅助函数,可能对 React 之外的人员有用:一种创建类型化查询(例如 SQL 查询)的简单方法。
export declare const queryParamsSymbol: unique symbol; export declare const queryReturnSymbol: unique symbol; export type Query< TParams extends Record<string, any> = Record<string, any>, TReturn extends Record<string, any> | undefined = undefined, TStatement extends string = string, > = { statement: TStatement; [queryParamsSymbol]: TParams; [queryReturnSymbol]: TReturn; }; export function query< TParams extends Record<string, any> = Record<string, any>, TReturn extends Record<string, any> | undefined = undefined, TStatement extends string = string, >(statement: TStatement): Query<TParams, TReturn> { return { statement: statement } as Query<TParams, TReturn, TStatement>; }
与 queryOptions() 类似,该函数本身相当乏味:它接受一条 SQL 语句,将其包装在 Query 类型的对象中,然后返回它。
这是一个如何调用它的简单示例:
const getUserById = query<{ id: number }, { name: string; email: string }>( 'SELECT name, email FROM users WHERE id = $id', );
注意我们如何将两种类型作为泛型参数传递。第一个是必需的查询参数 — 在本例中为 id。第二个代表查询的返回类型 - 姓名和电子邮件。
在底层,query() 将这两种类型嵌入到返回的对象中,并将它们存储在 queryParamsSymbol 和 queryReturnSymbol 中。这些符号被声明为唯一符号,这意味着它们仅存在于类型空间中,不会出现在转译的 JavaScript 中。
我使用这些符号来临时存储参数和返回类型,并在需要时检索它们。
type InferQueryParams<TQuery> = TQuery extends Query<infer Params, any> ? Params : never; type UserQueryParams = InferQueryParams<typeof getUserById>; // ^? { id: number } type InferQueryReturn<TQuery> = TQuery extends Query<any, infer Return> ? Return : never; type UserQueryReturn = InferQueryReturn<typeof getUserById>; // ^? { name: string; email: string }
InferQueryParams 和 InferQueryReturn 只是实用程序类型,用于确认我们的参数和返回类型被正确推断。您可能实际上并不需要它们,但它们可以方便地验证我们的方法。
现在我们知道如何在查询对象中嵌入参数和返回类型,那么我们如何实际运行这些查询呢?让我们看一下一个可以执行类型化查询的简单数据库客户端:
class DatabaseClient { async execute< TParams extends Record<string, any>, TReturn extends Record<string, any> >( query: Query<TParams, TReturn>, params: TParams, ): Promise<Array<TReturn>> { // execute the query and return the results // ... return []; } } const db = new DatabaseClient(); // Return type and parameters are inferred from the query object const result = await db.execute(getUserById, { id: 1 }); // ^? { id: number } // ^? Array<{ name: string; email: string }> console.log(result);
在此示例中,我们将类型化的 getUserById 查询对象传递给 db.execute() 方法。由于 Query 类型同时包含参数和返回类型信息,因此 TypeScript 会自动推断它们。我们可以通过将鼠标悬停在结果上轻松确认这一点,TypeScript 还会建议 id 作为参数对象的属性。
您可以在此 TypeScript Playground 中找到完整的代码示例。
以上是独特的符号:如何使用符号进行类型安全的详细内容。更多信息请关注PHP中文网其他相关文章!