加入我们,从 10 月 8 日至 10 月 10 日在纽约市,学习有关 GraphQL Federation 和 API 平台工程的最新的技巧、趋势和新闻。加入我们在纽约市举行的 2024 年 GraphQL Summit
文档
免费开始

Apollo 客户端的 TypeScript


随着您的应用程序的增长,一个可以成为早期发现错误并改善您的整体开发者体验的必要工具。

使用类型系统来明确定义每种类型及其中可供使用的数据。鉴于的架构是严格类型的,我们可以使用工具来自动生成 TypeScript 定义。GraphQL 代码生成器。我们将使用生成的类型来确保我们 GraphQL 操作的输入结果类型的完整性。

以下我们将指导您安装和配置 GraphQL 代码生成器以为您的钩子和组件生成类型。

设置您的项目

本文假设您的项目已经使用了 TypeScript。如果没有,请配置您的项目以使用 TypeScript创建一个新的项目

要开始使用GraphQL代码生成器,请首先安装以下包(使用 Yarn 或 NPM):

yarn add -D typescript graphql @graphql-codegen/cli @graphql-codegen/client-preset @graphql-typed-document-node/core

接下来,我们将为GraphQL代码生成器创建一个配置文件,命名为codegen.ts,并将其放在项目根目录下:

codegen.ts
import { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: '<URL_OF_YOUR_GRAPHQL_API>',
// this assumes that all your source files are in a top-level `src/` directory - you might need to adjust this to your file structure
documents: ['src/**/*.{ts,tsx}'],
generates: {
'./src/__generated__/': {
preset: 'client',
plugins: [],
presetConfig: {
gqlTagName: 'gql',
}
}
},
ignoreNoDocuments: true,
};
export default config;

在您的 codegen.ts 文件中有多种指定模式的方式,因此请选择最适合您项目设置的方法。

最后,我们将以下脚本添加到我们的 package.json 文件中:

package.json
{
"scripts": {
"compile": "graphql-codegen",
"watch": "graphql-codegen -w",
}
}

运行上述任一脚本都会根据您在 codegen.ts 文件中提供的模式文件或 GraphQL API 生成类型。

$ yarn run compile
✔ Parse Configuration
✔ Generate outputs

类型钩子

GraphQL 代码生成器会自动创建一个 gql 函数(来自 src/__generated__/gql.ts 文件),这个函数使我们能够为 React 钩子中的变量以及这些钩子返回的结果进行类型检查。

useQuery

以下,我们使用 gql 函数来定义我们的 ,这将自动生成用于我们的 useQuery 钩子的类型:

import React from 'react';
import { useQuery } from '@apollo/client';
import { gql } from '../src/__generated__/gql';
const GET_ROCKET_INVENTORY = gql(/* GraphQL */ `
query GetRocketInventory($year: Int!) {
rocketInventory(year: $year) {
id
model
year
stock
}
}
`);
export function RocketInventoryList() {
// our query's result, data, is typed!
const { loading, data } = useQuery(
GET_ROCKET_INVENTORY,
// variables are also typed!
{ variables: { year: 2019 } }
);
return (
<div>
<h3>Available Inventory</h3>
{loading ? (
<p>Loading ...</p>
) : (
<table>
<thead>
<tr>
<th>Model</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
{data && data.rocketInventory.map(inventory => (
<tr>
<td>{inventory.model}</td>
<td>{inventory.stock}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}

fetchMoresubscribeToMore

useQuery 钩子返回一个 QueryResult 的实例,它包含了 fetchMoresubscribeToMore 函数。详见 查询中的详细类型信息。因为这两个函数执行 GraphQL 操作,它们接受类型参数。

默认情况下,fetchMore 的类型参数与 useQuery 相同。由于 fetchMoreuseQuery 都封装了一个查询 operation,所以你不需要向 fetchMore 传递任何类型

在扩展我们之前的例子时,注意到我们没有显式地指定 fetchMore 的类型,因为它默认使用与 useQuery 相同的类型参数:

// ...
export function RocketInventoryList() {
const { fetchMore, loading, data } = useQuery(
GET_ROCKET_INVENTORY,
// variables are typed!
{ variables: { year: 2019 } }
);
return (
//...
<button
onClick={() => {
// variables are typed!
fetchMore({ variables: { year: 2020 } });
}}
>
Add 2020 Inventory
</button>
//...
);
}

subscribeToMore 的类型参数和默认值与 fetchMore 相同。请注意,subscribeToMore 执行的是 订阅,而 fetchMore 执行后续查询。

使用 subscribeToMore,你通常至少传递一个已类型化的 ,如下所示:

// ...
const ROCKET_STOCK_SUBSCRIPTION = gql(/* GraphQL */ `
subscription OnRocketStockUpdated {
rocketStockAdded {
id
stock
}
}
`);
export function RocketInventoryList() {
const { subscribeToMore, loading, data } = useQuery(
GET_ROCKET_INVENTORY,
{ variables: { year: 2019 } }
);
React.useEffect(() => {
subscribeToMore(
// variables are typed!
{ document: ROCKET_STOCK_SUBSCRIPTION, variables: { year: 2019 } }
);
}, [subscribeToMore])
// ...
}

useMutation

我们可以像类型化 useQuery 钩子一样类型化 useMutation 钩子。使用生成的 gql 函数来定义我们的 GraphQL mutations,我们确保我们为我们的突变变量和返回数据进行了类型化:

import React, { useState } from 'react';
import { useMutation } from '@apollo/client';
import { gql } from '../src/__generated__/gql';
const SAVE_ROCKET = gql(/* GraphQL */ `
mutation saveRocket($rocket: RocketInput!) {
saveRocket(rocket: $rocket) {
model
}
}
`);
export function NewRocketForm() {
const [model, setModel] = useState('');
const [year, setYear] = useState(0);
const [stock, setStock] = useState(0);
// our mutation's result, data, is typed!
const [saveRocket, { error, data }] = useMutation(SAVE_ROCKET, {
// variables are also typed!
variables: { rocket: { model, year: +year, stock: +stock } }
});
return (
<div>
<h3>Add a Rocket</h3>
{error ? <p>Oh no! {error.message}</p> : null}
{data && data.saveRocket ? <p>Saved!</p> : null}
<form>
<p>
<label>Model</label>
<input
name="model"
onChange={e => setModel(e.target.value)}
/>
</p>
<p>
<label>Year</label>
<input
type="number"
name="year"
onChange={e => setYear(+e.target.value)}
/>
</p>
<p>
<label>Stock</label>
<input
type="number"
name="stock"
onChange={e => setStock(e.target.value)}
/>
</p>
<button onClick={() => model && year && stock && saveRocket()}>
Add
</button>
</form>
</div>
);
}

useSubscription

我们可以像类型化我们的 useQueryuseMutation 钩子一样类型化我们的 useSubscription 钩子。使用生成的 gql 函数来定义我们的 GraphQL subscriptions,我们确保我们对订阅变量和返回数据进行类型化:

import React from 'react';
import { useSubscription } from '@apollo/client';
import { gql } from '../src/gql';
const LATEST_NEWS = gql(/* GraphQL */ `
subscription getLatestNews {
latestNews {
content
}
}
`);
export function LatestNews() {
// our returned data is typed!
const { loading, data } = useSubscription(LATEST_NEWS);
return (
<div>
<h5>Latest News</h5>
<p>
{loading ? 'Loading...' : data!.latestNews.content}
</p>
</div>
);
}

对渲染属性组件进行类型化

为了对渲染属性组件进行类型化,你首先将使用生成的 gql 函数(来自 src/__generated__/gql)定义一个 GraphQL query。

这将为该

import { gql, AllPeopleQuery, AllPeopleQueryVariables } from '../src/__generated__/gql';
const ALL_PEOPLE_QUERY = gql(/* GraphQL */ `
query All_People {
allPeople {
people {
id
name
}
}
}
`;
const AllPeopleComponent = <Query<AllPeopleQuery, AllPeopleQueryVariables> query={ALL_PEOPLE_QUERY}>
{({ loading, error, data }) => { ... }}
</Query>

我们的

这种方法也适用于

扩展组件

class SomeQuery extends Query<SomeData, SomeVariables> {}

现在,基于类的渲染属性组件已被转换为功能组件,您无法再以这种方式扩展组件。

虽然我们建议您尽快切换到使用新的useQueryuseMutationuseSubscription钩子,但在此期间,您可以替换您的类,使用包装并类型化组件:

export const SomeQuery = () => (
<Query<SomeData, SomeVariables> query={SOME_QUERY} /* ... */>
{({ loading, error, data }) => { ... }}
</Query>
);

类型化高阶组件

要类型化高阶组件,首先使用gql函数(从

我们的包装组件接收我们的

以下是一个使用

import React from "react";
import { ChildDataProps, graphql } from "@apollo/react-hoc";
import { gql, GetCharacterQuery, GetCharacterQueryVariables } from '../src/gql';
const HERO_QUERY = gql(/* GraphQL */ `
query GetCharacter($episode: Episode!) {
hero(episode: $episode) {
name
id
friends {
name
id
appearsIn
}
}
}
`);
type ChildProps = ChildDataProps<{}, GetCharacterQuery, GetCharacterQueryVariables>;
// Note that the first parameter here is an empty Object, which means we're
// not checking incoming props for type safety in this example. The next
// example (in the "Options" section) shows how the type safety of incoming
// props can be ensured.
const withCharacter = graphql<{}, GetCharacterQuery, GetCharacterQueryVariables, ChildProps>(HERO_QUERY, {
options: () => ({
variables: { episode: "JEDI" }
})
});
export default withCharacter(({ data: { loading, hero, error } }) => {
if (loading) return <div>Loading</div>;
if (error) return <h1>ERROR</h1>;
return ...// actual component with data;
});

以下逻辑也适用于

选项

通常,我们的包装组件的属性通过传递

以下是一个为组件的 props 设置类型的示例:

import React from "react";
import { ChildDataProps, graphql } from "@apollo/react-hoc";
import { gql, GetCharacterQuery, GetCharacterQueryVariables } from '../src/gql';
const HERO_QUERY = gql(/* GraphQL */ `
query GetCharacter($episode: Episode!) {
hero(episode: $episode) {
name
id
friends {
name
id
appearsIn
}
}
}
`);
type ChildProps = ChildDataProps<GetCharacterQueryVariables, GetCharacterQuery, GetCharacterQueryVariables>;
const withCharacter = graphql<
GetCharacterQueryVariables,
GetCharacterQuery,
GetCharacterQueryVariables,
ChildProps
>(HERO_QUERY, {
options: ({ episode }) => ({
variables: { episode }
}),
});
export default withCharacter(({ data: { loading, hero, error } }) => {
if (loading) return <div>Loading</div>;
if (error) return <h1>ERROR</h1>;
return ...// actual component with data;
});

当通过 props 将深层次对象传递给我们的组件时,这尤其有用。例如,当添加 prop 类型时,使用 TypeScript 的项目开始出现无效 props 的错误。

import React from "react";
import {
ApolloClient,
createHttpLink,
InMemoryCache,
ApolloProvider
} from "@apollo/client";
import Character from "./Character";
export const link = createHttpLink({
uri: "https://mpjk0plp9.lp.gql.zone/graphql"
});
export const client = new ApolloClient({
cache: new InMemoryCache(),
link,
});
export default () =>
<ApolloProvider client={client}>
// $ExpectError property `episode`. Property not found in. See: src/Character.js:43
<Character />
</ApolloProvider>;

Props

使用props函数,您可以手动将

import React from "react";
import { graphql, ChildDataProps } from "@apollo/react-hoc";
import { gql, GetCharacterQuery, GetCharacterQueryVariables } from '../src/gql';
const HERO_QUERY = gql(/* GraphQL */ `
query GetCharacter($episode: Episode!) {
hero(episode: $episode) {
name
id
friends {
name
id
appearsIn
}
}
}
`);
type ChildProps = ChildDataProps<GetCharacterQueryVariables, GetCharacterQuery, GetCharacterQueryVariables>;
const withCharacter = graphql<
GetCharacterQueryVariables,
GetCharacterQuery,
GetCharacterQueryVariables,
ChildProps
>(HERO_QUERY, {
options: ({ episode }) => ({
variables: { episode }
}),
props: ({ data }) => ({ ...data })
});
export default withCharacter(({ loading, hero, error }) => {
if (loading) return <div>Loading</div>;
if (error) return <h1>ERROR</h1>;
return ...// actual component with data;
});

在上面的示例中,我们为我们的响应、

export const withCharacter = graphql<
GetCharacterQueryVariables,
GetCharacterQuery,
GetCharacterQueryVariables,
Props
>(HERO_QUERY, {
options: ({ episode }) => ({
variables: { episode }
}),
props: ({ data, ownProps }) => ({
...data,
// $ExpectError [string] This type cannot be compared to number
episode: ownProps.episode > 1,
// $ExpectError property `isHero`. Property not found on object type
isHero: data && data.hero && data.hero.isHero
})
});

类和函数

如果你在使用 React 类(而不是使用 graphql 包装器),你仍然可以像这样为你的类键入传入的属性:

import { ChildProps } from "@apollo/react-hoc";
const withCharacter = graphql<GetCharacterQueryVariables, GetCharacterQuery>(HERO_QUERY, {
options: ({ episode }) => ({
variables: { episode }
})
});
class Character extends React.Component<ChildProps<GetCharacterQueryVariables, GetCharacterQuery>, {}> {
render(){
const { loading, hero, error } = this.props.data;
if (loading) return <div>Loading</div>;
if (error) return <h1>ERROR</h1>;
return ...// actual component with data;
}
}
export default withCharacter(Character);

使用 name 属性

如果你在 graphql 包装器的配置中使用 name 属性,你需要手动将响应类型附加到 props 函数中,如下所示:

import { NamedProps, QueryProps } from '@apollo/react-hoc';
export const withCharacter = graphql<GetCharacterQueryVariables, GetCharacterQuery, {}, Prop>(HERO_QUERY, {
name: 'character',
props: ({ character, ownProps }: NamedProps<{ character: QueryProps & GetCharacterQuery }, Props) => ({
...character,
// $ExpectError [string] This type cannot be compared to number
episode: ownProps.episode > 1,
// $ExpectError property `isHero`. Property not found on object type
isHero: character && character.hero && character.hero.isHero
})
});

使用 TypedDocumentNode

在 TypeScript 中,所有接受 DocumentNode 的 API 可以选择接受 TypedDocumentNode<Data, Variables>。这个类型具有相同的 JavaScript 表示形式,但可以使 API 推断数据和变量类型(而不是在调用时指定类型)。

这种技术使我们能够修改上述 useQuery 示例 以使用类型推断:

import React from 'react';
import { useQuery, gql, TypedDocumentNode } from '@apollo/client';
interface RocketInventoryData {
rocketInventory: RocketInventory[];
}
interface RocketInventoryVars {
year: number;
}
const GET_ROCKET_INVENTORY: TypedDocumentNode<RocketInventoryData, RocketInventoryVars> = gql`
query GetRocketInventory($year: Int!) {
rocketInventory(year: $year) {
id
model
year
stock
}
}
`;
export function RocketInventoryList() {
const { loading, data } = useQuery(
GET_ROCKET_INVENTORY,
{ variables: { year: 2019 } }
);
return (
<div>
<h3>Available Inventory</h3>
{loading ? (
<p>Loading ...</p>
) : (
<table>
<thead>
<tr>
<th>Model</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
{data && data.rocketInventory.map(inventory => (
<tr>
<td>{inventory.model}</td>
<td>{inventory.stock}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
上一个
开发者工具
下一个
测试 React 组件
评分文章评分在 GitHub 上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商业名称 Apollo GraphQL。

隐私策略

公司