获取当前按钮的内容_Apollo入门引导(七):通过查询获取数据

接上篇 —— Apollo 入门引导(六):创建Apollo客户端 —— 继续翻译 Apollo 的官网入门引导 。

使用 React 的 Hook:useQuery

Apollo 入门引导 - 目录:

  1. 介绍
  2. 构建 schema
  3. 连接数据源
  4. 编写查询解析器
  5. 编写变更解析器
  6. 连接 Apollo Studio
  7. 创建 Apollo 客户端
  8. 通过查询获取数据
  9. 通过变更修改数据
  10. 管理本地状态

完成时间:20 分钟

前一节已经设置了 Apollo 客户端,现在可以将其集成到我们的 React 应用中了。可以使用React Hooks将 GraphQL 查询的结果直接绑定到 UI。

与 React 集成

为了将 Apollo 客户端连接到 React,需要将应用程序封装在 @apollo/client 包中的 ApolloProvider 组件中。我们通过 client 属性将客户端实例传递给 ApolloProvider 组件。

打开 src/index.tsx ,更新以下内容:

import {
  ApolloClient,
  NormalizedCacheObject,
  ApolloProvider,
} from '@apollo/client';
import { cache } from './cache';
import React from 'react';
import ReactDOM from 'react-dom';
import Pages from './pages';
import injectStyles from './styles';

// 初始化Apollo 客户端
const client: ApolloClient = new ApolloClient({
  cache,
  uri: 'http://localhost:4000/graphql',
});
injectStyles();// 传递ApolloClient的实例给ApolloProvider组件
ReactDOM.render(
  </ApolloProvider>,
  document.getElementById('root')
);

ApolloProvider 组件类似于 React 的上下文提供器:它封装了 React 应用,并将 client 放置在上下文中,所以可以从组件树中的任何位置访问它。现在,准备构建执行 GraphQL 查询的 React 组件。

显示发射列表

接下来在应用中构建页面,该页面显示可获得的 SpaceX 发射的列表。打开 src / pages / launches.tsx,该文件如下所示:

import React, { Fragment, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { gql } from '@apollo/client';

export const LAUNCH_TILE_DATA = gql`
  fragment LaunchTile on Launch {
    __typename
    id
    isBooked
    rocket {
      id
      name
    }
    mission {
      name
      missionPatch
    }
  }
`;

interface LaunchesProps extends RouteComponentProps {}

const Launches: React.FC = () => {return 
};export default Launches;

定义查询

首先定义查询的格式,该查询将用于获取发射的分页列表。将以下内容粘贴到 LAUNCH_TILE_DATA 声明的下方:

export const GET_LAUNCHES = gql`
  query GetLaunchList($after: String) {
    launches(after: $after) {
      cursor
      hasMore
      launches {
        ...LaunchTile
      }
    }
  }${LAUNCH_TILE_DATA}
`;
使用片段

注意,在定义查询时,在其上方插入了 LAUNCH_TILE_DATA 的定义。LAUNCH_TILE_DATA 定义了一个 GraphQL 片段(fragment),名为 LaunchTile。片段对于定义一组字段时很有帮助,无需重写就可以将这组字段包含在多个查询中。

在上面的查询中,通过 ...LaunchTile 方式引入了片段,类似于 JavaScript spread 语法。

分页详细信息

注意,除了获取 launches 列表之外,查询还获取 hasMorecursor 字段。这是因为 launches 查询返回 分页结果

  • hasMore 字段指示服务返回的列表后面是否还有其他的发射信息。
  • cursor 字段指示客户端在发射列表中的当前位置。可以再次执行查询,并提供最新的 cursor 作为 $after 变量的值,以获取列表中的 下一批 发射的集合。

使用 useQuery hook

我们将使用 Apollo 客户端的 useQuery React Hook在 Launches 组件中执行新查询。Hook 的结果对象提供的属性可以在查询执行时填充和呈现组件。

  1. 修改 @apollo/client ,导入 useQuery,再导入一些预定义的组件以呈现页面:
import { gql, useQuery } from '@apollo/client';
import { LaunchTile, Header, Button, Loading } from '../components';

如果使用的是 TypeScript,需要从服务的 schema 定义中导入必需的类型:

import * as GetLaunchListTypes from './__generated__/GetLaunchList';
  1. 将伪声明 const Launches 替换为以下内容:
const Launches: React.FC = () => {const { data, loading, error } = useQuery<
    GetLaunchListTypes.GetLaunchList,
    GetLaunchListTypes.GetLaunchListVariables
  >(GET_LAUNCHES);if (loading) return ;if (error) return 

ERROR</p>;
  if (!data) return 

Not found

p>;return (
      {data.launches &&
        data.launches.launches &&
        data.launches.launches.map((launch: any) => (
        ))}
    </Fragment>
  );
};

该组件将 GET_LAUNCHES 查询传给 useQuery ,并从结果中获取 dataloadingerror 属性。根据这些属性的状态来展现发射列表,加载状态和错误信息。

使用 npm start 启动服务和客户端,并访问 localhost:3000。如果一切都配置正确,将会展现应用主页,并列出 20 次 SpaceX 发射!

但是有一个问题:总共的 SpaceX 发射数超过了 20 个。服务会分页显示其结果,并在一次响应中最多包含 20 次发射。

为了能够获取并存储 全部 启动,需要修改代码以使用查询中包含的 cursorhasMore 字段。接下来学习如何做分页支持。

添加分页支持

本教程中没有展现 Apollo client 3 为基于偏移和Relay 风格的分页助手函数(pagination helper function)。

Apollo 客户端提供了一个 fetchMore 助手函数来协助分页查询。可以用不同值的变量(例如当前游标)来执行相同的查询。

useQuery 结果对象中解构的对象列表中添加 fetchMore ,并定义一个 isLoadingMore 状态变量:

const Launches: React.FC = () => {const {
    data,
    loading,
    error,
    fetchMore, // highlight-line
  } = useQuery<
    GetLaunchListTypes.GetLaunchList,
    GetLaunchListTypes.GetLaunchListVariables
  >(GET_LAUNCHES);const [isLoadingMore, setIsLoadingMore] = useState(false); //highlight-line// ...
};

现在将 fetchMore 连接到 Launches 组件中的按钮上,单击该按钮可获取其他发射。

将此代码直接粘贴在 Launches 组件的结束 Fragment> 标签上方:

{
  data.launches &&
    data.launches.hasMore &&
    (isLoadingMore ? (
      
    ) : (
              onClick={async () => {
          setIsLoadingMore(true);
          await fetchMore({
            variables: {
              after: data.launches.cursor,
            },
          });
          setIsLoadingMore(false);
        }}
      >
        Load More
      </Button>
    ));
}
//Fragment>

单击按钮时,它将调用 fetchMore (将当前的 cursor 的值传给after 变量),直到查询返回结果前一直显示加载中的状态。

启动所有内容,然后再次访问 localhost:3000 。现在已有的 20 个发射下方会出现一个 Load More 按钮,点击它。查询返回后, 没有其他发射出现。?

如果检查浏览器的网络活动,会发现该按钮确实向服务发送了后续查询,并且服务确实响应了新的发射列表。但是,Apollo 客户端将这些列表分隔开,因为它们表示带有 不同变量值 (本例为 after 的值)的查询结果。

我们需要 Apollo 客户端来将 fetchMore 查询中的发射与 之前 查询中的发射进行 合并 。接下来配置该行为。

合并缓存的结果

Apollo 客户端将查询结果存储在内存缓存中。缓存可以智能高效地处理大多数操作,但是并不能自动知道我们是否要合并两个不同的发射列表。为了解决这个问题,为 schema 中的分页字段定义一个 合并函数(merge function)

打开 src/cache.ts,现在初始化的是默认 InMemoryCache

import { InMemoryCache, Reference } from '@apollo/client';

export const cache: InMemoryCache = new InMemoryCache({});

服务中分页的 schema 字段是 launches 列表。修改 cache 的初始化过程,为 launches 字段添加一个 merge 函数,如下所示:

export const cache: InMemoryCache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        launches: {
          keyArgs: false,
          merge(existing, incoming) {
            let launches: Reference[] = [];
            if (existing && existing.launches) {
              launches = launches.concat(existing.launches);
            }
            if (incoming && incoming.launches) {
              launches = launches.concat(incoming.launches);
            }
            return {
              ...incoming,
              launches,
            };
          },
        },
      },
    },
  },
});

这个 merge 方法接受我们现有的缓存发射(existing)和传入的发射(incoming),并将它们组合成一个列表并返回。缓存存储了此组合列表,并将其返回给所有使用 launches 字段的查询。

此示例展示字段策略的用法,这是针对 schema 中各个字段的缓存配置选项。

如果现在尝试单击 Load More 按钮,则 UI 将成功将其他发射附加到列表中!

显示单次发射的详情

我们希望能够单击列表中的发射以查看其完整详情。打开 src/pages/launch.tsx 并替换为以下内容:

import { gql } from '@apollo/client';
import { LAUNCH_TILE_DATA } from './launches';

export const GET_LAUNCH_DETAILS = gql`
  query LaunchDetails($launchId: ID!) {
    launch(id: $launchId) {
      site
      rocket {
        type
      }
      ...LaunchTile
    }
  }${LAUNCH_TILE_DATA}
`;

该查询包含了所有详情。注意,代码复用了已经在 launches.tsx 中定义的 LAUNCH_TILE_DATA 片段。

再一次将查询传递给 useQuery hook。这次还需要将对应发射的 launchId 作为变量传给查询。launchId'的值可用路由来传递。

现在,将 launch.tsx 的内容替换为以下内容:

import React, { Fragment } from 'react'; // preserve-line
import { gql, useQuery } from '@apollo/client'; // preserve-line

import { LAUNCH_TILE_DATA } from './launches';
import { Loading, Header, LaunchDetail } from '../components'; // preserve-line
import { ActionButton } from '../containers'; // preserve-line
import { RouteComponentProps } from '@reach/router';
import * as LaunchDetailsTypes from './__generated__/LaunchDetails';

export const GET_LAUNCH_DETAILS = gql`
  query LaunchDetails($launchId: ID!) {
    launch(id: $launchId) {
      site
      rocket {
        type
      }
      ...LaunchTile
    }
  }${LAUNCH_TILE_DATA}
`;

interface LaunchProps extends RouteComponentProps {
  launchId?: any;
}

const Launch: React.FC = ({ launchId }) => {const { data, loading, error } = useQuery<
    LaunchDetailsTypes.LaunchDetails,
    LaunchDetailsTypes.LaunchDetailsVariables
  >(GET_LAUNCH_DETAILS, { variables: { launchId } });if (loading) return ;if (error) return 

ERROR: {error.message}</p>;
  if (!data) return 

Not found

p>;return (        image={
          data.launch && data.launch.mission && data.launch.mission.missionPatch
        }
      >
        {data && data.launch && data.launch.mission && data.launch.mission.name}
      </Header>>
    </Fragment>
  );
};
export default Launch;

像以前一样,正在查询时呈现 loadingerror 状态,在查询完成后呈现数据。

回到应用中,单击列表中的发射以查看详情页面。

显示个人资料页面

我们希望用户的个人资料页面显示其已预订的发射列表。打开 src/pages/profile.tsx 并将其内容替换为以下内容:

import React, { Fragment } from 'react'; // preserve-line
import { gql, useQuery } from '@apollo/client'; // preserve-line

import { Loading, Header, LaunchTile } from '../components'; // preserve-line
import { LAUNCH_TILE_DATA } from './launches'; // preserve-line
import { RouteComponentProps } from '@reach/router';
import * as GetMyTripsTypes from './__generated__/GetMyTrips';

export const GET_MY_TRIPS = gql`
  query GetMyTrips {
    me {
      id
      email
      trips {
        ...LaunchTile
      }
    }
  }${LAUNCH_TILE_DATA}
`;

interface ProfileProps extends RouteComponentProps {}

const Profile: React.FC = () => {const { data, loading, error } = useQuery(
    GET_MY_TRIPS,
    { fetchPolicy: 'network-only' } // highlight-line
  );if (loading) return ;if (error) return 

ERROR: {error.message}</p>;
  if (data === undefined) return 

ERROR

p>;return (My Trips</Header>
      {data.me && data.me.trips.length ? (
        data.me.trips.map((launch: any) => (>
        ))
      ) : (

You haven't booked any trips


      )}
  );
};
export default Profile;

你应该从已经完成的页面中找到上述代码中的所有概念,只有一个例外:正在设置的 fetchPolicy

自定义 fetch 策略

如前所述,Apollo 客户端将查询结果存储在其缓存中。如果查询缓存中已存在的数据,则 Apollo 客户端会直接返回该数据,而无需通过网络获取。

但是,缓存的数据可能会过时。在大部分情况下,稍微过时的数据是可以接受的,但是用户的预订行程列表应当是时刻保持最新的。为了解决这个问题,专门为 GET_MY_TRIPS 查询指定了fetch 策略

fetch 策略定义了 Apollo 客户端如何将缓存用于特定查询。默认策略是 cache-first(缓存优先),这意味着 Apollo 客户端在发出网络请求之前会检查缓存,查看结果是否存在。如果存在结果,则不会发生网络请求。

通过将此查询的 fetch 策略设置为 network-only(仅限网络来源),就能保证 Apollo 客户端 始终 是从服务中获取用户的最新预订行程列表。

有关所有支持的 fetch 策略的列表,请参阅支持的 fetch 策略。

如果访问应用中的个人资料页面,则会发现查询返回 null。这是因为还未实现登录功能。将在下一节中解决这个问题!

c9a0e6c4aef7052f0248fe14dea45942.png


前端记事本,不定期更新,欢迎关注!

  • 微信公众号:林景宜的记事本
  • 博客:林景宜的记事本
  • 掘金专栏:林景宜的记事本
  • 知乎专栏:林景宜的记事本
  • Github: MageeLin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值