ReactJs - Xây dựng ứng dụng chat và deploy lên web - Phần 1

Giới thiệu

Ứng dụng này là một web app đơn giản có chức năng chat realtime giữa các người dùng, phần frontend sẽ được xây dựng bằng ReactJs và đây cũng chính là trọng tâm kiến thức mà mình muốn chia sẻ với các bạn. Còn backend sẽ là một server node.js làm nhiệm vụ gửi và nhận tin nhắn giữa các người dùng, tất nhiên là sẽ realtime nghĩa là bạn không cần phải reload lại trang mà vẫn có thể nhận được tin nhắn từ người dùng khác gửi đến cho mình, và thư viện socket.io sẽ giúp ta làm việc đó. Nếu bạn chưa biết về node.js và socket.io thì cũng không vấn đề gì, khi cần dùng mình sẽ giới thiệu về nó, và vì node.js và socket.io đều được viết bằng javascript nên chúng ta sẽ làm quen với nó rất nhanh.

Chuẩn bị kiến thức

Để làm được ứng dụng này thì bạn phải có hiểu biết cơ bản về ReactJs rồi, hiểu về một số khái niệm như components, props, state, handling events, lifecycle ... và cũng cần hiểu biết một chút về ES6. Nhưng nếu các bạn chưa biết thì cũng không sao, trong lúc làm mình sẽ giải thích từng đoạn code, nếu đọc code mà các bạn vẫn không hiểu thì cũng không sao, cứ copy code cho chạy được rồi hiểu sau(mình cũng hay như thế). Điều quan trọng là bạn muốn làm nên ứng dụng này mà thôi. Nhưng mình vẫn khuyến khích các bạn tìm hiểu về react trước vì khi đó bạn sẽ dễ dàng theo dõi bài viết này hơn. Tài liệu về ReacJS có rất nhiều trên mạng, bạn có thể tham khảo một số project đơn giản ví dụ https://github.com/DoanhPHAM/todo, và đọc document https://reactjs.org/docs/hello-world.html

Xây dựng giao diện cho ứng dụng bằng React

Đầu tiên, trước khi xây dựng giao diện bằng React thì cần phải biết giao diện nó thế nào, lên google search 'bootstrap chat template' rồi lang thang một số trang, mình cũng chọn được cái giao diện ưng ý, và nó đây https://bootsnipp.com/snippets/WaEvr

Khởi tạo ứng dụng React

Khi đã có giao diện html thuần rồi thì ta phải chuyển nó qua React, và đây cũng chính là phần khó nhất trong bài viết này. Để chuyển giao diện qua React điều đầu tiên là các bạn phải khởi tạo project React, để có một project React tốt nhất, ta cần phải biết đến npm, babel, webpack. Đối với người mới thì việc config sẽ khá phức tạp, nhưng không sao để đơn giản, chúng ta sẽ không khởi tạo project bằng tay mà bằng Create React App, nó là một package giúp tự động hóa việc xây dựng ứng dụng React. Để cài đặt được package Create React App máy tình cần được cài đặt Node.Js, mình sẽ mặc định các bạn đã cài Node js rồi, nếu chưa thì các bạn google để cài nha, cứ bản mới nhất mà cài thôi Mở command line lên và gõ

npm install -g create-react-app

Create React App sẽ được cài đặt cục bộ trên máy tính, tiếp theo khởi tạo ứng dụng

create-react-app chat-client

với chat-client là tên của project sau khi cài xong, các bạn cd chat-client và chạy lệnh

npm run

nếu trình duyệt bật lên tab thế này là chúng ta đã khởi tạo thành công project React Sau khi chạy xong, bạn đã có ứng dụng chat-client, cấu trúc thư mục của ứng dụng như sau:

├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   └── favicon.ico
│   └── index.html
│   └── manifest.json
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js 

cuối cùng bạn mở file package.json lên và thay đổi nội dung thành như này

{
"name": "client",
"version": "0.1.0",
"private": true,
"homepage": "https://ththth0303.github.io/chat-client",
"author": "Thang",
"dependencies": {
  "gh-pages": "^1.0.0",
  "jquery": "^3.2.1",
  "lodash": "^4.17.4",
  "react": "^16.0.0",
  "react-dom": "^16.0.0",
  "react-scripts": "1.0.14",
  "socket.io-client": "^2.0.4"
},
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject",
  "deploy": "react-scripts build && gh-pages -d build"
}
}

Chú ý trường homepage bạn cần thay đổi chat-client bằng tên reopsitory github của bạn để phục vụ cho việc deploy sau này. Tiếp theo, cài đặt các gói cần thiết, chạy

npm install

Chia components

Đây là giao diện chat mà mình giới thệu với các bạn, những khung màu khác nhau là những components mà mình chia, việc chia components này chưa hẳn là tối ưu vì nó vẫn có thể chia nhỏ hơn nữa để dễ quản lý, nhưng đối với người mới chưa quen với việc làm việc với các components thì sẽ khá là rắc rối, nên mình chỉ chia đơn giản vậy thôi Ngoài cùng là component App, các bạn vào trong thư mục src, đây là thư mục làm việc chính chứa sourec code của các bạn, mở file App.js ra, xóa cái code cũ đi và copy đè đoạn code này lên, các bạn đọc comment bên trong code để hiểu hơn nhé

import React from 'react';
import $ from 'jquery';
import Messages from './message-list';
import Input from './input';
import _map from 'lodash/map';
import io from 'socket.io-client';

import './App.css';

export default class App extends React.Component {
   constructor(props) {
       super(props);
       //Khởi tạo state,
       this.state = {
           messages: [
               {id: 1, userId: 0, message: 'Hello'}
           ],
           user: null,
       }
       this.socket = null;
   }
   //Connetct với server nodejs, thông qua socket.io
   componentWillMount() {
       this.socket = io('localhost:6969');
       this.socket.on('id', res => this.setState({user: res})) // lắng nghe event có tên 'id'
       this.socket.on('newMessage', (response) => {this.newMessage(response)}); //lắng nghe event 'newMessage' và gọi hàm newMessage khi có event
   }
   //Khi có tin nhắn mới, sẽ push tin nhắn vào state mesgages, và nó sẽ được render ra màn hình
   newMessage(m) {
       const messages = this.state.messages;
       let ids = _map(messages, 'id');
       let max = Math.max(...ids);
       messages.push({
           id: max+1,
           userId: m.id,
           message: m.data
       });

       let objMessage = $('.messages');
       if (objMessage[0].scrollHeight - objMessage[0].scrollTop === objMessage[0].clientHeight ) {
           this.setState({messages});
           objMessage.animate({ scrollTop: objMessage.prop('scrollHeight') }, 300); //tạo hiệu ứng cuộn khi có tin nhắn mới

       } else {
           this.setState({messages});
           if (m.id === this.state.user) {
               objMessage.animate({ scrollTop: objMessage.prop('scrollHeight') }, 300);
           }
       }
   }
   //Gửi event socket newMessage với dữ liệu là nội dung tin nhắn
   sendnewMessage(m) {
       if (m.value) {
           this.socket.emit("newMessage", m.value); //gửi event về server
           m.value = ""; 
       }
   }

   render () {
       return (
          <div className="app__content">
             <h1>chat box</h1>
             <div className="chat_window">
                 <Messages user={this.state.user} messages={this.state.messages} typing={this.state.typing}/>
                 <Input sendMessage={this.sendnewMessage.bind(this)}/>
             </div>
           </div>
       )
   }
}

Tiếp theo là component khoanh màu đỏ, component Input, tạo file input.js

import React from 'react';

export default class App extends React.Component {

    checkEnter(e) {
      console.log(e)
      if (e.keyCode === 13) {
        this.props.sendMessage(this.refs.messageInput);
      }
    }
    render () {
        return (
           <div className="">
               <div className="bottom_wrapper">
                   <div  className="message_input_wrapper">
                        <input ref="messageInput" type="text" className="message_input" placeholder="Type your message here" onKeyUp={this.checkEnter.bind(this)} />
                   </div>
                   <div className="send_message" onClick={() => this.props.sendMessage(this.refs.messageInput)} ref="inputMessage" >
                        <div className='icon'></div>
                        <div className='text'>Send</div>
                   </div>
               </div>
           </div>
        )
    }
}

Component màu tím là component MessageItem, tạo file message-item.js

import React from 'react';

import './item.scss';

export default class App extends React.Component {

    
    render () {
        return (
            <li className={this.props.user ? 'message right' : 'message left'}>
                <div className="avatar"><img src="" alt="user" /></div>
                <div className="text_wrapper">
                    <div className="box bg-light-info">{this.props.message}</div>
                </div>
                <div className="time">10:56 am</div>
            </li>
        )
    }
}

Component màu vàng component MessageList, nó gồm nhiều component MessageItem, tạo file message-list

import React from 'react';
import Item from '../message';

import './message-list.scss'


export default class App extends React.Component {
    render () {
        return (
            <ul className="messages clo-md-5">
                {this.props.messages.map(item =>
                    <Item key={item.id} user={item.userId == this.props.user? true : false} message={item.message}/>
                )}   
            </ul>
        )
    }
}

Đã xong, giờ các bạn vào địa chỉ localhost:3000 sẽ được màn hình thế này Tất nhiên chỉ có giao diện thôi chứ chưa gửi được tin nhắn, để gửi được ta cần tạo server node js.

Tạo server Node

Việc tạo server Node khá đơn giản, đầu tiên các bạn tạo 1 thưc mục tên gì cũng được, thư mục của mình là bên trong sẽ có file package.json với nội dung như sau

{
  "name": "botchat",
  "version": "1.0.0",
  "description": "begin",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ththth0303/server-chat.git"
  },
  "author": "thangnt",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/ththth0303/server-chat/issues"
  },
  "homepage": "https://github.com/ththth0303/server-chat#readme",
  "dependencies": {
    "ejs": "^2.5.7",
    "express": "^4.15.4",
    "request": "^2.81.0",
    "socket.io": "^2.0.3"
  }
}

Tiếp theo tạo file index.js với nội dung

var express = require('express');
var app = express();

var server = require('http').Server(app);
var port = (process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || 6969);
var io = require('socket.io')(server);
server.listen(port, () => console.log('Server running in port ' + port));

io.on('connection', function(socket){
  console.log(socket.id + ': connected');
  socket.emit('id', socket.id);

  socket.on('disconnect', function(){
    console.log(socket.id + ': disconnected')
  })

  socket.on('newMessage', data => {
    io.sockets.emit('newMessage', {data: data, id: socket.id});
    console.log(data);
  })

});

app.get('/', (req, res) => {
  res.send("Home page. Server running okay.");
})

Mở command line lên và chạy lện npm install, khi cài xong thì bạn chạy tiếp lệnh

node index.js

Nếu màn hình console hiện lên như này là các bạn đã thành công Giờ các bạn mở lại địa chỉ http://localhost:3000, giờ các bạn có thể chat được rồi, mở 2 tab lên và thử nha!

Kết

Giờ chúng ta có thể chat với nhau qua local, nhưng như này thì chưa được gọi là ứng dụng chat được, cần phải định danh người chat, phòng chat, lưu database, và cuối cùng là deploy, phần này chỉ làm quen với React, Node, Socket.io và demo nho nhỏ thôi.. Phần sau mình sẽ giải thích kỹ hơn về Socket.io, cơ chế hoạt động của ứng dụng. Phần này tạm dừng ở đây thôi, hẹn gặp bạn ở phần sau !!! Các bạn có thể xem link git của ứng dụng tại đây