react apollo_React with Apollo中的实时GraphQL UI更新。

react apollo

介绍 ( Introduction )

This is the fourth and final part of a 4-part series of articles explaining the implementation of GraphQL on an Express Server and similarly in a React client using Apollo. In this article we look at how to maintain realtime UI updates after implementing different GraphQL updates that result in either creation, update or deletion of data on the server side. For part 1, 2 and 3, refer to the links:

这是由4部分组成的系列文章的第4部分,也是最后一部分,这些文章说明了在Express Server上以及类似地在使用Apollo的React客户端中GraphQL的实现。 在本文中,我们研究了在实现不同的GraphQL更新后如何维护实时UI更新,这些更新导致服务器端数据的创建,更新或删除。 对于第1、2和3部分,请参考链接:

突变后更新客户端状态 ( Updating the client state after a mutation )

In the second part of this series of articles, we had to reload the page to see the new channel that had been created; there was no automatic re-rendering of the UI. This is because Apollo has no way of knowing that the mutation we made has anything to do with the channels query that renders our list. Only the server knows that, but it has no way of notifying our client. To update the client after a mutation, we can opt for on of the following three ways:

在本系列文章的第二部分中,我们必须重新加载页面以查看已创建的新频道。 没有自动重新呈现用户界面。 这是因为Apollo无法知道我们所做的突变与呈现列表的渠道查询有任何关系。 只有服务器知道这一点,但是它无法通知我们的客户。 要在发生突变后更新客户端,我们可以选择以下三种方式之一:

  • Refetch queries that could be affected by the mutations.

    重新获取可能受突变影响的查询。
  • Manually updating the client state based on the mutation result.

    根据变异结果手动更新客户端状态。
  • Using GraphQL subscriptions.

    使用GraphQL订阅。

重读 ( Refetch )

Refetches are the simplest way to force a portion of your cache to reflect the information available to your server. Essentially, a refetch forces a query to immediately hit the server again, bypassing the cache. The result of this query, just like all other query results, updates the information available in the cache, which updates all of the query results on the page. To tell Apollo Client that we want to refetch the channels after our mutation completes, we pass it via the refetchQueries option on the call to mutate. We will reuse the channelsListQuery by exporting it from ChannelsList.jsx and import it in CreateChannel.jsx.

重新引用是强制缓存的一部分反映服务器可用信息的最简单方法。 本质上,重新引导会强制查询绕过缓存立即再次命中服务器。 与所有其他查询结果一样,此查询的结果将更新缓存中可用的信息,该信息将更新页面上的所有查询结果。 为了告诉Apollo客户我们想要在完成变异后重新获取频道,我们通过mutate调用中的refetchQueries选项传递它。 通过将CreateChannel.jsxChannelsList.jsx导出并将其导入CreateChannel.jsx我们将重用channelsListQuery

//  src/app/components/ChannelList/ChannelList.jsx
export const channelsListQuery = gql`
   query ChannelsListQuery {
     channels {
       id
       name
     }
   }
 `;

We then pass the query via refetchQueries option in mutate.

然后,我们通过mutate refetchQueries选项传递查询。

...
// src/app/components/CreateChannel/CreateChannel.jsx
...
import { channelsListQuery } from '../ChannelList/ChannelList';
const CreateChannel = ({mutate}) => {
    const handleKeyUp = (evt) => {
      if (evt.keyCode === 13) {
        evt.persist();
        mutate({
          variables: { name: evt.target.value },
          refetchQueries: [ { query: channelsListQuery }]
        })
        .then( res => {
          evt.target.value = '';
        });
      }
  };
  ...

When we create a new channel right now, the UI shows it without reloading the page.

现在,当我们创建一个新频道时,UI会在不重新加载页面的情况下显示它。

This is a good step but the downside is that this method will not show the updates if the request was made by another client. You won’t find out until you do a mutation of your own and refetch the list from the server or reload the page which we are trying to avoid. To make the application more realtime, Apollo provides a feature that allows us to poll queries within an interval. To enable this feature, we pass the pollInterval option with our channelsListQuery.

这是一个好步骤,但缺点是,如果请求是由另一个客户端发出的,则此方法将不会显示更新。 您只有自己做一个变异并从服务器中重新获取列表或重新加载我们试图避免的页面,您才能找到答案。 为了使应用程序更加实时,Apollo提供了一项功能,使我们可以在一定间隔内轮询查询。 要启用此功能,我们将pollInterval选项与channelsListQuery一起传递。

//  src/app/components/ChannelList/ChannelList.jsx
...
const ChannelsListWithData = graphql(channelsListQuery, { options: { pollInterval: 5000 }})(ChannelsList);
...

Creating a new channel from another client now updates our list of channels after 5 seconds, not the best way of doing it but a step in making the application realtime.

现在,从另一个客户端创建新频道会在5秒后更新我们的频道列表,这不是最好的方法,而是使应用程序实时化的一步。

更新商店 ( Updating the Store )

To update the store based on a client action, Apollo provides a set of tools to perform imperative store updates: readQuery,writeQuery, readFragment and writeFragment. The client normalizes all of your data so that if any data you previously fetched from your GraphQL server is updated in a later data fetch from your server then your data will be updated with the latest truth from your server. Apollo exposes these functions via the update property in mutate. Using update gives you full control over the cache, allowing you to make changes to your data model in response to a mutation in any way you like.

为了根据客户端操作更新商店,Apollo提供了一组用于执行命令性商店更新的工具: readQuery,writeQuery, readFragment and writeFragment 。 客户端对所有数据进行规范化,以便如果先前从GraphQL服务器获取的任何数据在以后从服务器获取的数据中得到更新,则将使用服务器的最新真相来更新您的数据。 Apollo通过mutateupdate属性公开这些功能。 使用更新可以完全控制高速缓存,从而允许您以自己喜欢的任何方式对数据模型进行更改以响应突变。

To implement it, we replace the refetchQueries option with the following call to update.

为了实现它,我们refetchQueries替换refetchQueries选项以进行更新。

// src/app/components/CreateChannel/CreateChannel.jsx
...
mutate({
          variables: { name: evt.target.value },
          update: (store, { data: { addChannel } }) => {
            // Read the data from the cache for this query.
            const data = store.readQuery({query: channelsListQuery });
            // Add our channel from the mutation to the end.
            data.channels.push(addChannel);
            // Write the data back to the cache.
            store.writeQuery({ query: channelsListQuery, data });
          }
        })
...

As soon as the mutation completes, we read the current result for the channelsListQuery out of the store, append the new channel to it, and tell Apollo Client to write it back to the store. Our ChannelsList component will automatically get updated with the new data. There's still a delay which is a result of the network request. Apollo provides, optimistic UI, a feature that allows your client code to easily predict the result of a successful mutation even before the server responds with the result providing a fater user experience. This is possible with Apollo if the client can predict an optimistic response for the mutation.

突变完成后,我们立即从商店中读取channelsListQuery的当前结果,将新的渠道添加到它中,并告诉Apollo Client将其写回到商店中。 我们的ChannelsList组件将自动使用新数据进行更新。 网络请求仍然存在延迟。 Apollo提供了乐观的UI功能,即使在服务器响应之前,客户端代码也可以轻松地预测成功突变的结果,从而为用户提供更好的用户体验。 如果客户可以预测对突变的乐观React ,则使用Apollo可以做到这一点。

乐观的用户界面 (Optimistic UI)

To enable optimistic UI, we add the optimisticResponse property to mutate call. This “fake result” will be used to update active queries immediately, in the same way that the server’s mutation response would have done. The optimistic patches are stored in a separate place in the cache, so once the actual mutation returns, the relevant optimistic update is automatically thrown away and replaced with the real result. Since the server generates the channel Id's which we use as keys in rendering the list, we will generate random keys to use before the server response and retain the channel name since we know what we expect. Finally, we also have to specify the __typename to make sure Apollo Client knows what kind of object it is.

要启用开放式用户界面,我们添加了optimisticResponse属性以使调用mutate 。 该“伪造结果”将用于立即更新活动查询,就像服务器的变异响应一样。 乐观补丁存储在缓存中的单独位置,因此,一旦实际突变返回,相关的乐观更新将自动丢弃,并替换为实际结果。 由于服务器生成通道ID(我们将其用作呈现列表的键),因此我们将生成随机键以在服务器响应之前使用,并保留通道名称,因为我们知道我们的期望。 最后,我们还必须指定__typename ,以确保Apollo Client知道它是哪种对象。

// src/app/components/CreateChannel/CreateChannel.jsx
...
       variables: { name: evt.target.value },
          optimisticResponse: {
             addChannel: {
               name: evt.target.value,
               id: Math.round(Math.random() * -1000000), // Generate a random ID to avoid conflicts.
               __typename: 'Channel',
             },
          },
          update:
...

After this addition, creating a new channel instantly shows the channel on the list with no delays. These updates have not been confirmed by the server yet. For purposes of development and knowledge, we can choose to show the user which items are pending confirmation. As you can notice, we used negative values for the random Id's; this enables us to differentiate already confirmed channels which have positive Id's. We could add a CSS class to highlight channels awaiting confirmation.

添加之后,创建新频道将立即在列表中显示该频道,而不会出现延迟。 服务器尚未确认这些更新。 出于发展和知识的目的,我们可以选择向用户显示哪些项目正在等待确认。 如您所见,我们为随机ID使用了负值; 这使我们能够区分具有正ID的已确认渠道。 我们可以添加CSS类来突出显示等待确认的频道。

...
 return <ul className="list-group">
     { channels.map( ch => <li className={ "list-group-item " + (ch.id < 0 ? "pending" : "")} key={ch.id}>{ch.name}</li> ) }
   </ul>;
...

For a brief moment, with the addition of pending class, the new item appears red before turning to black. We can make this behaviour more defined so as to observe it for development pusrposes by simulating a latency in the network. In the following snippet, we fake a 3-second network delay that allows us to see the UI changes clearly. We use applyMiddleware which allows us to modify the request made over the netWorkInterface.

短暂的一会儿,添加了pending课程,新项目在变成黑色之前显示为红色。 通过模拟网络中的等待时间,我们可以使此行为更明确,以便观察其是否适合开发。 在以下代码段中,我们伪造了3秒的网络延迟,该延迟使我们可以清楚地看到UI更改。 我们使用applyMiddleware ,它允许我们修改通过netWorkInterface发出的请求。

// src/app/app.jsx
...
networkInterface.use([{
  applyMiddleware(req, next) {
    setTimeout(next, 3000);
  },
}]);
...

Initially.

原来。

After 3 seconds.

3秒后。

订阅内容 ( Subscriptions )

Use of GraphQL subscriptions is a way to push data from the server to the clients that choose to listen to real time messages from the server. Subscriptions are similar to queries in that they specify a set of fields to be delivered to the client, but instead of immediately returning a single answer, a result is sent every time a particular event happens on the server.

使用GraphQL订阅是一种将数据从服务器推送到选择侦听服务器实时消息的客户端的方法。 订阅与查询相似,因为订阅指定了要传递给客户端的一组字段,但是每次都不会在服务器上发生特定事件,而是立即发送结果,而不是立即返回单个答案。

A common use case for subscriptions is notifying the client side about particular events, for example the creation of a new object, updated fields and so on. This section relies on server-side implementation of subscriptions and I would advise going through it for maximum gain. The first step to adding subscriptions to our client is to set up the WebSocket connection that the client and server will communicate over. Forming and maintaining the WebSocket connection will be the job of the Apollo network interface, defined in client/src/App.js. To add WebSocket support to our existing interface, we will construct a GraphQL Subscription client and merge it with our existing network interface to create a new interface that performs normal GraphQL queries over HTTP and subscription queries over WebSockets. The most popular transport for GraphQL subscriptions today is subscriptions-transport-ws which we can install using:

订阅的一个常见用例是向客户端通知特定事件,例如,创建新对象,更新字段等。 本节依赖于订阅的服务器端实现,我建议您仔细阅读一下以获取最大收益。 向我们的客户端添加订阅的第一步是设置客户端与服务器之间进行通信的WebSocket连接。 形成并维护WebSocket连接将是client/src/App.js定义的Apollo网络接口的工作。 为了向现有接口添加WebSocket支持,我们将构建一个GraphQL订阅客户端,并将其与我们现有的网络接口合并,以创建一个新接口,该接口可以通过HTTP执行常规的GraphQL查询,并通过WebSockets执行订阅查询。 今天,对于GraphQL订阅而言,最受欢迎的传输方式是subscriptions-transport-ws ,我们可以使用以下方式安装:

# Note the package version. Future updates will change the implementation.
yarn add subscriptions-transport-ws@0.8.2   # Alternatively npm i subscriptions-transport-ws@0.8.2

We also remove the following part used to demonstrate refetching of queries and polling to show the real power of real-time updates.

我们还将删除以下部分,该部分用于演示查询的重新提取和轮询,以显示实时更新的真正功能。

//src/app/components/CreateChannel/CreateChannel.jsx
...
 refetchQueries: [ { query: channelsListQuery }]  // Remove this part to disable query refetching feature.
...

We edit the following part to disable polling.

我们编辑以下部分以禁用轮询。

//src/app/components/ChannelList/ChannelList.jsx
...
 const ChannelsListWithData = graphql(channelsListQuery, { options: { pollInterval: 5000 }})(ChannelsList);
...

Edit the above to:

编辑以上内容以:

//src/app/components/ChannelList/ChannelList.jsx
...
 const ChannelsListWithData = graphql(channelsListQuery)(ChannelsList);
...

Trying to add a new channel now in our app does not reflect the changes until we refresh the page and that is what we expect.

现在尝试在我们的应用中添加新频道不会反映出更改,直到我们刷新页面,这就是我们所期望的。

Then, initialize a GraphQL subscriptions transport client and merge it with our existing network interface.

然后,初始化GraphQL订阅传输客户端并将其与我们现有的网络接口合并。

//src/app/app.jsx
...
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws';
...
const networkInterface = createNetworkInterface({
  uri: 'http://localhost:7900/graphql',
});

const wsClient = new SubscriptionClient(`ws://localhost:7900/subscriptions`, {
  reconnect: true
});

// Extend the network interface with the WebSocket
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
  networkInterface,
  wsClient
);

To enable subscriptions throughout our application, we use networkInterfaceWithSubscriptions as the Apollo Client’s network interface:

为了在整个应用程序中启用订阅,我们将networkInterfaceWithSubscriptions用作Apollo客户端的网络接口:

const client = new ApolloClient({
   networkInterface: networkInterfaceWithSubscriptions // Use the subscriptions interface as the client interface.
});

When we inspect our network in the browser, it shows that we are subscribed to a channel.

当我们在浏览器中检查网络时,它表明我们已订阅频道。

Our queries and mutations will now go over HTTP as normal, but subscriptions will be done over the WebSocket transport.

现在,我们的查询和变异将照常通过HTTP进行,但是订阅将通过WebSocket传输进行。

听信息 (Listening for Messages)

Now that we can use GraphQL Subscriptions in our client, the next step is to use subscriptions to detect the creation of new channels. Our goal here is to use subscriptions to update our React views to see new channels as they are added. We begin by writing the subscription.

现在我们可以在客户端中使用GraphQL订阅了,下一步是使用订阅来检测新渠道的创建。 我们的目标是使用订阅来更新我们的React视图,以便在添加新频道时看到它们。 我们首先编写订阅。

// src/app/components/ChannelList/ChannelList.jsx
...
// Create a subscription
const channelSubscription = gql`
    subscription Channels {
     channelAdded {
       id
       name
     }
    }
`
...

With GraphQL subscriptions your client will be alerted on push from the server and we can use the data sent along with the notification and merge it directly into the store (existing queries are automatically notified). We can accomplish this using subscribeToMore function available on every query result in react-apollo. The update function gets called every time the subscription returns.

通过GraphQL订阅,您的客户端将在服务器推送时收到警报,我们可以使用与通知一起发送的数据并将其直接合并到存储中(现有查询将自动得到通知)。 我们可以做到这一点使用subscribeToMore每个查询结果中提供的功能react-apollo 。 每次订阅返回时,都会调用update函数。

Before we start, we have to refactor our client/src/components/ChannelDetails.js component to be a full ES6 class component instead of just a function, so that we can use the React lifecycle events to set up the subscription.

在开始之前,我们必须将client/src/components/ChannelDetails.js组件重构为一个完整的ES6类组件,而不仅仅是一个函数,以便我们可以使用React生命周期事件来设置订阅。

In componentWillMount we add a functionaliity that will subscribe using subscribeToMore and update the query’s store with the new data using updateQuery. The updateQuery callback must return an object of the same shape as the initial query data, otherwise the new data won’t be merged.

componentWillMount我们添加了一个功能,该功能将使用updateQuery进行subscribeToMore并使用updateQuery使用新数据更新查询的存储。 updateQuery回调必须返回与初始查询数据形状相同的对象,否则新数据将不会合并。

//src/app/components/ChannelList/ChannelList.jsx
componentWillMount() {
   this.props.data.subscribeToMore({
     document: channelSubscription,   // Use the subscription
     updateQuery: (prev, {subscriptionData}) => {
       if (!subscriptionData.data) {
         return prev;
       }

       const newChannel = subscriptionData.data.channelAdded;
       // Add check to prevent double adding of channels.
      if (!prev.channels.find((channel) => channel.name === newChannel.name)) {
         let updatedChannels = Object.assign({}, prev, { channels : [...prev.channels, newChannel ] });
         return updatedChannels;
       } else {
         return prev;
       }
     }
   });
 }
 render() {
 ...

测试中 ( Testing )

We can now test our application by opening two browser windows at http://localhost:7800/ side by side. Adding a channel in either window immediately shows the new channel in the adjacent window.

现在,我们可以并排打开两个浏览器窗口,分别位于http:// localhost:7800 /,以测试我们的应用程序。 在任一窗口中添加频道会立即在相邻窗口中显示新频道。

结论 ( Conclusion )

We have explored different ways in which we can update our client to avoid page refreshes and large data requests and we can conclude that using subscriptions is the fastest and most efficient way of achieving this.

我们探索了各种更新客户端的方法,以避免页面刷新和大数据请求,并且可以得出结论,使用订阅是实现此目的的最快,最有效的方法。

翻译自: https://scotch.io/tutorials/realtime-graphql-ui-updates-in-react-with-apollo

react apollo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值