Created
September 13, 2018 01:06
-
-
Save oucb/f6c37db1d543e1090b564e98daaaec12 to your computer and use it in GitHub Desktop.
Revisions
-
djyde revised this gist
Sep 11, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # 高效的 GraphQL + Antd > 本文原载于我的独立博客 https://lutaonan.com/blog/effective-graphql-and-antd/ -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,7 @@ # Effective GraphQL + Antd > 本文原载于我的独立博客 https://lutaonan.com/blog/effective-graphql-and-antd/ 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,5 @@ # Effective GraphQL + Antd 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -732,8 +732,8 @@ export default class Pagination extends React.Component { [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Fpagination&module=%2Fchapters%2FTable%2Fpagination.tsx) ## What's next GraphQL 比 RESTful 的优势在于,GraphQL 让你专注于你想做什么,想获取什么。「查询语言」是声明式的,而「HTTP 请求」是命令式的。声明式可以让复杂度转移给运行时,就像 GraphQL 语句最终执行的 HTTP 请求可以交给像 Apollo 这样的封装去处理。 当你不再需要自己管理这么多 HTTP 请求的状态时,你就要仔细考虑你的应用到底需不需要状态管理工具了。尤其在开发中后台类的管理系统应用时,往往不会涉及复杂的数据流。[Local state is fine](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367). -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 9 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -534,10 +534,10 @@ class CreatePost extends React.Component { export default Form.create()(CreatePost); ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Fcreate&module=%2Fchapters%2FTable%2Fcreate.tsx) 和 `<Query />` 一样,`<Mutation />` 把请求状态都传递给了 children. > 在 [官方文档](https://www.apollographql.com/docs/react/essentials/mutations.html) 详细了解 `<Mutation />` 的用法 ### 操作成功后更新列表数据 @@ -730,4 +730,10 @@ export default class Pagination extends React.Component { ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Fpagination&module=%2Fchapters%2FTable%2Fpagination.tsx) ## 总结 GraphQL 比 RESTful 的优势在于,GraphQL 让你专注于你想做什么,想获取什么。「查询语言」是声明式的,而「HTTP 请求」是命令式的。声明式可以让复杂度转移给运行时,就像 GraphQL 语句最终执行的 HTTP 请求可以交给像 Apollo 这样的封装去处理。 当你不再需要自己管理这么多 HTTP 请求的状态时,你就要仔细考虑你的应用到底需不需要状态管理工具了。尤其在开发中后台类的管理系统应用时,往往不会涉及复杂的数据流。[Local state is fine.](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367). -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 13 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -162,7 +162,7 @@ server.listen().then(({ url }) => { > 你也可以通过 [Apollo Launchpad](https://launchpad.graphql.com/) 在线上快速搭建一个测试用的 GraphQL 服务. ### 最简单的前端查询 有了 GraphQL 服务后,我们开始编写前端组件。首先要创建一个 `ApolloClient` 实例。最简单的方法是通过 `apollo-boost`: @@ -193,7 +193,7 @@ const result = await apolloClient.query({ 不过,更高效的做法是用 `<Query />` 和 `<Mutation />` 组件进行声明式的查询。因为它们用了 [Function as Child Components ](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 的模式,把 `loading` 状态,返回的数据 `data` 都通过参数传递。**你不需要手动去管理请求的状态**。 ```js import { Query, ApolloProvider } from 'react-apollo' import gql from 'graphql-tag' import { Table } from 'antd' @@ -248,14 +248,12 @@ export default () => { > `<ApolloProvider />` 的作用是向所有子组件里的 `<Query />` 和 `<Mutation />` 传递 `ApolloClient` 实例. ## 进阶实例 ### 查询参数 我们希望通过一个下拉框 `<Select />` 选择需要获取的 Post 数量:  我们可以让 `posts` 查询接受一个 `limit` 参数: @@ -298,7 +296,7 @@ const resolvers = { 在前端,`<Query />` 的 `variables` props 可以传递参数: ```js import * as React from "react"; import { Table, Select } from "antd"; @@ -374,7 +372,7 @@ export default class Limit extends React.Component { > 在 [官方文档](https://graphql.org/learn/queries/#variables) 详细了解 GraphQL 查询变量定义 ### 操作数据 (Mutation) 接下来实现创建一篇 Post: @@ -424,7 +422,7 @@ const resolvers = { 前端结合 Ant.Design 的 `<Modal />`, `<Form />` 组件和 `react-apollo` 提供的 `<Mutation />` 组件,就可以完成整个「新建 Post」动作: ```js const GET_POSTS = gql` query GetPost($limit: Int) { posts(limit: $limit) { @@ -542,7 +540,7 @@ export default Form.create()(CreatePost); > 在 [官方文档](https://www.apollographql.com/docs/react/essentials/mutations.html) 详细了解 `<Mutation />` 的用法 ### 操作成功后更新列表数据 成功「新建 Post」以后,通常我们会更新数据列表。`react-apollo` 有两种方法实现。 @@ -552,7 +550,9 @@ export default Form.create()(CreatePost); 例如,在获取数据列表的 `<Query />` 中,是通过 `GET_POSTS` 来查询的: ```js query={GET_POSTS} variables={{ limit: 5 }} ``` 那么,在 `update` 回调里,我们可以得到 `GET_POSTS` 对应的 cache, 然后更新这个 cache. 更新 cache 后,通过 `GET_POSTS` (以及相同的 `variables`) 查询的组件,会自动 rerender: @@ -612,8 +612,8 @@ const typeDefs = gql` + } type Query { posts(page: Int, limit: Int): [Post] + postsWithMeta(page: Int, limit: Int!): PostResultWithMeta! } ` ``` -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 3 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -254,7 +254,9 @@ export default () => { 我们希望通过一个下拉框 `<Select />` 选择需要获取的 Post 数量:  我们可以让 `posts` 查询接受一个 `limit` 参数: -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -376,9 +376,7 @@ export default class Limit extends React.Component { 接下来实现创建一篇 Post:  当我们需要操作数据的时候,就要用到 `Mutation`. 还用到一个特殊的数据类型 [Input](https://graphql.org/learn/schema/#input-types). 通常用来在 `Mutation` 的参数里传一整个对象。 @@ -589,6 +587,8 @@ const refetch = () => { Ant.Design 的 `Table` 组件可以通过 `Pagination` 很容易地实现[分页异步加载](https://ant.design/components/table-cn/#components-table-demo-ajax).  首先先让 GraphQL 接口支持分页: ```diff -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 5 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -12,6 +12,8 @@ > 本文的前端代码在 CodeSandbox https://codesandbox.io/s/pwmrnjz2km > 本文使用大量 ES6+ 特性,请在阅读本文前熟悉 ES6+ 语法。 ## 什么是 GraphQL [GraphQL](https://graphql.org) 是一个查询语言,和 SQL 是同等概念的。 @@ -724,4 +726,6 @@ export default class Pagination extends React.Component { } } ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Fpagination&module=%2Fchapters%2FTable%2Fpagination.tsx) -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 142 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -583,3 +583,145 @@ const refetch = () => { 这样,所有 query 是 `GET_POSTS` 的组件都会重新执行查询并 rerender. ### 分页异步加载 Ant.Design 的 `Table` 组件可以通过 `Pagination` 很容易地实现[分页异步加载](https://ant.design/components/table-cn/#components-table-demo-ajax). 首先先让 GraphQL 接口支持分页: ```diff const typeDefs = gql` type Post { userId: Int! id: Int! title: String! body: String! } + type Meta { + total: Int! + } + type PostResultWithMeta { + metadata: Meta! + data: [Post]! + } type Query { posts(page: Int, limit: Int, sort: String, order: String): [Post] + postsWithMeta(page: Int, limit: Int!, sort: String, order: String): PostResultWithMeta! } ` ``` ```diff const resolvers = { Query: { async postsWithMeta(root, args) { const { page, limit } = args const res = await http.get('/posts', { params: { + _page: page, _limit: limit } }) return { + metadata: { + total: res.headers['x-total-count'] + }, + data: res.data } } }, } ``` 前端就可以传 `limit` 和 `page` 实现分页: ```js const GET_POSTS = gql` query GetPosts($limit: Int!, $page: Int) { postsWithMeta(limit: $limit, page: $page) { metadata { total }, data { id, title } } } `; export default class Pagination extends React.Component { // 传给 Ant.Design Table 的 pagination 信息 state = { pagination: { pageSize: 10, current: 1, total: 0 } }; // Query 完成后,给 pagination 设置数据总数 onCompleteQuery = ({ postsWithMeta: { metadata: { total } } }) => { const pagination = { ...this.state.pagination }; pagination.total = total; this.setState({ pagination }); }; handleTableChange = pagination => { const pager = { ...pagination }; pager.current = pagination.current; this.setState({ pagination }); }; render() { return ( <div style={{ padding: "2rem" }}> <Query onCompleted={this.onCompleteQuery} query={GET_POSTS} variables={{ // 在 pagination 信息中得到 `limit` 和 `page` limit: this.state.pagination.pageSize, page: this.state.pagination.current }} > {({ loading, data }) => { const columns = [ { title: "ID", dataIndex: "id" }, { title: "Title", dataIndex: "title" } ]; const dataSource = data.postsWithMeta ? data.postsWithMeta.data : []; return ( <Table pagination={this.state.pagination} onChange={this.handleTableChange} rowKey={record => record.id} size="small" loading={loading} dataSource={dataSource} columns={columns} /> ); }} </Query> </div> ); } } ``` -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 14 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -569,4 +569,17 @@ const update = (cache, { data: { createPost } }) => { } ``` #### 重新执行查询 有时我们想要直接重新请求数据列表而不是手动更新 cache. 我们可以使用 `refetchQueries` 返回一个你要重新查询的查询数组: ```js const refetch = () => { return [ { query: GET_POSTS } ] } ``` 这样,所有 query 是 `GET_POSTS` 的组件都会重新执行查询并 rerender. -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 27 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -393,6 +393,33 @@ const typeDefs = gql` ` ``` 然后在为 `createPost` 这个 `mutation` 创建一个 `resolver`: ```js const resolvers = { Mutation: { async createPost(root, args) { const { post } = args const res = await http.post('/posts', { data: post }) const now = Date.now() const id = Number(now.toString().slice(8, 13)) return { ...res.data.data, id, userId: 12 } } } } ``` 前端结合 Ant.Design 的 `<Modal />`, `<Form />` 组件和 `react-apollo` 提供的 `<Mutation />` 组件,就可以完成整个「新建 Post」动作: ```tsx -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -517,7 +517,7 @@ export default Form.create()(CreatePost); 成功「新建 Post」以后,通常我们会更新数据列表。`react-apollo` 有两种方法实现。 #### 更新查询的 Cache `<Mutation />` 有 `update` 这个 props. 在 `mutation` 执行成功后回调,并且带有 `cache` 和 `mutation` 的响应数据。我们可以通过更新 `cache` 来实现更新数据列表。 @@ -542,4 +542,4 @@ const update = (cache, { data: { createPost } }) => { } ``` #### 重新执行查询 -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -370,7 +370,7 @@ export default class Limit extends React.Component { > 在 [官方文档](https://graphql.org/learn/queries/#variables) 详细了解 GraphQL 查询变量定义 ### 创建数据 接下来实现创建一篇 Post: -
djyde revised this gist
Sep 11, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -513,6 +513,8 @@ export default Form.create()(CreatePost); > 在 [官方文档](https://www.apollographql.com/docs/react/essentials/mutations.html) 详细了解 `<Mutation />` 的用法 ### 创建成功后更新列表数据 成功「新建 Post」以后,通常我们会更新数据列表。`react-apollo` 有两种方法实现。 1. 更新查询的 Cache -
djyde renamed this gist
Sep 11, 2018 . 1 changed file with 24 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,3 @@ 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 @@ -192,7 +191,7 @@ const result = await apolloClient.query({ 不过,更高效的做法是用 `<Query />` 和 `<Mutation />` 组件进行声明式的查询。因为它们用了 [Function as Child Components ](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 的模式,把 `loading` 状态,返回的数据 `data` 都通过参数传递。**你不需要手动去管理请求的状态**。 ```tsx import { Query, ApolloProvider } from 'react-apollo' import gql from 'graphql-tag' import { Table } from 'antd' @@ -295,7 +294,7 @@ const resolvers = { 在前端,`<Query />` 的 `variables` props 可以传递参数: ```tsx import * as React from "react"; import { Table, Select } from "antd"; @@ -396,7 +395,7 @@ const typeDefs = gql` 前端结合 Ant.Design 的 `<Modal />`, `<Form />` 组件和 `react-apollo` 提供的 `<Mutation />` 组件,就可以完成整个「新建 Post」动作: ```tsx const GET_POSTS = gql` query GetPost($limit: Int) { posts(limit: $limit) { @@ -518,6 +517,27 @@ export default Form.create()(CreatePost); 1. 更新查询的 Cache `<Mutation />` 有 `update` 这个 props. 在 `mutation` 执行成功后回调,并且带有 `cache` 和 `mutation` 的响应数据。我们可以通过更新 `cache` 来实现更新数据列表。 例如,在获取数据列表的 `<Query />` 中,是通过 `GET_POSTS` 来查询的: `query={GET_POSTS} variables={{ limit: 5 }}` 那么,在 `update` 回调里,我们可以得到 `GET_POSTS` 对应的 cache, 然后更新这个 cache. 更新 cache 后,通过 `GET_POSTS` (以及相同的 `variables`) 查询的组件,会自动 rerender: ```ts const update = (cache, { data: { createPost } }) => { // 取得 `GET_POSTS` 对应的 cache // 注意要和你要更新的组件的 query 和 variables 都要一致 const { posts } = cache.readQuery({ query: GET_POSTS, variables: { limit: 5 } }) // 用 mutation 的响应数据更新 cache // 同样,query 和 variables 都要一致 cache.writeQuery({ query, GET_POSTS, variables: { limit: 5 }, data: { posts: [createPost].concat(posts) } }) } ``` 2. 重新执行查询 -
djyde revised this gist
Sep 10, 2018 . 1 changed file with 174 additions and 16 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -197,16 +197,18 @@ import { Query, ApolloProvider } from 'react-apollo' import gql from 'graphql-tag' import { Table } from 'antd' const GET_POSTS = gql` query GetPosts { posts { id, title } } ` const App = () => { return ( <Query query={GET_POSTS} > {({ loading, data }) => { const columns = [ @@ -245,6 +247,8 @@ export default () => { > `<ApolloProvider />` 的作用是向所有子组件里的 `<Query />` 和 `<Mutation />` 传递 `ApolloClient` 实例. ## #### 查询参数 我们希望通过一个下拉框 `<Select />` 选择需要获取的 Post 数量: @@ -278,7 +282,7 @@ const resolvers = { async posts(root, args) { // 每个 resolver 的第二个参数就是查询参数 const { limit } = args const res = await axios.get('https://jsonplaceholder.typicode.com/posts', { params: { _limit: limit } @@ -299,6 +303,14 @@ import { Table, Select } from "antd"; import { Query } from "react-apollo"; import gql from "graphql-tag"; const GET_POSTS = gql` query GetPosts($limit: Int) { posts(limit: $limit) { id, title } } ` export default class Limit extends React.Component { state = { limit: 5 @@ -312,13 +324,7 @@ export default class Limit extends React.Component { return ( <div style={{ padding: "2rem" }}> <Query query={GET_POSTS} variables={{ limit: this.state.limit }} > {({ loading, data }) => { @@ -359,7 +365,159 @@ export default class Limit extends React.Component { ); } } ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Flimit&module=%2Fchapters%2FTable%2Flimit.tsx) > 在 [官方文档](https://graphql.org/learn/queries/#variables) 详细了解 GraphQL 查询变量定义 ### Create 接下来实现创建一篇 Post:   当我们需要操作数据的时候,就要用到 `Mutation`. 还用到一个特殊的数据类型 [Input](https://graphql.org/learn/schema/#input-types). 通常用来在 `Mutation` 的参数里传一整个对象。 ```js const typeDefs = gql` input CreatePostInput { title: String! body: String! } Mutation { createPost(post: CreatePostInput!): Post! } ` ``` 前端结合 Ant.Design 的 `<Modal />`, `<Form />` 组件和 `react-apollo` 提供的 `<Mutation />` 组件,就可以完成整个「新建 Post」动作: ```js const GET_POSTS = gql` query GetPost($limit: Int) { posts(limit: $limit) { id, title } } `; // 「新建 Post」 的 Muation const CREATE_POST = gql` mutation CreatePost($post: CreatePostInput!) { createPost(post: $post) { id, title } } ` class CreatePost extends React.Component { state = { modalVisible: false }; showModal = () => { this.setState({ modalVisible: true }); }; closeModal = () => { this.setState({ modalVisible: false }); }; // Modal 的 onOk 事件 onCreatePost = createPost => { const { form } = this.props; form.validateFields(async (err, values) => { if (!err) { // `createPost` 是 `<Mutation />` 组件传给 children 的 mutation 方法 await createPost({ variables: { post: values } }); this.closeModal(); form.resetFields(); } }); }; render() { const { form } = this.props; return ( <div style={{ padding: "2rem" }}> <Query query={GET_POSTS} variables={{ limit: 5 }}> {({ loading, data }) => { const columns = [ { title: "ID", dataIndex: "id" }, { title: "Title", dataIndex: "title" } ]; const dataSource = data.posts || []; return ( <React.Fragment> <Mutation mutation={CREATE_POST}> {(createPost, { loading, data }) => { return ( <Modal onOk={e => this.onCreatePost(createPost)} onCancel={this.closeModal} title="Create Post" confirmLoading={loading} visible={this.state.modalVisible} > <Form> <Form.Item label="Title"> {form.getFieldDecorator("title", { rules: [{ required: true }] })(<Input />)} </Form.Item> <Form.Item label="Body"> {form.getFieldDecorator("body", { rules: [{ required: true }] })(<Input.TextArea />)} </Form.Item> </Form> </Modal> ); }} </Mutation> <div style={{ marginBottom: "12px" }}> <Button onClick={this.showModal} type="primary"> New Post </Button> </div> <Table rowKey={record => record.id} size="small" loading={loading} dataSource={dataSource} columns={columns} /> </React.Fragment> ); }} </Query> </div> ); } } export default Form.create()(CreatePost); ``` 和 `<Query />` 一样,`<Mutation />` 把请求状态都传递给了 children. [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Fcreate&module=%2Fchapters%2FTable%2Fcreate.tsx) > 在 [官方文档](https://www.apollographql.com/docs/react/essentials/mutations.html) 详细了解 `<Mutation />` 的用法 成功「新建 Post」以后,通常我们会更新数据列表。`react-apollo` 有两种方法实现。 1. 更新查询的 Cache 2. 重新执行查询 -
djyde revised this gist
Sep 10, 2018 . 1 changed file with 123 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,7 +7,7 @@ 这篇文章的目标就是让你认识 GraphQL / Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。 > _Keep frontend code lean and straight._ > > —— Randy Lu @@ -195,14 +195,15 @@ const result = await apolloClient.query({ ```js import { Query, ApolloProvider } from 'react-apollo' import gql from 'graphql-tag' import { Table } from 'antd' const App = () => { return ( <Query query={gql` query { posts { id, title } } `} @@ -240,6 +241,125 @@ export default () => { } ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable&module=%2Fchapters%2FTable%2Findex.tsx) > `<ApolloProvider />` 的作用是向所有子组件里的 `<Query />` 和 `<Mutation />` 传递 `ApolloClient` 实例. #### 查询参数 我们希望通过一个下拉框 `<Select />` 选择需要获取的 Post 数量:  我们可以让 `posts` 查询接受一个 `limit` 参数: ```diff import gql from 'graphql-tag' const typeDefs = gql` type Post { userId: Int! id: Int! title: String! body: String! } type Query { + posts(limit: Int): [Post] } ` ``` 然后在 `resolvers` 里拿到参数,进行处理: ```js const resolvers = { Query: { async posts(root, args) { // 每个 resolver 的第二个参数就是查询参数 const { limit } = args const res = await axios.get('https://jsonplaceholder.typicode.com/posts?', { params: { _limit: limit } }) return res.json() } } } ``` 在前端,`<Query />` 的 `variables` props 可以传递参数: ```js import * as React from "react"; import { Table, Select } from "antd"; import { Query } from "react-apollo"; import gql from "graphql-tag"; export default class Limit extends React.Component { state = { limit: 5 }; onChangeLimit = limit => { this.setState({ limit }); }; render() { return ( <div style={{ padding: "2rem" }}> <Query query={gql` query GetPosts($limit: Int) { posts(limit: $limit) { id, title } } `} variables={{ limit: this.state.limit }} > {({ loading, data }) => { const columns = [ { title: "ID", dataIndex: "id" }, { title: "Title", dataIndex: "title" } ]; const dataSource = data.posts || []; return ( <React.Fragment> <div style={{ marginBottom: "12px" }}> <Select onChange={this.onChangeLimit} value={this.state.limit} style={{ width: "100px" }} > <Select.Option value={5}>5</Select.Option> <Select.Option value={10}>10</Select.Option> <Select.Option value={15}>15</Select.Option> </Select> </div> <Table rowKey={record => record.id} size="small" loading={loading} dataSource={dataSource} columns={columns} /> </React.Fragment> ); }} </Query> </div> ); } } ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable%2Flimit&module=%2Fchapters%2FTable%2Flimit.tsx) -
djyde revised this gist
Sep 10, 2018 . 1 changed file with 80 additions and 56 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,4 @@ # Effective Apollo + Antd 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 @@ -54,60 +53,17 @@ mutation { [Apollo](https://apollographql.com) 是一系列的 GraphQL 工具链,从客户端(不同的前端框架)到服务器端都提供了使用和搭建 GraphQL 的工具。 下面会通过一个简单的例子,让你从前端到服务器端对 GraphQL 有个初步的了解。 想象有这样一个需求:用表格展示一组数据。  后端告诉你,有如下接口: - https://jsonplaceholder.typicode.com/posts 这个接口可以获取所有 `Post`, 返回的格式如下: ```ts interface Post { @@ -118,7 +74,9 @@ interface Post { } ``` 第一步我们需要搭建一个 GraphQL 服务器。 ### 搭建一个 GraphQL 服务器 搭建一个 GraphQL 服务器不难,Apollo Server 对主流的 Node.js Web 框架都有封装,本文不赘述如何搭建一个 GraphQL 服务器,只介绍 GraphQL 后端编写的一些概念。 @@ -138,12 +96,7 @@ const typeDefs = gql` title: String! body: String! } ` ``` 另外,我们需要对 `Query` 进行定义,来定义有哪些查询操作: @@ -204,18 +157,89 @@ server.listen().then(({ url }) => { }) ``` > 我在本文用到的 GraphQL 服务器源码在 https://github.com/djyde/graphql-jsonplaceholder , 通过 https://graphql-jsonplaceholder.now.sh 可以访问 Playground. > 你也可以通过 [Apollo Launchpad](https://launchpad.graphql.com/) 在线上快速搭建一个测试用的 GraphQL 服务. ### 前端进行查询 有了 GraphQL 服务后,我们开始编写前端组件。首先要创建一个 `ApolloClient` 实例。最简单的方法是通过 `apollo-boost`: ```ts import ApolloClient from "apollo-boost"; const apolloClient = new ApolloClient({ // GraphQL 服务器地址 uri: "https://graphql-jsonplaceholder.now.sh" }); ``` `ApolloClient` 可以命令式地进行查询: ```js const result = await apolloClient.query({ query: gql` query { posts { id, title, body } } ` }) ``` 不过,更高效的做法是用 `<Query />` 和 `<Mutation />` 组件进行声明式的查询。因为它们用了 [Function as Child Components ](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 的模式,把 `loading` 状态,返回的数据 `data` 都通过参数传递。**你不需要手动去管理请求的状态**。 ```js import { Query, ApolloProvider } from 'react-apollo' import gql from 'graphql-tag' const App = () => { return ( <Query query={gql` query { posts { id, title, body } } `} > {({ loading, data }) => { const columns = [ { title: "ID", dataIndex: "id" }, { title: "Title", dataIndex: "title" } ] const dataSource = data.posts || [] return ( <Table size="small" loading={loading} dataSource={dataSource} columns={columns} /> ); }} </Query> ) } export default () => { return ( <ApolloProvider client={apolloClient}> <App /> </ApolloProvider> ) } ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable) > `<ApolloProvider />` 的作用是向所有子组件里的 `<Query />` 和 `<Mutation />` 传递 `ApolloClient` 实例. -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 27 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,7 +6,7 @@ 真的需要状态管理库吗?在之前,我没有信心回答这个问题。但在使用了 [GraphQL](https://graphql.org) ([Apollo](https://apollographql.com)) 后,我确信,在大多数场景,你不再需要状态管理。 这篇文章的目标就是让你认识 GraphQL / Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。 > _Let's keep frontend code lean and straight!_ > @@ -149,7 +149,7 @@ const typeDefs = gql` 另外,我们需要对 `Query` 进行定义,来定义有哪些查询操作: ```diff import gql from 'graphql-tag' const typeDefs = gql` type Post { @@ -194,3 +194,28 @@ const resolvers = { > 在 [官方文档](https://www.apollographql.com/docs/apollo-server/v2/essentials/data.html) 详细了解 `resolvers` 的用法。 最后,通过 Apollo Server 把 `typeDefs` 和 `resolvers` 连起来,一个 GraphQL 服务器就成功搭起来了。 ```js const server = new ApolloServer({ typeDefs, resolvers }) server.listen().then(({ url }) => { console.log(`Ready at ${url}`) }) ``` 我在本文用到的 GraphQL 服务器源码在 https://github.com/djyde/graphql-jsonplaceholder , 通过 https://graphql-jsonplaceholder.now.sh 可以访问 Playground. #### 前端进行查询 有了 GraphQL 服务后,我们开始编写前端组件。首先要创建一个 `ApolloClient` 实例。最简单的方法是通过 `apollo-boost`: ```ts import ApolloClient from "apollo-boost"; export default new ApolloClient({ // GraphQL 服务器地址 uri: "https://graphql-jsonplaceholder.now.sh" }); ``` -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -105,7 +105,7 @@ const App = () => { 后端告诉你,有如下接口: - https://jsonplaceholder.typicode.com/posts 这个接口可以获取所有 `Posts`, 返回的格式如下: @@ -129,7 +129,7 @@ interface Post { 我们已经知道 `Post` 的数据类型是怎样的,就可以编写 `Post` 的类型定义: ```js import gql from 'graphql-tag' const typeDefs = gql` type Post { -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 95 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -18,7 +18,7 @@ [GraphQL](https://graphql.org) 是一个查询语言,和 SQL 是同等概念的。 举个例子,在 RESTful 的场景里,我们查询一个资源是通过命令式地进行网络请求: ```js const posts = await fetch('/api/v1/posts') @@ -37,7 +37,7 @@ query { 写数据时,命令式地 POST: ```js const response = await fetch('/api/v1/posts', { method: 'POST', body: { title: "foo", body: "content" } } ) ``` 使用 GraphQL, 声明式地触发 mutation: @@ -91,7 +91,7 @@ const App = () => { ``` 你可以看到,`<Query />` 是一个高阶组件 ([High Order Component](https://reactjs.org/docs/higher-order-components.html)), 并且用了 [Function as Child Components ](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 的模式,把 `loading` 状态,返回的数据 `data` 都通过参数传递。 ## Real world examples @@ -102,3 +102,95 @@ const App = () => { 现在有一个需求:用表格展示一组数据。  后端告诉你,有如下接口: https://jsonplaceholder.typicode.com/posts 这个接口可以获取所有 `Posts`, 返回的格式如下: ```ts interface Post { userId: number, id: number, title: string, body: string } ``` #### 搭建一个 GraphQL 服务器 搭建一个 GraphQL 服务器不难,Apollo Server 对主流的 Node.js Web 框架都有封装,本文不赘述如何搭建一个 GraphQL 服务器,只介绍 GraphQL 后端编写的一些概念。 用 Apollo Server 编写 GraphQL 服务器有两个主要概念,`typeDefs` 和 `resolvers`. `typeDefs` 指的是类型定义。GraphQL 是一个有类型系统的查询语言,因此在编写 GraphQL 服务时,要先对查询的数据类型进行定义。 我们已经知道 `Post` 的数据类型是怎样的,就可以编写 `Post` 的类型定义: ```js import gql from 'graphql-tag` const typeDefs = gql` type Post { userId: Int! id: Int! title: String! body: String! } type Query { posts: [Post] } ` ``` 另外,我们需要对 `Query` 进行定义,来定义有哪些查询操作: ```diff import gql from 'graphql-tag` const typeDefs = gql` type Post { userId: Int! id: Int! title: String! body: String! } + type Query { + posts: [Post] + } ` ``` > 在 [官方文档](https://graphql.org/learn/schema/) 详细了解 GraphQL 的类型系统。 这样一来,外界就可以通过 ```js query { posts { id, title } } ``` 这样的查询语句查询到 `posts` 了。 光是类型定义还不够,因为服务器还不知道「查询 posts」这个操作到底应该做什么。这里就是 `resolvers` 要做的事了。在 `resolvers` 里定义查询的实际行为: ```js const resolvers = { Query: { async posts() { const res = await fetch('https://jsonplaceholder.typicode.com/posts') return res.json() } } } ``` > 在 [官方文档](https://www.apollographql.com/docs/apollo-server/v2/essentials/data.html) 详细了解 `resolvers` 的用法。 -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 92 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -9,5 +9,96 @@ 这篇文章的目标就是让你认识 GraphQL/Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。 > _Let's keep frontend code lean and straight!_ > > —— Randy Lu > 本文的前端代码在 CodeSandbox https://codesandbox.io/s/pwmrnjz2km ## 什么是 GraphQL [GraphQL](https://graphql.org) 是一个查询语言,和 SQL 是同等概念的。 举个例子,在 RESTful 的场景里,我们查询一个资源是命令式地进行网络请求: ```js const posts = await fetch('/api/v1/posts') ``` 而使用 GraphQL, 是声明式地查询: ```gql query { posts { title, body, id } } ``` 写数据时,命令式地 POST: ```js const response = await fetch('/api/v1/posts', { method: 'POST', body: {} } ) ``` 使用 GraphQL, 声明式地触发 mutation: ```gql mutation { createPost(post: { title: "foo", body: "content" }) } ``` 你也许会疑惑,这些 GraphQL 语句怎么执行?其实这些语句需要被转换,而转换的工具就是接下来要介绍的 Apollo. ## 什么是 Apollo [Apollo](https://apollographql.com) 是一系列的 GraphQL 工具链,从客户端(不同的前端框架)到服务器端都提供了使用和搭建 GraphQL 的工具。 ### react-apollo `react-apollo` 是 Apollo Client 的 React 封装,它提供 `<Query />` 和 `<Mutation />` 组件,可以声明式地做 GraphQL `query` 和 `mutation`. Query: ```tsx import gql from 'graphql-tag' import { Query } from 'react-apollo' const query = gql` query { posts { title, id, body } } ` const App = () => { return ( <Query query={query} > {({ loading, data }) => { if (loading) return "Loading..." return ( <div>{data.posts.length}</div> ) }} </Query> ) } ``` 你可以看到,`<Query />` 是一个高阶组件 ([High Order Component](https://reactjs.org/docs/higher-order-components.html)), 并且用了 [Function as Child Components ](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 的模式,把 `loading` 状态,返回的数据 `data` 都通过函数传递了。 ## Real world examples 对 GraphQL 和 Apollo 有了初步的了解后,下面会通过一些真实的例子,来让你了解到 GraphQL 从服务器端到客户端的开发体验。 ### 数据展示表格 现在有一个需求:用表格展示一组数据。  -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,5 @@ # Effective Apollo + Antd 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 @@ -8,4 +9,5 @@ 这篇文章的目标就是让你认识 GraphQL/Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。 > _Let's keep frontend code lean and straight!_ > > —— Randy Lu -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 6 additions and 48 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,53 +1,11 @@ # Effective Apollo + Antd 在过去的几年,不论是面向内部的系统,还是面向外部的产品,我们都大量地使用了 [Ant.Design](http://ant.design) —— 一个基于 React 的 UI 组件库。 在做内部系统时,Ant.Design 解决了几乎 60% 的问题。剩下的问题在业务逻辑和代码组织的复杂度。我见过很多内部系统因为滥用状态管理而使代码变得复杂,他们之所以使用状态管理库,并不是因为应用的状态复杂,而是因为需要一个状态树来管理网络请求的状态、接口返回的数据等等这些和接口相关的状态。 真的需要状态管理库吗?在之前,我没有信心回答这个问题。但在使用了 [GraphQL](https://graphql.org) ([Apollo](https://apollographql.com)) 后,我确信,在大多数场景,你不再需要状态管理。 这篇文章的目标就是让你认识 GraphQL/Apollo, 以及在 Ant.Design 里如何高效地使用他。你不必担心 GraphQL 会给你带来负担,学习和使用 GraphQL 都是令人愉快的过程。你会发现以往让你感到厌烦的需要重复编写的逻辑,可以不必再写了。 > _Let's keep frontend code lean and straight!_ > —— Randy Lu -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 6 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,9 @@ # Effective Apollo + Antd ## 场景 ### CRUD 表格 ```tsx import * as React from "react"; -
djyde revised this gist
Sep 6, 2018 . 1 changed file with 47 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,47 @@ ```tsx import * as React from "react"; import { Button, Table, Row, Col } from "antd"; import { Query } from "react-apollo"; import gql from "graphql-tag"; export default () => { return ( <Row> <Query query={gql` query { posts(limit: 10) { id, title } } `} > {({ loading, data }) => { const columns = [ { title: "ID", dataIndex: "id" }, { title: "Title", dataIndex: "title" } ]; const dataSource = data.posts || []; return ( <Table size="small" loading={loading} dataSource={dataSource} columns={columns} /> ); }} </Query> </Row> ); }; ``` [](https://codesandbox.io/s/pwmrnjz2km?initialpath=%2Ftable) -
djyde created this gist
Sep 6, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ <iframe src="https://codesandbox.io/embed/pwmrnjz2km?initialpath=%2Ftable" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>