graphql入门(二)

graphql 前端页面的应用

前端 graphql 简单应用

上次我们讲到了如何搭建一个简单的 graphql 中间层服务器,复习一下,那接下来我们就需要在前端调用这个服务。首先我们创建一个叫做 “fe-react” 的 react 项目:

1
create-react-app fe-react && cd fe-react && npm run start

浏览器会自动访问 localhost:3000,这样我们看到了我们熟悉的界面。
接下来我们需要安装 graphql 需要的一些依赖

1
npm install apollo-boost react-apollo graphql

依赖安装完成后我们来创建一个 apollo-client,这个 client 是为了连接我们的中间层并且请求数据用的,我们在 src 下面创建一个 graphql 的文件夹,用来管理我们的 graphql 请求,接着我们创建 client.js 文件,内容如下:

1
2
3
4
5
import ApolloClient from 'apollo-boost';

export const client = new ApolloClient({
uri: 'http://localhost:4000/graphql'
});

这样我们的 client 就创建好了,接下来我们要开始请求数据了,在 src/graphql/文件夹下面创建 server.js 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { gql } from 'apollo-boost';
import { client } from './client';

export function getGrade(gradeId) {
return client.query({
query: gql`
{
grade(gradeId: ${gradeId}) {
id
name
}
}
`
});
}

我们先看看这段代码,client.query 代表的是请求是 query 类型,传参为一个对象,对象中的 query 为请求主体,${gradeId}是使用了字符串模版,其实更合理的写法是增加一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function getGrade(gradeId) {
return client.query({
query: gql`
query getGrade($gradeId: Int!) {
grade(gradeId: $gradeId) {
id
name
}
}
`,
variables: {
gradeId
}
});
}

来我们看这一行:query getGrade($gradeId: Int!)
,变量及其类型需要在操作中申明,并且类型需要和请求定义的类型一致,用一个“$”来表示变量,如果有变量,那么需要给client.query增加variables的参数,对应在 graphql playground 中的操作为

1
2
3
4
5
6
query getGrade($gradeId: Int!) {
grade(gradeId: $gradeId) {
id
name
}
}

还需要在左下方的“QUERY VARIABLES”中增加

1
2
3
{
"gradeId": 1
}

“QUERY VARIABLES”比较不起眼,在左下角,和它并列的还有“HTTP HEADERS”,注意不要搞混。
到这里大家可能会有个疑问query getGrade($gradeId: Int!)这句里面的“getGrade”是哪来的呢?它其实只是自己定义的一个操作名字,是为了更方便的查询日志和调试,虽然这个名字不影响功能,但是它的定义应该考虑其功能,不应该随便起个名字。下面是反面教材

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function getGrade(gradeId) {
return client.query({
query: gql`
query a1($gradeId: Int!) {
grade(gradeId: $gradeId) {
id
name
}
}
`,
variables: {
gradeId
}
});
}

改成这样功能一点不受影响,但是为调试和查找日志带来了很大的隐患。

上面这些呢就已经在前端定义了一个查询接口,我们直接在 src/app.js 中调用一下
首先引入方法

1
import { getGrade } from './graphql/server';

function App() { 下增加

1
2
3
getGrade(1).then(res => {
console.log(res);
});

控制台就可以打印出数据,那我们接口就调用完成了,只要理解了 query,那 mutation 就可以轻易的举一反三。那么接下来介绍一下其他炫酷的写法

前端 graphql+react 的炫酷操作

  • ApolloProvider
    这个东西可以将之前我们定义的那个client绑定到 react 组件的上下文中,我们改造我们的 src/app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import React from 'react';
    import { ApolloProvider } from 'react-apollo';
    import logo from './logo.svg';
    import './App.css';
    import { client } from './graphql/client';

    function App() {
    return (
    <div className="App">
    <ApolloProvider client={client}>
    <header className="App-header">
    <img src={logo} className="App-logo" alt="logo" />
    <p>
    Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
    className="App-link"
    href="https://reactjs.org"
    target="_blank"
    rel="noopener noreferrer"
    >
    Learn React
    </a>
    </header>
    </ApolloProvider>
    </div>
    );
    }

    export default App;

    是不是感觉没啥用?不要急,我们认识另一个新组件 Query

  • Query

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import React from 'react';
    import { ApolloProvider, Query } from 'react-apollo';
    import { gql } from 'apollo-boost';
    import logo from './logo.svg';
    import './App.css';
    import { client } from './graphql/client';

    function App() {
    return (
    <div className="App">
    <ApolloProvider client={client}>
    <Query
    query={gql`
    query getGrade($gradeId: Int!) {
    grade(gradeId: $gradeId) {
    id
    name
    }
    }
    `}
    variables={{
    gradeId: 1
    }}
    >
    {({ loading, error, data }) => {
    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error :(</p>;

    return <p>{data.grade.name}</p>;
    }}
    </Query>
    </ApolloProvider>
    </div>
    );
    }

    export default App;

    需要注意的是 ApolloProvider 组件应该放到所有 Query 组件和 Mutation 组件的父级之中,最好呢放在最顶端,这样不需要考虑 client 是否注入到上下文中

这里只是做了一个简单的示例,如果有兴趣,请移步官方文档查看更多的用法,

本地缓存

apollo-client 从 v2.5 版本开始提供本地状态管理的功能,这一功能完全可以替换 redux,实现所有数据的来源统一。我们了解了 ApolloProvider 组件之后,再看 ApolloConsumer 组件就会容易理解,它其实是把 client 直接绑定到了上下文里面。我们在 src 下面新建一个 components 的文件夹用来存放自定义的一些组件,新建一个 showMore.jsx 的文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import { ApolloConsumer } from 'react-apollo';

export const ShowMore = () => (
<ApolloConsumer>
{client => (
<button onClick={() => client.writeData({ data: { showMore: true } })}>
展示更多
</button>
)}
</ApolloConsumer>
);

紧接着创建 more.jsx,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const GET_SHOW_MORE_STATUS = gql`
{
showMore @client
}
`;
export const More = () => (
<Query query={GET_SHOW_MORE_STATUS}>
{({ data }) => {
if (data && data.showMore) {
return <div>我是more</div>;
}
return <div />;
}}
</Query>
);

在 app.js 中引用两个组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React from 'react';
import { ApolloProvider, Query } from 'react-apollo';
import { gql } from 'apollo-boost';
import logo from './logo.svg';
import './App.css';
import { client } from './graphql/client';
import { More } from './components/more';
import { ShowMore } from './components/showMore';

function App() {
return (
<div className="App">
<ApolloProvider client={client}>
<Query
query={gql`
query getGrade($gradeId: Int!) {
grade(gradeId: $gradeId) {
id
name
}
}
`}
variables={{
gradeId: 1
}}
>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;

return <p>{data.grade.name}</p>;
}}
</Query>
<More />
<ShowMore />
</ApolloProvider>
</div>
);
}

export default App;

在页面点击按钮的时候是不是会有变化
我们回过头总结一下发现我们通过client.writeData将数据写入到了缓存中,然后通过带有@client指令的字段从内存中获取到了数据。实际上我们更应该通过 mutation 来变更本地数据。我们应当添加我们本地的 resolvers。
我们修改 src/graphql/client.js 为下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { gql } from 'apollo-boost';

const cache = new InMemoryCache();
// 初始化缓存
cache.writeData({
data: {
showMore: false
}
});

export const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache,
resolvers: {
Query: {
showMore: (_root, variables, { cache, getCacheKey }) => {
const getShowMoreStatusQuery = gql`
{
showMore @client
}
`;
// 此处仅仅为了演示cache.readQuery,实际Query中可以不需要showMore
const data = cache.readQuery({ query: getShowMoreStatusQuery });
return data.showMore;
}
},
Mutation: {
changeShowMore: (_root, variables, { cache, getCacheKey }) => {
const getShowMoreStatusQuery = gql`
{
showMore @client
}
`;
cache.writeQuery({
query: getShowMoreStatusQuery,
data: {
showMore: !!variables.showMoreStatus
}
});
}
}
}
});

首先我们初始化缓存中的showMoreStatus,然后在 resolvers 中定义了这个字段的 query 和 mutation,接着我们修改 src/components/showMore.jsx 文件为下面内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import { Mutation } from 'react-apollo';
import { gql } from 'apollo-boost';

const SHOW_MORE = gql`
mutation changeShowMore($showMoreStatus: Int!) {
changeShowMore(showMoreStatus: $showMoreStatus) @client
}
`;
export const ShowMore = () => (
<Mutation mutation={SHOW_MORE}>
{addTodo => (
<button
onClick={() => {
addTodo({ variables: { showMoreStatus: 1 } });
}}
>
展示更多
</button>
)}
</Mutation>
);

这里我们对内存中数据的修改改成使用 mutation 的方式,注意操作本地需要@client

这样我们简易的本地缓存就搭建好了。根据上一节课我们讲到的 typeDefs 里面任何字段我们都可以重新实现一遍,那么我们也能猜到,任何字段我们都可以实现成本地缓存,只要在前端的 resolvers 中定义实现,那么请求时加上@client就会去本地获取,比如这个 query

1
2
3
4
5
6
7
8
9
10
11
12
grade(gradeId: 1) {
id
name @client
classes {
id
name
students {
id
name
}
}
}

那我只需要在本地的 reolvers 中增加

1
2
3
Grade: {
name: () => '我从本地来'
}

即可。

课件地址

https://github.com/yangjiagongzi/graphql-study