Hướng dẫn build app chat realtime với React và GraphQL

App chat realtime là một trong những project cần được biết khi tiếp cận ngôn ngữ lập trình mới. Hôm nay mình sẽ hướng dẫn các bạn tạo một ứng dụng chat realtime đơn giản bằng React vs GraphQL.


1061

Tạp project React

create-react-app được các developer yêu thích cho việc tạo ứng dụng React. Tạo project react mới bằng lệnh sau

create-react-app graphql-chat

Điều này sẽ tạo một thư mục mới có tên là graphql-chat và tải xuống tất cả các tệp React cần thiết mà bạn cần để làm việc.

Cập nhật các dependencies trong package.json như sau:

"dependencies": {
  "apollo-client-preset": "^1.0.6",
  "apollo-link-ws": "^1.0.4",
  "graphql": "^0.12.3",
  "graphql-tag": "^2.6.1",
  "react": "^16.2.0",
  "react-apollo": "^2.0.4",
  "react-dom": "^16.2.0",
  "react-scripts": "1.0.17",
  "subscriptions-transport-ws": "^0.9.4"
},

Sau đó chạy lệnh cài đặt để tải xuống tất cả các tệp này:

npm install
# hoặc
yarn

Cập nhật thẻ head trong public / index.html để bao gồm các tệp CSS sau:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" />

Setup GraphQL

Thay vì máy chủ local, chúng ta có thể sử dụng một dịch vụ lưu trữ miễn phí hiện có để thiết lập GraphQL. Điều này không chỉ miễn phí mà còn rất đơn giản. Graphcool là hữu ích cho các dự án nhỏ, lớn và đang phát triển.

Cài đặt với câu lệnh sau

npm install -g graphcool-framework

Chuyển đến đến app React mà chúng ta vừa thiết lập thông qua terminal và khởi động máy chủ Graphcool:

graphcool-framework init server

Lệnh này sẽ tạo một thư mục trong app React có tên server. Lệnh server chứa các định nghĩa kiểu và schema GraphQL của bạn.

Đối với app chat, chúng ta chỉ cần tập trung vào file type.graphql được tạo trong thư mục server. Tạo schema GraphQl với cấu trúc sau:

type Chat @model {
  id: ID! @isUnique
  from: String!
  content: String!
  createdAt: DateTime!
}

Bạn cần triển khai schema này đến server Graphcool:

graphcool-framework deploy

Điều này trước tiên sẽ mở trình duyệt để bạn thiết lập tài khoản Graphcool và sau đó deploy. Bạn có thể mở phiên bản từ menu ở phía trên bên trái của bảng điều khiển Graphcool.

Trở lại terminal, sẽ in URL mà bạn cần để tương tác với server Graphcool.

Khi bạn deploy app, hãy mở playground để kiểm tra xem deploy có đúng loại của bạn không:

graphcool-framework playground

Chạy query sau:

query FETCH_CHATS{
  allChats{
    from,
    content
  }
}
mutation CREATE_CHAT {
  createChat(content: "test", from: "test") {
    content,
    from
  }
}

Playground sẽ hỏi bạn những lệnh nào bạn muốn chạy:

Tạo GraphQL cho React

Dự án React đã sẵn sàng và server GraphQL đã deploy thành công. Chúng ta cần liên kết chúng lại với tất cả các modules mà chúng ta đã cài đặt với file package.json.

Hãy bắt đầu với việc import chúng. Cập nhật file src / index.js để import các dependencies sau:

import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-client-preset'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'

Ngay dưới import, cấu hình link WebSocket. Bạn có thể làm điều này bằng cách sử dụng mô-đun WebSocketLink:

...
const wsLink = new WebSocketLink({
  uri: '[Subscriptions API URI]',
  options: {
    reconnect: true
  }
})

Hàm constructor lấy một đối tượng với các tùy chọn config. Uri là bắt buộc và phải giống với URI Subscriptions API mà bạn nhận được sau khi deploy. Chúng ta sẽ sử dụng tùy chọn kết nối lại để yêu cầu WebSocket thử kết nối lại sau khi thử thất bại.

Chúng ta không chỉ tạo kết nối WebSocket. Cũng cần thiết lập kết nối HTTP cho các hoạt động request-response. Thêm phần này ngay bên dưới thiết lập link WebSocket:

...
const httpLink = new HttpLink({ uri: '[SIMPLE API URI]' })

Tại thời điểm này, chúng ta có hai link. Chúng ta sẽ sử dụng phương thức split để báo cho server biết khi nào sử dụng link phù hợp:

...
const link = split(
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query)
    return kind === 'OperationDefinition' && operation === 'subscription'
  },
  wsLink,
  httpLink,
)

Phương thức split có ba đối số. Đầu tiên là một bài kiểm tra trả về một boolean. Nếu giá trị boolean là true, yêu cầu được chuyển tiếp đến đối số thứ hai (wsLink). Nếu sai, nó đã chuyển tiếp đến đối số thứ ba (httpLink).

Bây giờ chúng ta có thể tạo một máy khách Apollo với link được trả về:

...
const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
})

Bạn có thể thực hiện các request trực tiếp đến máy chủ của mình bằng các URL được cung cấp. Điều này phức tạp hơn một chút so với việc sử dụng thư viện wrapper cung cấp các chức năng để đơn giản hóa tương tác máy chủ. Apollo là một trong những thư viện như vậy.

Cung cấp cho client bằng cách sử dụng thành phần AppProvider:

...
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

Truy vấn Server GraphQL

Với sự ràng buộc giữa app React và GraphQL của chúng ta, đã đến lúc bắt đầu truy vấn cơ sở dữ liệu và hiển thị dữ liệu trong trình duyệt.

Cập nhật src / App.js để thiết lập truy vấn:

import React, { Component } from 'react';
// Import GraphQL helpers
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
// App component styles
import './App.css';
class App extends Component {
  state = {
    from: 'anonymous',
    content: ''
  };
  componentDidMount() {
    // Get username form prompt
    // when page loads
    const from = window.prompt('username');
    from && this.setState({ from });
  }
  render() {
    // Coming up next
  }
}
const ALL_CHATS_QUERY = gql`
  query AllChatsQuery {
    allChats {
      id
      createdAt
      from
      content
    }
  }
`;
export default graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' })(App);

Hãy chia nhỏ những gì đang diễn ra ở đây:

  • Đầu tiên chúng ta import graphql và gql. Thư viện này sẽ giúp thiết lập và phân tích truy vấn tương ứng.
  • Cuối định nghĩa class component, chúng ta tạo truy vấn. Nó trông giống hệt như những gì chúng ta đã làm trong playground nhưng được gói bằng phương thức gql sử dụng gắn tagging.
  • Sau đó, graphql HOC được sử dụng để hiển thị kết quả của truy vấn này với các props component.

Bây giờ bạn có thể render truy vấn trong DOM thông qua props:

// Chatbox UI component
import Chatbox from './components/Chatbox';
    class App extends Component {
      //...
      render() {
        const allChats = this.props.allChatsQuery.allChats || [];
        return (
          <div className="">
            <div className="container">
              <h2>Chats</h2>
              {allChats.map(message => (
                <Chatbox key={message.id} message={message} />
              ))}
            </div>
          </div>
        );
      }
    }
    // ...
    export default graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' })(App);

Phương thức render lặp lại qua từng kết quả truy vấn và in chúng ra màn hình bằng component Chatbox. Đây là những gì component trông giống sau:

import React from 'react';
import './Chatbox.css'
    const Chatbox = ({message}) => (
      <div className="chat-box">
        <div className="chat-message">
          <h5>{message.from}</h5>
          <p>
            {message.content}
          </p>
        </div>
      </div>
    );
    export default Chatbox;

Tạo Messages mới

Giống như cách chúng ta tạo một truy vấn:

import { graphql, compose } from 'react-apollo';
    // App component ...
    const CREATE_CHAT_MUTATION = gql`
      mutation CreateChatMutation($content: String!, $from: String!) {
        createChat(content: $content, from: $from) {
          id
          createdAt
          from
          content
        }
      }
    `;
    export default compose(
      graphql(ALL_CHATS_QUERY, { name: 'allChatsQuery' }),
      graphql(CREATE_CHAT_MUTATION, { name: 'createChatMutation' })
    )(App);

Trong phương thức render, chúng ta có thể có một input đầu vào thu thập các message:

render() {
   const allChats = this.props.allChatsQuery.allChats || [];
       return (
          <div className="">
            <div className="container">
              <h2>Chats</h2>
              {allChats.map(message => (
                <Chatbox key={message.id} message={message} />
              ))}
              {/* Message content input */}
              <input
                value={this.state.content}
                onChange={e => this.setState({ content: e.target.value })}
                type="text"
                placeholder="Start typing"
                onKeyPress={this._createChat}
              />
            </div>
          </div>
        );
      }
    }

Input kích hoạt một event tại mọi keyPress và sự kiện này gọi phương thức _createChat. Dưới đây là định nghĩa của phương thức này trong class App:

_createChat = async e => {
     if (e.key === 'Enter') {
       const { content, from } = this.state;
        await this.props.createChatMutation({
          variables: { content, from }
        });
        this.setState({ content: '' });
      }
    };

Chúng ta chỉ muốn chạy mutation khi nhấn phím enter. Lưu ý cách tạo createChatMutation cũng được hiển thị trên các props và biến được truyền vào dưới dạng object cho phương thức mutation.

Khi bạn gửi một message mới, không có gì xảy ra cho đến khi bạn reload.

Setup Subscriptions Realtime

Subscriptions trong GraphQL được sử dụng để listen những thay đổi của các clients được kết nối. Những clients này có thể hành động theo những thay đổi này cho phù hợp. Có thể bằng cách cập nhật giao diện người dùng với dữ liệu đã thay đổi hoặc thậm chí gửi thông báo push. Công nghệ cơ bản hỗ trợ subscriptions là WebSockets.

Thêm phương thức này vào class app để thiết lập subscription(đăng ký):

_subscribeToNewChats = () => {
      this.props.allChatsQuery.subscribeToMore({
          document: gql`
            subscription {
              Chat(filter: { mutation_in: [CREATED] }) {
                node {
                  id
                  from
                  content
                  createdAt
                }
              }
            }
          `,
          updateQuery: (previous, { subscriptionData }) => {
            const newChatLinks = [
              ...previous.allChats,
              subscriptionData.data.Chat.node
            ];
            const result = {
              ...previous,
              allChats: newChatLinks
            };
            return result;
          }
        });
      };

allChatsQuery tạo ra phương thức subscribeToMore. Khi method này được gọi, nó sẽ mở ra một kênh để giao tiếp realtime. Phương thức này lấy object mà chúng ta có thể định nghĩa tài liệu truy vấn và phương thức updateQuery.

Bạn có thể khởi động subscription này trong method lifecycle componentDidMount:

this._subscribeToNewChats();

Khi bạn chạy lại, app chat realtime sẽ được khởi động.

Kết luận

Qua bài viết chúng ta đã xây dựng được app chat realtime sử dụng React với GraphQL.

Tham khảo thêm React: Bí quyết lập trình React cho người mới bắt đầu


Like it? Share with your friends!

1061