apollo client学习笔记
查询用法:
import { gql, useQuery } from '@apollo/client';
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
}
}
`;
function Dogs({ onDogSelected }) {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<select name="dog" onChange={onDogSelected}>
{data.dogs.map(dog => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}
无需定义state, const { loading, error, data } = useQuery(GET_DOGS);即是当前的状态。
缓存查询结果
每次查询的结果都会自动缓存在内存之中,下次请求结果将用本地缓存
查询的时候携带参数
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}
刷新缓存
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
pollInterval: 500,
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}
或者调用refetch方法手动刷新:
import { NetworkStatus } from '@apollo/client';
function DogPhoto({ breed }) {
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true,
},
);
if (networkStatus === NetworkStatus.refetch) return 'Refetching!';
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
}
手动获取数据
import React from 'react';
import { useLazyQuery } from '@apollo/client';
function DelayedQuery() {
const [getDog, { loading, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
return (
<div>
{data && data.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}
获取数据的策略(是否每次从网络获取),
默认是从本地缓存获取(cache-first),要修改这个策略可以在query的时候指定:
const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: "network-only"
});
支持的策略列表为:
cache-first
:Apollo Client首先对缓存执行查询。如果所有请求的数据都在缓存中,则返回该数据。否则,Apollo Client将对GraphQL服务器执行查询,并在缓存数据后返回该数据。
优先级最小化应用程序发送的网络请求数量。
这是默认的获取策略。
cache-only
:Apollo Client只对缓存执行查询。在这种情况下,它从不查询您的服务器。
如果缓存不包含所有请求字段的数据,则仅缓存查询将抛出错误。
cache-and-network
:Apollo Client对缓存和GraphQL服务器执行完整的查询。如果服务器端查询的结果修改了缓存字段,则查询将自动更新。
提供快速响应,同时也有助于保持缓存数据与服务器数据一致
network-only
:Apollo Client对GraphQL服务器执行完整的查询,而不首先检查缓存。查询的结果存储在缓存中。
优先考虑与服务器数据的一致性,但不能在缓存数据可用时提供近乎即时的响应。
no-cache
:服务器请求数据本地不做缓存
standby
:使用与cache-first相同的逻辑,只是此查询在基础字段值更改时不会自动更新。您仍然可以使用refetch和updateQueries手动更新该查询。
添加、更新数据
import { gql, useMutation } from '@apollo/client';
const ADD_TODO = gql`
mutation AddTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;
function AddTodo() {
let input;
const [addTodo, { data }] = useMutation(ADD_TODO);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
如上,useMutation
不会立即执行ADD_TODO
,而是会返回一个函数(addTodo),这个函数需要在需要的时候手动执行,如上是在onSubmit里面执行的:
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = '';
}}
更新数据
const UPDATE_TODO = gql`
mutation UpdateTodo($id: String!, $type: String!) {
updateTodo(id: $id, type: $type) {
id
type
}
}
`;
function Todos() {
const { loading, error, data } = useQuery(GET_TODOS);
const [updateTodo] = useMutation(UPDATE_TODO);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.todos.map(({ id, type }) => {
let input;
return (
<div key={id}>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
</div>
);
});
}
上面onSubmit之后,服务器端数据会更新,而且本地缓存数据也会自动更新,界面会重新render.
更新多项数据
如果是列表中删除或者新增一项,本地数据不会自动更新,需要调用update方法来手动更新:
const GET_TODOS = gql`
query GetTodos {
todos {
id
}
}
`;
function AddTodo() {
let input;
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = "";
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
数据订阅
通常不要使用订阅功能,使用轮询、重新获取等方式都能满足一般需求,数据订阅基于websockt,服务器端有更新之后,会主动更新客户端数据,主要用于以下情况:
1、对大对象的微小的增量变化。反复轮询大型对象的开销很大,特别是在对象的大部分字段很少更改的情况下。相反,您可以通过查询获取对象的初始状态,并且您的服务器可以在更新发生时主动地将更新推送到各个字段。
2、低延迟的实时更新。例如,聊天应用程序的客户机希望在新消息可用时立即接收它们。
服务器端实现:
type Subscription {
commentAdded(postID: ID!): Comment
}
客户端:
const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
用来做restful api请求
比如我们想用它做登录操作,而登录操作是restful写好的接口,postman信息如下:
image.png
yarn add apollo-link-rest
yarn add graphql-anywhere -d
新建文件signin.ts
import { gql } from "@apollo/client";
export const signin = gql`
query Signin($input: AccountInfo!) {
signin: login(body: $input)
@rest(
type: "Login"
path: "/oauth/token"
method: "POST"
bodyKey: "body"
) {
access_token
id_token
scope
expires_in
token_type
}
}
`;
添加restful client
const createApolloClient = (
token: string,
isRest?: boolean
): ApolloClient<NormalizedCacheObject> => {
globalToken = token;
const links: ApolloLink[] = [
errorLink,
authLink(),
// add the named link upon development
// ...(process.env.NODE_ENV !== "production" ? [namedLink()] : []),
httpLink,
];
const restLink = new RestLink({ uri: AuthConfig.Domain });
client = new ApolloClient({
link: isRest ? restLink : from(links),
cache: new InMemoryCache(),
});
return client;
};
const result = await restClient.query<ILoginResponseProps>({
query: signin,
variables: {
input: {
realm: "Username-Password-Authentication",
audience: AuthConfig.Audience,
client_id: AuthConfig.ClientId,
scope: "openid email name profile",
grant_type: "http://auth0.com/oauth/grant-type/password-realm",
username: username,
password: password,
},
},
});
登录请求,这里注意
image.png
1是把返回结果加一个signin字段,2和3名字必须对应起来,