# hackernews-react-apollo **Repository Path**: custer_git/hackernews-react-apollo ## Basic Information - **Project Name**: hackernews-react-apollo - **Description**: 学习:https://www.howtographql.com - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://news.ycombinator.com/news - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2017-08-20 - **Last Updated**: 2022-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # hackernews-react-apollo :fire: [学习](https://www.howtographql.com) [效果](https://news.ycombinator.com/news) :fire: ### :rocket: [安装和初始化](#part1) ### :rocket: [Query: 查询价值Link](#part2) ### :rocket: [Mutation: CreateLink](#part3) ### :rocket: [Routing](#part4) ### :rocket: [Authentication 认证](#part5) ### :rocket: [More Mutations and Updating the Store](#part6) ### :rocket: [Filtering: Searching the List of Links](#part7) ### :rocket: [Realtime Updates with GraphQl](#part8) ### :rocket: [Pagination](#part9) ### :rocket: [Summary](#part10) ## :checkered_flag: 安装和初始化 ### 工具安装 create-react-app graph cool yarn 我们需要在命令行中安装 create-react-app 工具,graphcool工具,你可能还需要安装 yarn。 ``` $ npm install -g create-react-app yarn graphcool ``` yarn 命令是 ``` yarn global add create-react-app graph cool ``` ### 创建GraphQL Server ``` graphcool init --schema https://graphqlbin.com/hn-starter.graphql --name Hackernews ``` 浏览器会打开一个页面,需要登录,进入之后,页面显示 > Your project is ready Successfully authenticated. You can now close this tab. You'll find your GraphQL endpoints in your terminal. ![页面](https://git.oschina.net/uploads/images/2017/0820/211941_b5b9fc41_1489606.png "2017-08-20_21-04-45.png") Creating project Hackernews... 创建成功后,目录下有个文件夹 _project.graphcool_ ### 新建Frontend项目 $ create-react-app hackernews-react-apollo 工具会自动初始化一个脚手架并安装 React 项目的各种必要依赖,如果在过程中出现网络问题,请尝试配置代理或使用其他 npm registry。 然后我们进入项目并启动。 $ cd hackernews-react-apollo $ yarn start 此时浏览器会访问 http://localhost:3000/ ,看到 Welcome to React 的界面就算成功了。 把project.graphcool 文件夹拷贝到 hackernews-react-apollo 目录下 然后清理下目录结构如下图,方便开发: ``` . ├── README.md ├── node_modules ├── project.graphcool ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.test.js │ ├── components │ │ └── App.js │ ├── index.js │ ├── logo.svg │ ├── registerServiceWorker.js │ └── styles │ ├── App.css │ └── index.css └── yarn.lock ``` ### 准备样式 在 public/index.html 中新建一个 link ``` ``` 打开 index.css 替换成下面的代码 ``` body { margin: 0; padding: 0; font-family: Verdana, Geneva, sans-serif; } input { max-width: 500px; } .gray { color: #828282; } .orange { background-color: #ff6600; } .background-gray { background-color: rgb(246,246,239); } .f11 { font-size: 11px; } .w85 { width: 85%; } .button { font-family: monospace; font-size: 10pt; color: black; background-color: buttonface; text-align: center; padding: 2px 6px 3px; border-width: 2px; border-style: outset; border-color: buttonface; cursor: pointer; max-width: 250px; } ``` ### 安装 react-apollo ``` yarn add react-apollo ``` 下面就可以开始编写代码了 ### 配置 ApolloClient ``` import React from 'react'; import ReactDOM from 'react-dom'; import './styles/index.css'; import App from './components/App'; import registerServiceWorker from './registerServiceWorker'; // 导入需要的模块 import { ApolloProvider, createNetworkInterface, ApolloClient } from 'react-apollo' /** * Apollo Client有一个可插拔的网络接口层,可以配置网络接口 使用 createNetworkInterface * 修改GraphQL服务器的URL,创建自定义的NetworkInterface * * Query batching:当多个请求在一个特定的时间间隔内产生时(比如:100毫秒内) * Apollo会把多个查询组合成一个请求, * 比如在渲染一个包含导航条,边栏,内容等带有GraphQL查询的组件时 * 使用Query batching,要传递BatchedNetworkInterface给ApolloClient构造函数 */ const networkInterface = createNetworkInterface({ uri: '__SIMPLE_API_ENDPOINT__' }) /** * 实例化ApolloClient - 默认情况客户端会发送到相同主机名(域名)下的/graphql端点 * 查询去除重复(Query deduplication) * 查询去除重复可以减少发送到服务器的查询数量,默认关闭 * 通过queryDeduplication选项传递给ApolloClient构造函数开启 * 查询去重在多个组件显示相同数据的时候非常有用,避免从服务器多次获取相同的数据 */ const client = new ApolloClient ({ networkInterface }) /* 挂载组件 - 要连接客户端到React组件树,要确保ApolloProvider作为一个容器去包裹 其他的需要访问GraphQL服务器数据的React组件 */ ReactDOM.render( , document.getElementById('root') ) registerServiceWorker() ``` 打开终端,在 project.graphcool的目录下,运行 ``` graphcool endpoints ``` 拷贝 Simple API 粘贴到 src/index.js 替换 __SIMPLE_API_ENDPOINT__ 这样就可以开始加载数据到你的App里面了。 ## Query : 查询价值Link ### 准备 React components 第一步就是书写简单的单个链接Link组件,在目录src/compinents/下创建一个新的文件Link.js ``` import React, { Component } from 'react' class Link extends Component { render(){ return(
{this.props.link.description} ({this.props.link.url})
) } _voteForLink = async() => { // ...将在第六章实现这里的代码 } } export default Link ``` 下面实现渲染links列表的组件, 新建src/components/LinkList.js文件 ``` import React, { Component } from 'react' import Link from './Link' class LinkList extends Component { render(){ const linksToRender = [{ id: '1', description: 'The Coolest GraphQL Backend 😎', url: 'http://www.graph.cool' }, { id: '2', description: 'The Best GraphQL Client', url: 'http://dev.apollodata.com/' }] return(
{linksToRender.map(link => ( ))}
) } } export default LinkList ``` 把组件挂载到App.js ``` import React, { Component } from 'react'; import LinkList from './LinkList' class App extends Component { render() { return ( ); } } export default App; ``` 现在网页上有两条你刚才添加的数据,想把它变成后台加载的数据,还要等一会,加油 ### 写 GrapQL Query 查询语句 想要从数据库加载数据 链接,第一件事情就是你需要定义GraphQL查询语句 像这样语法格式: ``` query AllLinks { allLinks { id createdAt description url } } ``` ### 使用 Apollo Client 查询 当使用Apollo的时候,你有两种方式发送需要查询的语句到服务端 第一种是在ApolloClient直接使用 query 方法 一个实际的例子像下面的代码: ``` client.query({ query: gql` query AllLinks { allLinks { id } } ` }).then(response => console.log(response.data.allLinks)) ``` 打开 LinkList.js 文件,在底部书写查询语句,并且替换 export default LinkList ``` const ALL_LINKS_QUERY = gql` query AllLinksQuery { allLinks { id createdAt url description } } ` export default graphql(ALL_LINKS_QUERY, { name: 'allLinksQuery' })(LinkList) ``` 对于上面的代码,你需要导入相对应的库 ``` import { graphql, gql } from 'react-apollo' ``` 上面就是全部你查询数据的语句,现在可以把之前的渲染语句删除,替换成真正的从服务端取的数据 更新 LinkList.js 中的 render 代码, 直接展示本节课的 LinkList.js 的全部代码 ``` import React, { Component } from 'react' import Link from './Link' import { graphql, gql } from 'react-apollo' class LinkList extends Component { render(){ if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { return
Loading
} if(this.props.allLinksQuery && this.props.allLinksQuery.error){ return
Error
} const linksToRender = this.props.allLinksQuery.allLinks return(
{linksToRender.map(link => ( ))}
) } } const ALL_LINKS_QUERY = gql` query AllLinksQuery { allLinks { id createdAt url description } } ` export default graphql(ALL_LINKS_QUERY, { name: 'allLinksQuery' })(LinkList) ``` 此时,如果你没有向服务端添加数据,页面显示是空的,现在让我们往后台添加数据吧: 在命令行,cd 到 hackernews-react-apollo 目录下 运行 ``` graphcool playground ``` 此时浏览器会打开 https://console.graph.cool/, 我们在左侧 Data 中找到 Link,点击进入添加数据 左侧下方 点击 PLAYGROUND 按钮,把我们的查询代码拷贝进去 ``` query AllLinksQuery { allLinks { id createdAt url description } }Ï ``` 点击运行就可以查看到刚才添加的数据,此时,返回到前端页面,刷新页面,就可以看到后端传递过来的数据 ## Mutation: CreateLink 准备一个新的文件 src/components/CreateLink.js,书写下面的代码 ``` import React, { Component } from 'react' class CreateLink extends Component { state = { description: '', url: '' } render(){ return(
this.setState({ description: e.target.value })} type="text" placeholder='对于 link 的描述' /> this.setState({ url: e.target.value })} type="text" placeholder='link 的 url 地址' />
) } _createLink = async () => { // ...等会实现 } } export default CreateLink ``` 简单的书写两个 input 表单,提供 url 和 description。 ### 书写 Mutation 第一步:定义 mutation,并且使用 graphql,在上面的代码底部添加下面的语句,并替换export default CreateLink ``` const CREATE_LINK_MUTATION = gql` mutation CreateLinkMutation($description: String!, $url: String!) { createLink( description: $description, url: $url, ) { id createdAt url description } } ` export default graphql(CREATE_LINK_MUTATION, { name: 'createLinkMutation' })(CreateLink) ``` 在运行之前,在文件头部导入需要的模块 ``` import { graphql, gql } from 'react-apollo' ``` 实现_createLink: ``` _createLink = async () => { const { description, url } = this.state await this.props.createLinkMutation({ variables: { description, url } }) } ``` 下面在 App.js 中 render 刚书写的 CreateLink 组件 ``` import React, { Component } from 'react'; import LinkList from './LinkList' import CreateLink from './CreateLink' class App extends Component { render() { return (
); } } export default App; ``` 现在保存代码,刷新页面,会出现 input 框,填写,提交之后,刷新页面,会有刚才提交的信息 ## Routing 本节课内容是学习使用 react-router 和 Apollo 实现导航栏功能 ### 首先安装依赖的模块 ``` yarn add react-router react-router-dom ``` ### 创建一个 Header 创建一个新的头部组件,用户可以从这里完成跳转到你 app 的各个部分 src/components/Header.js ``` import React, { Component } from 'react' import { Link } from 'react-router-dom' import { withRouter } from 'react-router' class Header extends Component { render(){ return(
Hacker News
new
|
submit
) } } export default withRouter(Header) ``` 这里简单的渲染了两个 Link 组件,可以方便用户跳转到 LinkList 和 CreateLink 组件 ### 设置 routes 在根组件 App 下配置不同的 routes。打开 App.js,更新 render 代码: ``` import React, { Component } from 'react'; import LinkList from './LinkList' import CreateLink from './CreateLink' import Header from './Header' import { Switch, Route } from 'react-router-dom' class App extends Component { render() { return (
); } } export default App; ``` 最后更新 index.js 代码: ``` [ ... ] import { BrowserRouter } from 'react-router-dom' [ ... ] ReactDOM.render( , document.getElementById('root') ) registerServiceWorker() ``` ### 实现导航 需要在执行 Mutation 之后,实现从 CreateLink 到 LinkList 的自动重定向 打开 CreateLink.js 文件,书写 _createLink 代码: ``` _createLink = async () => { const { description, url } = this.state await this.props.createLinkMutation({ variables: { description, url } }) this.props.history.push('/') } ``` 这样就完成了在CreateLink之后自动跳转到LinkList页面 ## Authentication 认证 使用 Apollo 和 Graphcoll 实现认证功能完成用户的登录 ### 准备 React 相关组件 先简单实现一个初等级的 Login 组件, src/component/Login.js ``` import React, { Component } from 'react' import { GC_USER_ID, GC_AUTH_TOKEN } from '../constants' class Login extends Component { state = { login: true, // 在 login 和 signup 之间切换 email: '', password: '', name: '' } render(){ return(

{this.state.login ? 'Login' : 'Sign Up'}

{!this.state.login && this.setState({ name: e.target.value })} type="text" placeholder='用户名' />} this.setState({ email: e.target.value })} type="text" placeholder='电子邮箱地址' /> this.setState({ password: e.target.value })} type="password" placeholder='密码' />
this._confirm()} > {this.state.login ? 'login' : 'create account'}
this.setState({ login: !this.state.login })} > {this.state.login ? '需要创建一个账户?' : '已经拥有账户'}
) } _confirm = async () => { // 一会来实现 } _saveUserData = (id, token) => { localStorage.setItem(GC_USER_ID, id) localStorage.setItem(GC_AUTH_TOKEN, token) } } export default Login ``` 让我们快速理解下新组建的结构,它有两个主要的 state 一个 state 是为了用户已经拥有账户,只需要直接登录,在这个 state,组件组件仅仅 render 两个 input 框,给用户提供 email 和 password。注意在这种情况下 state.login 的值是 true 第二个 state 是用户还没有创建账户,因此需要 sign up,这里你需要 render 第三个 input 框,用户可以输入name。在这种情况下 state.login 的值是 false _confirm 方法将被是用来实现 mutation,我们需要使用 mutation 向后台发送 login 信息 下一步,你也需要提供 constants.js 文件,我们用来定义 keys 来认证,我们在浏览器 localStorage 存储的 JWT TOKEN 在 src/ 下创建 constants.js ``` export const GC_USER_ID = 'graphcool-user-id' export const GC_AUTH_TOKEN = 'graphcool-auth-token' ``` 下一步打开 App.js 更新 route ``` import React, { Component } from 'react'; import { Switch, Route } from 'react-router-dom' import LinkList from './LinkList' import CreateLink from './CreateLink' import Header from './Header' import Login from './Login' class App extends Component { render() { return (
); } } export default App; ``` 更新 Header.js, 把 Link 加入代码,用户可以直接在导航栏跳转 ``` import React, { Component } from 'react' import { Link } from 'react-router-dom' import { withRouter } from 'react-router' import { GC_USER_ID, GC_AUTH_TOKEN } from '../constants' class Header extends Component { render() { const userId = localStorage.getItem(GC_USER_ID) return (
Hacker News
new {userId &&
|
submit
}
{userId ?
{ localStorage.removeItem(GC_USER_ID) localStorage.removeItem(GC_AUTH_TOKEN) this.props.history.push(`/new/1`) }}>logout
: login }
) } } export default withRouter(Header) ``` 在 Login.js 中实现认证功能之前,需要准备 Graphcool 项目,并在服务端启用身份验证 ### 启用 Email-和-Password 验证 && 更新 Schema 在终端文件 project.graphcool 的目录下,输入下面的命令 ``` graphcool console ``` 将会打开 Graphcool Console,一套 Web UI,允许你配置你的 Graphcool poject 选择左侧的 integrations 点击 email-password-auth-integration 完成之后,在终端 project.graphcool 的目录下,输入下面的命令 ``` graphqlcool pull ``` 选择yes,然后在 Graphcool Console WebUI中修改更新 User 和 Link type ``` type Link implements Node { url: String! description: String! createdAt: DateTime! id: ID! @isUnique updatedAt: DateTime! postedBy: User @relation(name: "UsersLinks") } type User implements Node { createdAt: DateTime! email: String @isUnique id: ID! @isUnique password: String updatedAt: DateTime! name: String! links: [Link!]! @relation(name: "UserLinks") } ``` 在 Link 中增加 ``` postedBy: User @relation(name: "UsersLinks") ``` 在 User 中增加 ``` name: String! links: [Link!]! @relation(name: "UserLinks") ``` 保存并在终端运行 ``` graphcool status ``` 查看状态,如果是在WebUI上修改的,应该 graphcool pull 同步到本地 如果是在project file project.graphcool 修改的,应该 graphcool push 同步到服务器 ### 实现 Login Mutation createUser 和 signinUser 是两个常规的 GraphQL mutations 打开 Login.js 在文件底部加上这两个 mutation 的定义,同时替换 export defautl ``` [ ... ] import { gql, graphql, compose } from 'react-apollo' [ ... ] const CREATE_USER_MUTATION = gql` mutation CreateUserMutation($name: String!, $email: String!, $password: String!) { createUser( name: $name, authProvider: { email:{ email: $email, password: $password } } ){ id } signinUser(email:{ email: $email, password: $password }){ token user { id } } } ` const SIGNIN_USER_MUTATION = gql` mutation SigninUserMutation($email: String!, $password: String!) { signinUser(email:{ email: $email, password: $password }) { token user{ id } } } ` export default compose( graphql(CREATE_USER_MUTATION, { name: 'createUserMutation' }), graphql(SIGNIN_USER_MUTATION, { name: 'signinUserMutation' }), )(Login) ``` react-apollo 导出一个 compose 函数,用于减少书写代码的量 在 Login.js 中实现 _confirm 的代码: ``` _confirm = async () => { const { name, email, password } = this.state if (this.state.login) { const result = await this.props.signinUserMutation({ variables: { email, password } }) const id = result.data.signinUser.user.id const token = result.data.signinUser.token this._saveUserData(id, token) }else{ const result = await this.props.createUserMutation({ variables: { name, email, password } }) const id = result.data.signinUser.user.id const token = result.data.signinUser.token this._saveUserData(id, token) } this.props.history.push(`/`) } ``` 代码非常的直接,如果用户只是想登录,调用 signinUserMutation 传入需要的参数 email 和 password 另一方面,如果用户想要创建用户 createUserMutation 还要另外传参 name。 之后保存 id 和 token 在 localStorage,并导航到 根路由 这时候就可以创建用户 完成之后自动跳转到根目录 ### 更新 createLink Mutation 重新定义 `CREATE_LINK_MUTATION`: ``` const CREATE_LINK_MUTATION = gql` mutation CreateLinkMutation($description: String!, $url: String!, $postedById: ID!) { createLink( description: $description, url: $url, postedById: $postedById ) { id createdAt url description postedBy{ id name } } } ` ``` 更新 _createLink 代码: ``` import { GC_USER_ID } from '../constants' [ ... ] _createLink = async () => { const postedById = localStorage.getItem(GC_USER_ID) if(!postedById) { console.error('用户没有登录') return } const { description, url } = this.state await this.props.createLinkMutation({ variables: { description, url, postedById } }) this.props.history.push('/') } [ ... ] ``` ### 配置 Apollo with Auth Token 在index.js中书写 middleware 部分的代码 ``` import { GC_AUTH_TOKEN } from './constants' [ ... ] networkInterface.use([{ applyMiddleware(req, next){ if(!req.options.headers){ req.options.headers = {} } const token = localStorage.getItem(GC_AUTH_TOKEN) req.options.headers.authorization = token ? `Bearer ${ token }` : null next() } }]) ``` 到这里,就可以注册、登录、登录之后的创建新的链接 ## More Mutations and Updating the Store 将实现的下一个功能是 `投票功能`。登录的用户可以投票,最高支持的链接将会在单独的路由地址显示 ### 准备 React Component 组件 打开 Link.js 更新 render 代码: ``` render(){ const userId = localStorage.getItem(GC_USER_ID) return(
{this.props.index + 1}. {userId &&
this._voteForLink()}>👍
}
{this.props.link.description} ({this.props.link.url})
{this.props.link.votes.length} votes | by {this.props.link.postedBy ? this.props.link.postedBy.name : 'Unknown'} {timeDifferenceForDate(this.props.link.createdAt)}
) } ``` 这样就准备好了 Link 组件来呈现每个链接的投票数以及发布它的名称。 注意:你使用了 `timeDifferenceForDate` 函数,该函数将时间转换成用户友好的字符串,例如‘3小时前’ 在 src下创建 utils.js 来完成函数 `timeDifferenceForDate` 的实现 ``` function timeDifference(current, previous) { const milliSecondsPerMinute = 60 * 1000 const milliSecondsPerHour = milliSecondsPerMinute * 60 const milliSecondsPerDay = milliSecondsPerHour * 24 const milliSecondsPerMonth = milliSecondsPerDay * 30 const milliSecondsPerYear = milliSecondsPerDay * 365 const elapsed = current - previous if(elapsed < milliSecondsPerMinute / 3) { return '刚刚' } if(elapsed < milliSecondsPerMinute) { return '一分钟前' } else if(elapsed < milliSecondsPerHour) { return Math.round(elapsed / milliSecondsPerMinute) + '分钟前' } else if(elapsed < milliSecondsPerDay) { return Math.round(elapsed / milliSecondsPerHour) + '小时前' } else if(elapsed < milliSecondsPerMonth) { return Math.round(elapsed / milliSecondsPerDay) + '天前' } else if(elapsed < milliSecondsPerYear) { return Math.round(elapsed / milliSecondsPerMonth) + '月前' } else { return Math.round(elapsed / milliSecondsPerYear) + '年前' } } export function timeDifferenceForDate(date){ const now = new Date().getTime() const updated = new Date(date).getTime() return timeDifference(now, updated) } ``` 返回 Link.js 在文件头引入需要的模块文件 ``` import { GC_USER_ID } from '../constants' import { timeDifferenceForDate } from '../utils' ``` 最后,每个 Link 元素还将在列表中显示其位置,因此要从 LinkList 组件传递一个索引 ``` return(
{linksToRender.map((link, index) => ( ))}
)Ï ``` 现在还不能运行,因为 votes 还没有加载到查询语句中,下面来修改代码: ### 更新 Schema 上一次是在浏览器的 Web UI 中修改,然后 graphcool pull 下来到本地,这次修改 project.graphcool 文件 ``` vim project.graphcool ``` ``` type Link implements Node { url: String! description: String! createdAt: DateTime! id: ID! @isUnique updatedAt: DateTime! postedBy: User @relation(name: "UsersLinks") votes: [Vote!]! @relation(name: "VotesOnLink") } type File implements Node { contentType: String! createdAt: DateTime! id: ID! @isUnique name: String! secret: String! @isUnique size: Int! updatedAt: DateTime! url: String! @isUnique } type User implements Node { createdAt: DateTime! email: String @isUnique id: ID! @isUnique password: String updatedAt: DateTime! name: String! links: [Link!]! @relation(name: "UsersLinks") votes: [Vote!]! @relation(name: "UsersVotes") } type Vote { user: User! @relation(name: "UsersVotes") link: Link! @relation(name: "VotesOnLink") } ``` 然后在终端运行 ``` graphcool push ``` 现在可以修改 LinkList.js 中的 `ALL_LINKS_QUERY` 语句 ``` const ALL_LINKS_QUERY = gql` query AllLinksQuery { allLinks { id createdAt url description postedBy { id name } votes { id user { id } } } } ` ``` ### 调用 Mutation 打开 Link.js 文件在底部添加 mutation 定义,并且替换 export Link 语句 ``` const CREATE_VOTE_MUTATION = gql` mutation CreateVoteMutation($userId: ID!, $linkId: ID!) { createVote(userId: $userId, linkId: $linkId){ id link { votes{ id user{ id } } } user { id } } } ` export default graphql(CREATE_VOTE_MUTATION, { name: 'createVoteMutation' })(Link) ``` 现在我们来实现 _voteForLink 功能: ``` _voteForLink = async() => { const userId = localStorage.getItem(GC_USER_ID) const voterIds = this.props.link.votes.map(vote => vote.user.id) if(voterIds.includes(userId)) { console.log(`用户 (${userId}) 已经给这个链接投过票了`) return } const linkId = this.props.link.id await this.props.createVoteMutation({ variables:{ userId, linkId } }) } ``` 该方法的第一步是检查当前用户是否已经为该链接投票,如果是这种情况,可以提前返回,而不是执行 mutation 现在重新运行 yarn start 就可以有点赞的功能,刷新之后,就可以看见投票个数的增加 但是仍有缺陷,投票之后不能自动刷新,所以用户可以提交无限的投票,直到页面刷新 但是至少知道 mutation 是有效的,下面,将会解决问题,并确保每次 mutation 后缓冲都会被刷新 ### 更新缓存 Apollo 可以手动控缓存内容,这是非常方便的,特别是在执行 mutation 之后 修改 Link.js 文件下的 _voteForLink 函数 ``` const linkId = this.props.link.id await this.props.createVoteMutation({ variables:{ userId, linkId }, update: (store, {data: { createVote }}) => { this.props.updateStroeAfterVote(store, createVote, linkId) } }) ``` 在 Link 的父组件 LinkList 实现 update 函数功能 ``` [ ... ] {linksToRender.map((link, index) => ( ))} [ ... ] _updateCacheAfterVote = (store, createVote, linkId) => { const data = store.readQuery({ query: ALL_LINKS_QUERY }) const votedLink = data.allLinks.find(link => link.id === linkId) votedLink.votes = createVote.link.votes store.writeQuery({ query: ALL_LINKS_QUERY, data }) } [ ... ] ``` CreateLink.js 更新 createLinkMutation 在 _createLink 里面 ``` [ ... ] import { ALL_LINKS_QUERY } from './LinkList' [ ... ] await this.props.createLinkMutation({ variables: { description, url, postedById }, update: (store, {data: { createLink }}) => { const data = store.readQuery({ query: ALL_LINKS_QUERY }) data.allLinks.splice(0, 0, createLink) store.writeQuery({ query: ALL_LINKS_QUERY, data }) } }) ``` 在 LinkList.js 中给 ALL_LINKS_QUERY 添加 export 关键字 ``` export const ALL_LINKS_QUERY = gql` query AllLinksQuery { allLinks { id createdAt url description postedBy { id name } votes { id user { id } } } } ` ``` 现在 LinkList.js 文件代码如下: ``` import React, { Component } from 'react' import Link from './Link' import { graphql, gql } from 'react-apollo' class LinkList extends Component { render(){ if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { return
Loading
} if(this.props.allLinksQuery && this.props.allLinksQuery.error){ return
Error
} const linksToRender = this.props.allLinksQuery.allLinks return(
{linksToRender.map((link, index) => ( ))}
) } _updateCacheAfterVote = (store, createVote, linkId) => { const data = store.readQuery({ query: ALL_LINKS_QUERY }) const votedLink = data.allLinks.find(link => link.id === linkId) votedLink.votes = createVote.link.votes store.writeQuery({ query: ALL_LINKS_QUERY, data }) } } export const ALL_LINKS_QUERY = gql` query AllLinksQuery { allLinks { id createdAt url description postedBy { id name } votes { id user { id } } } } ` export default graphql(ALL_LINKS_QUERY, { name: 'allLinksQuery' })(LinkList) ```