10
0

More than 3 years have passed since last update.

Apollo Clientでwebsocket通信してみよう

Last updated at Posted at 2020-12-11

はじめに

この記事は2020年のRevCommアドベントカレンダー12日目の記事です。11日目は @hiratake55 さんの RevCommにおけるリモート開発を支える取り組みを紹介します でした。

こんにちは、フロントエンドエンジニアの山田です。
普段の業務ではReactを使ってアプリケーション開発をしています。

本日のお題は「Apollo Clientでwebsocket通信してみよう」です。Day 5でApollo Server構築の記事がありますので、こちら使ってフロントの作成を試みます。

今回のアプリケーションでできること

  • データの取得
  • データの作成
  • データのサブスクライブ

ezgif-2-86200b7e4e63.gif

目次

  1. 準備
  2. Apollo Clientの作成
  3. Queryを定義
  4. 描画
  5. まとめ

準備

さて、今回はReactを使っていきます。

npx create-react-app apollo-client

必要なパッケージをインストール

yarn add @apollo/client subscriptions-transport-ws

Apollo Clientの作成

GraphQLとのやりとりをすべてWebSocketで行うので、エンドポイントをws://localhost:4000/graphqlとします。
SubscriptionsClientインスタンスをネットワークインターフェイスとして使用しApolloClientインスタンスを作成します。

/src/apollo/client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from '@apollo/client/link/ws';

const cache = new InMemoryCache();
const wsClient = new SubscriptionClient('ws://localhost:4000/graphql', {
  reconnect: true,
});

const wsLink = new WebSocketLink(wsClient);

export const client = new ApolloClient({
  cache,
  link: wsLink,
});

ApolloProviderをWrapしていきます。

/src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from '@apollo/client';
import App from './App';
import { client } from './apollo/client';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Queryを定義

バックエンドをもとにgraphqlのqueryを作成します。

/src/apollo/query/book.js
import { gql } from '@apollo/client';

export const GET_BOOKS = gql`
  query books {
    books {
      title
      author
    }
  }
`;

export const ADD_BOOKS = gql`
  mutation AddBook($title: String!, $author: String!) {
    addBook(title: $title, author: $author) {
      title
      author
    }
  }
`;

export const SUBSCRIBE_BOOKS = gql`
  subscription {
    bookAdded {
      title
      author
    }
  }
`;

描画

最後の仕上げです。
ここで大事なのはsubscribeToMoreを使ってデータの監視を行うことです。

  • document:はsubscriptionのクエリを設定します。
  • updateQueryはクエリの現在キャッシュされている結果(prev)をGraphQLサーバーによってプッシュされたsubscriptionDataと組み合わせる方法をApolloクライアントに指示する関数です。この関数の戻り値は、クエリの現在のキャッシュ結果を完全に置き換えます。

今回の例ではuseMutationで作成されたデータをsubscribeToMoreで監視し、prevのデータに追加することでリストの更新が行われる仕組みとなっています。

/src/App.jsx
import React, { useEffect, useState } from 'react';
import { useMutation, useLazyQuery } from '@apollo/client';

import { GET_BOOKS, ADD_BOOKS, SUBSCRIBE_BOOKS } from './apollo/query/book';

const App = () => {
  const [author, setAuthor] = useState('');
  const [title, setTitle] = useState('');
  const [getBooks, { data, called, subscribeToMore, refetch }] = useLazyQuery(GET_BOOKS);
  const [addBook] = useMutation(ADD_BOOKS);

  useEffect(() => {
    if (!called) {
      getBooks();
    }
  }, [called, getBooks]);

  useEffect(() => {
    if (refetch && called) {
      refetch();
    }
  }, [called, refetch]);

  useEffect(() => {
    if (subscribeToMore) {
      subscribeToMore({
        document: SUBSCRIBE_BOOKS,
        updateQuery: (prev, { subscriptionData }) => {
          if (!subscriptionData.data) return prev;
          const newBook = subscriptionData.data.bookAdded;
          const updatedBooks = [...prev.books, newBook];
          return { books: updatedBooks }
        }
      });
    }
  }, [subscribeToMore]);

  const handleAuthorChange = (e) => {
    setAuthor(e.target.value);
  };

  const handleTitleChange = (e) => {
    setTitle(e.target.value);
  };

  const handleSubmit = () => {
    addBook({ variables: { title, author }});
    setAuthor('');
    setTitle('');
  };

  return (
    <div style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "flex-end" }}>
        <div>
          <p style={{ margin: 0 }}>タイトル</p>
          <input type="text" name="title" value={title} onChange={handleTitleChange} />
        </div>
        <div style={{ marginLeft: 20 }}>
          <p style={{ margin: 0 }}>著者名</p>
          <input type="text" name="author" value={author} onChange={handleAuthorChange} />
        </div>
        <div style={{ marginLeft: 10 }}>
          <button style={{ cursor: "pointer" }} type="submit" onClick={handleSubmit}>登録</button>
        </div>
      </div>
      {data && (
        <ul>
          {data.books.map((book, i) => {
            return (
              <li key={i}>{`${book.title} (${book.author})`}</li>
            )
          })}
        </ul>
      )}
    </div>
  );
}

export default App;

まとめ

いかがでしたでしょうか?
今回はApollo Clientでwebsocket通信をしてみました。

Revcommで一緒に働いてくれる方を絶賛募集しています。
募集中のポジションは、株式会社RevComm 採用情報 をご覧ください。

次回は@shintaromatsudoのさんの記事になります。

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0