Apollo 客户端 (React) — Web SDK
在此页面上
GraphQL已弃用。 了解详情。
Overview
您可以使用 Apollo 客户端 从React应用程序连接到Realm应用程序 公开的GraphQL API 。 Apollo Client 运行查询和变更,维护客户端数据缓存,并使用一致的React组件和钩子集成到您的应用中。
注意
查看正在运行的演示应用程序
查看RealmGraphQL - Apollo (React ) Github以查看完全设置的 和React ApolloAtlas App Services 应用程序, 应用程序已准备好连接到您自己的 后端。它使用 MongoDB Atlas样本数据集中包含的 sample_mflix.movies
集合。
如果您不想克隆存储库,也可以在浏览器内的 Realm GraphQL CodeSandbox 中使用演示应用程序。
设置 Apollo 客户端
安装依赖项
与所有 Realm 项目一样,您需要安装 Realm Web SDK 对用户和请求进行身份验证。
npm install realm-web
Apollo 会将创建客户端所需的核心组件捆绑到一个名为 @apollo/client 的包中。此外,它还需使用 graphql 包来解析 GraphQL 查询。
npm install @apollo/client graphql
创建 Apollo GraphQL 客户端
新建一个 ApolloClient 对象,指向您 Realm 应用的 GraphQL API 端点。您可以根据 Realm 应用 ID 生成端点 URL,或者在 Realm UI 的 GraphQL 页面上找到它。
// Add your App ID const graphqlUri = `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`; // Local apps should use a local URI! // const graphqlUri = `https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://eu-west-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://ap-southeast-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` const client = new ApolloClient({ link: new HttpLink({ uri: graphqlUri, }), cache: new InMemoryCache(), });
设置用户身份验证
ApolloClient
将配置为向您的应用发送请求。但是,所有 Realm GraphQL 请求均须包含有效的用户访问令牌来验证请求,因此现在从 Apollo 发送的任何操作均会失败。访问令牌会在 30 分钟后过期,且需进行刷新。
若要验证请求,您需为每个 GraphQL 请求添加一个附带有效 Realm 用户访问令牌的授权标头。
您可以使用 Realm Web SDK 验证用户身份并获取其访问令牌。Apollo HttpLink
对象允许您通过定义自定义 fetch
函数向每个请求添加自定义标头。
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });
将 Apollo 客户端添加到您的应用
Apollo client
对象现已配置为将经过身份验证的 GraphQL 请求发送到 App Services 后端。最后,使该对象可用于 React 应用程序的其余部分即可。
@apollo/client
包会导出 ApolloProvider
组件,而该组件会让 client
可用于您从子组件调用的所有 Apollo 挂钩。用 ApolloProvider
封装该应用,并将 client
对象传递给提供程序。
// ... code to create the GraphQL client const AppWithApollo = () => ( <ApolloProvider client={client}> <App /> </ApolloProvider> );
运行查询和变更
@apollo/client
软件包包含一组声明性 React 钩子,用于将您的组件连接到 GraphQL API 并处理查询和更改执行。
要定义可以传递给钩子的查询和更改,则安装 graph ql-tag:
npm install graphql-tag
在您使用相关钩子和 GraphQL 查询构造函数的文件的顶部导入它们。
// import whichever Apollo hooks you're using import { useQuery, useMutation } from "@apollo/client"; import gql from "graphql-tag";
注意
Apollo 钩子必须有 ApolloProvider
调用查询和更改钩子的组件必须是 ApolloProvider 的后代Atlas App Services 为 后端配置的。钩子调用 查询 和 突变 提供的client
对象上的方法。
运行查询
Apollo 客户端包含两个用于执行查询的钩子。钩子接受相同的参数,但在执行查询时有所不同:
useQuery() 会在其组件加载时自动运行。它还会返回回调函数,且每当调用此函数时均会重新运行该查询。
useLazyQuery() 返回一个回调函数,会在每次调用时执行查询。它不会在安装组件时运行查询。
两个挂钩都接受查询定义和附加选项,包括 Apollo 传递给查询的variables
。此外,它们都会返回有关查询当前执行状态的信息和从最近执行返回的数据。
const ALL_MOVIES = gql` query AllMovies { movies { _id title year runtime } } `; // Must be rendered inside of an ApolloProvider function Movies() { const { loading, error, data } = useQuery(ALL_MOVIES); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error}</div>; } return <MovieList movies={data.movies} />; }
运行修改
useMutation() 钩子接受突变定义和可选配置对象。您需要传递的最常见选项是映射到variables
GraphQL 变量 的 对象 在突变定义中。
钩子以数组形式返回多个对象:
执行更改的回调函数
一个对象,其中包含有关更改执行状态的信息和从最近执行返回的数据。
const UPDATE_MOVIE_TITLE = gql` mutation UpdateMovieTitle($oldTitle: String!, $newTitle: String!) { updateOneMovie(query: { title: $oldTitle }, set: { title: $newTitle }) { title year } } `; // Must be rendered inside of an ApolloProvider function MovieList({ movies }) { const [updateMovieTitle] = useMutation(UPDATE_MOVIE_TITLE); return ( <ul> {movies.map((movie) => ( <li key={movie._id}> <div>{movie.title}</div> <button onClick={() => { updateMovieTitle({ variables: { oldTitle: movie.title, newTitle: "Some New Title", }, }); }} > Update Title </button> </li> ))} </ul> ); }
Paginate Data
您可以使用 API 生成的 GraphQL 模式提供的类型,对查询中的数据进行分页。您可以通过 Apollo GraphQL 客户端,使用 useQuery()
和 useLazyQueryHook()
钩子对数据进行分页。
Atlas GraphQL API 没有像 GraphQL 文档建议的那样用于分页的 offset
操作符。
以下示例使用 useQuery()
钩子和 GraphQL 查询,它们可以按升序和降序查询数据,具体取决于您向其传递的变量。
const PAGINATE_MOVIES = gql` query PaginateMovies( $prevTitle: String $nextTitle: String $limit: Int! $sortDirection: MovieSortByInput! ) { movies( # Can add other query filters here if you'd like query: { title_gt: $prevTitle, title_lt: $nextTitle } limit: $limit sortBy: $sortDirection ) { _id title year } } `; const resultsPerPage = 5; function PaginateMovies() { const [variables, setVariables] = useState({ prevTitle: undefined, nextTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); const [firstTitle, setFirstTitle] = useState(); const { data, error, loading } = useQuery(PAGINATE_MOVIES, { variables, }); const [pagePreviousDisabled, setPagePreviousDisabled] = useState(true); const [pageNextDisabled, setPageNextDisabled] = useState(false); useEffect(() => { if (data?.movies?.length && firstTitle === undefined) { setFirstTitle(data.movies[0].title); } setPagePreviousDisabled(false); if (data?.movies?.length < resultsPerPage) { setPageNextDisabled(true); setPagePreviousDisabled(false); } if ( variables.prevTitle === undefined || data?.movies[0]?.title === firstTitle ) { setPagePreviousDisabled(true); setPageNextDisabled(false); } }, [data, data?.movies?.length, firstTitle, variables.prevTitle]); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error.message}</div>; } function goToNextPage() { setVariables({ nextTitle: undefined, prevTitle: data.movies[data.movies.length - 1].title, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); } function goToPrevPage() { setVariables({ nextTitle: data.movies[0].title, prevTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_DESC", }); } const sorted = data.movies.sort((a, b) => { const titleA = a.title.toUpperCase(); // ignore upper and lowercase const titleB = b.title.toUpperCase(); // ignore upper and lowercase if (titleA < titleB) { return -1; // titleA comes first } if (titleA > titleB) { return 1; // titleB comes first } }); return ( <div> <h1>Movies</h1> {data?.movies?.length ? ( sorted.map((movie) => ( <div key={movie._id}> <h3>{movie.title}</h3> <p>Year Published: {" " + movie.year}</p> <br /> </div> )) ) : ( <p>No movies in system</p> )} <div> <button disabled={pagePreviousDisabled} onClick={goToPrevPage}> ← Previous Page </button> <button disabled={pageNextDisabled} onClick={goToNextPage}> Next Page → </button> </div> </div> ); }
刷新访问令牌
使用 Realm GraphQL 和 Apollo Client 时,访问令牌会在授予后 30 分钟后过期。您可以使用 Realm Web SDK 的 refreshAccessToken()
方法刷新用户访问令牌。
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });