用React + Redux + NodeJS 開發一個線上聊天室
阿新 • • 發佈:2019-01-05
最近工作比較閒,所以學了點React和Redux相關的東西,然後又做了個簡單的線上聊天室練手, 現在就記錄下如何用React和Redux來構建一個簡單的線上聊天室。
原始碼地址: Github (之後有時間也會陸續加點新功能、完善原有程式碼)
效果圖:
一、技術棧
1.前端展示層:React 2.前端資料流管理:Redux 3.前端樣式:Less 6.JS語法: ES6, 用Babel編譯 7.包管理: NPM二、專案結構
(因為考慮到後端實現可以很方便切換成別的語言實現,所以並沒有把前後端做成一個同構(isomorphic)應用。)
目錄結構:
client目錄下分src和build兩個資料夾。
- actions目錄下存放Redux框架中action的部分
- components目錄下存放用React寫的各個元件
- constants目錄下存放專案裡的一些常量
- containers目錄下存放對React元件的封裝,這種封裝是React和Redux連結的橋樑
- pages目錄下存放webpack打包的entries
- reducers目錄下存放Redux框架中reducer的部分
- routes目錄下存放React框架中路由管理的部分
server目錄下放的是後臺邏輯
特別簡單,就一個server.js,用來接收使用者請求,並返回響應。package.json和webpack配置檔案是NPM+Webpack組合的配置檔案,負責包管理、打包。 (使用NPM + Webpack進行前端開發的示例)
三、原始碼
1.向伺服器傳送請求
後臺server.js很簡單:
對相應的請求路徑返回相應的內容: 對'/'預設路徑返回index.html, 同時定義static路徑(static路徑在html中用到)對應原始碼中的哪個資料夾。 利用socket.io庫,監聽websocket連線,對連線發出的事件進行響應,廣播給別的連線知曉。/** * Created by hshen on 9/23/16. */ // Import modules var express = require('express'); var path = require('path'); var ejs = require('ejs'); // Create server var app = express() , server = require('http').createServer(app) , io = require('socket.io').listen(server); var port = process.env.PORT || 3000; server.listen(port); // Return index.html for '/' app.get('/', function (req, res) { res.render('index'); }); // Set path for views and static resources app.set('views', './client/src/html'); app.set('view engine', 'html'); app.engine('html', ejs.renderFile); app.use('/static', express.static('./client/build')); var userNumber = 0; io.sockets.on('connection', function (socket) { var signedIn = false; socket.on('newMessage', function (text) { io.sockets.emit('newMessage',{ userName: socket.userName, text: text }) }); socket.on('signIn', function (userName) { if (signedIn) return; // we store the username in the socket session for this client socket.userName = userName; ++userNumber; signedIn = true; io.sockets.emit('userJoined', { userName: userName, userNumber: userNumber }); }); socket.on('disconnect', function () { if (signedIn) { --userNumber; io.sockets.emit('userLeft', { userName: socket.userName, userNumber: userNumber }); } }); });
<!--client/src/html/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chatting Room</title>
</head>
<body>
<div class="main"></div>
<script type="text/javascript" src="static/js/common.js"></script>
<script type="text/javascript" src="static/js/index.js"></script>
</body>
</html>
使用者在訪問‘/’路徑後,拿到的是如上的html,這裡將會再去伺服器請求打包後的index.js和common.js。
index.js和common.js是webpack根據配置打包一系列js而成的,這時候瀏覽器會再發請求去獲取。(這樣的確影響前端效能,因此我覺得更好的做法是伺服器端渲染第一步,之後都在客戶端進行路由跳轉、渲染。)
2.客戶端渲染
這部分內容也就是主要的React+Redux這部分了。1)利用Provider來使React連線Redux的store
// js/pages/index.js , 專案入口檔案
/**
* Created by hshen on 9/20/16.
*/
import $ from "jquery"
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from 'js/reducers'
import Routes from 'js/routes'
let store = createStore(reducer)
import 'css/main.less'
render(
<Provider store={store}>
{Routes}
</Provider>,
$('.main')[0]
);
2)用react-router來做路由
/**
* Created by hshen on 9/24/16.
*/
import React from 'react'
import ChatContainer from 'js/containers/ChatContainer';
import SignInContainer from 'js/containers/SignInContainer';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
const Routes = (
<Router history={browserHistory}>
<Route path="/" component={SignInContainer} />
<Route path="/chat" component={ChatContainer}></Route>
</Router>
);
export default Routes;
3)在Container中利用connect方法獲取Redux的state和actions
/**
* Created by hshen on 9/24/16.
*/
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import Chat from 'js/components/chat/Chat';
import * as actions from 'js/actions/actions';
class ChatContainer extends Component {
render() {
return (
<Chat {...this.props} />
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
messages: state.messages,
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
receiveMessage: actions.receiveMessage,
sendMessage: actions.sendMessage,
userJoined: actions.userJoined,
userLeft: actions.userLeft
},dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatContainer)
4)在Component中對資料進行渲染
/**
* Created by hshen on 9/24/16.
*/
import React, { Component, PropTypes } from 'react';
import MessageInput from 'js/components/chat/MessageInput';
import MessageItem from 'js/components/chat/MessageItem';
import Singleton from 'js/socket'
import 'css/chat.less'
export default class Chat extends Component {
constructor(props, context) {
super(props, context);
this.socket = Singleton.getInstance();
}
componentWillMount() {
const { receiveMessage, userJoined, userLeft } = this.props;
this.socket.on('newMessage',function (msg) {
receiveMessage(msg);
});
this.socket.on('userJoined',function (data) {
console.log(data);
userJoined(data);
});
this.socket.on('userLeft',function (data) {
console.log(data);
userLeft(data);
});
}
sendMessage(newMessage) {
const { sendMessage, userName } = this.props;
if (newMessage.length !== 0) {
sendMessage(newMessage);
this.socket.emit('newMessage', newMessage);
}
}
render() {
const { messages} = this.props;
return (
<div className="chat">
<div className="chat-area">
<ul className="messages">
{messages.map( (message, index) =>
<MessageItem message={message} key={index}/>
)}
</ul>
</div>
<MessageInput sendMessage={this.sendMessage.bind(this)} />
</div>
);
}
}
就這樣,一個線上聊天室就完成了。