apollo+react实战项目 stepbystep(八)
使用subscription来自动更新页面
1、什么是graphql subscription
subscription就是当客户端某个事件发生时,服务器端自动发送数据到客户端。subscription通常是由websockets协议实现的。与HTTP不同,WebSocket提供全双工通信,可以在客户端和服务器之间进行双向持续对话。使用subscription意味着我们不再像之前的请求-响应循环那样,而是要在客户端与服务器端建立了稳定的链接,服务器端订阅了一个客户端的事件,一旦客户端事件发生,服务器端便通过已经建立的链接发送数据。
何时使用subscription?
在大多数情况下,间歇性轮询(pollInterval)或手动重新获取(update)实际上是保持客户端最新的最佳方式。那么订阅什么时候才是最佳选择?在以下情况下,订阅特别有用:
(1)初始状态很大,但增量变化很小。可以使用查询获取起始状态,然后通过订阅更新。
(2)在特定条件下,你可能关心低延迟更新。例如在聊天情况下,用户希望尽快收到新消息。
2、使用apollo 进行subscription
(1)配置apolloClient使用websocket连接进行subscription操作。
使用apolloClient进行订阅前首先要告诉apolloClient订阅的服务器接口端点是什么。我们使用一个名为WebSocketLink的中间件来自apollo-link-ws包来创建websockets链接。
首先要导入apollo-link-ws
yarn add apollo-link-ws
注意:要使apollo-link-w工作,您还需要安装subscriptions-transport-ws
yarn add subscriptions-transport-ws
在index.js头部引入相关的包:
import { split } from 'apollo-link' import { WebSocketLink } from 'apollo-link-ws' import { getMainDefinition } from 'apollo-utilities'
webSocketLink用来创建一个ws Link
const wsLink = new WebSocketLink({ uri: `ws://localhost:4000`, options: { reconnect: true, connectionParams: { authToken: localStorage.getItem(AUTH_TOKEN), } } })
connectionParams可以自定义一个对象,服务器可以在设置任何订阅之前验证该连接。这里我们使用ws协议代替了http协议来指定端口。这样我们使用WebSocketLink创建了一个wsLink。
split是一个link路由选择器,接受三个参数,第一个是一个返回true或者false的函数,当为true时选择第二个link,否则选择第三个link。我们判断是否使用wsLink的基础是,客户端发送了query是subsctiption,如果是订阅请求则使用wsLink,
getMainDefiniion:用来解析请求,检查请求是否为subscription操作
const link = split( ({ query }) => { const { kind, operation } = getMainDefinition(query) return kind === 'OperationDefinition' && operation === 'subscription' }, wsLink, authLink.concat(httpLink) )
修改ApolloClient
const client = new ApolloClient({ link, cache: new InMemoryCache() })
(2)订阅创建新连接
使用Query组件渲染的subscribeToMore,类似于fetchMore可以接受当前组件的查询结果,和订阅的结果,可以将订阅前的查询结果和订阅结果连接在一起然后更新缓存。
LinkList.js
class LinkList extends Component { _updateCacheAfterVote = (store, createVote, linkId) => { const data = store.readQuery({ query: FEED_QUERY }) const votedLink = data.feed.links.find(link => link.id === linkId) votedLink.votes = createVote.link.votes store.writeQuery({ query: FEED_QUERY, data }) } _subscribeToNewLinks = async () => { // ... you'll implement this 🔜 } render() { return ( <Query query={FEED_QUERY}> {({ loading, error, data, subscribeToMore }) => { if (loading) return <div>Fetching</div> if (error) return <div>Error</div> this._subscribeToNewLinks(subscribeToMore) const linksToRender = data.feed.links return ( <div> {linksToRender.map((link, index) => ( <Link key={link.id} link={link} index={index} updateStoreAfterVote={this._updateCacheAfterVote} /> ))} </div> ) }} </Query> ) } }
_subscribeToNewLinks:
_subscribeToNewLinks = subscribeToMore => { subscribeToMore({ document: NEW_LINKS_SUBSCRIPTION, updateQuery: (prev, { subscriptionData }) => { if (!subscriptionData.data) return prev const newLink = subscriptionData.data.newLink.node return Object.assign({}, prev, { feed: { links: [newLink, ...prev.feed.links], count: prev.feed.links.length + 1, __typename: prev.feed.__typename } }) } }) }
subscribeToMore接受两个参数,一个是document,代表订阅查询本身,一个是updateQuery,在updateQuery中类似于update,可以根据结果更新存储。
新链接的订阅document
const NEW_LINKS_SUBSCRIPTION = gql` subscription { newLink { node { id url description createdAt postedBy { id name } votes { id user { id } } } } } `
(3)订阅投票
_subscribeToNewVotes = subscribeToMore => { subscribeToMore({ document: NEW_VOTES_SUBSCRIPTION }) }
订阅投票document
const NEW_VOTES_SUBSCRIPTION = gql` subscription { newVote { node { id link { id url description createdAt postedBy { id name } votes { id user { id } } } user { id } } } } `
添加订阅
this._subscribeToNewVotes(subscribeToMore)
这里我们的link及订阅了投票,也订阅了创建新链接。
除了使用subscribeToMore函数完成订阅,简单的订阅更新还可以使用《Subscription》组件完成。