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》组件完成。
浙公网安备 33010602011771号