用React + Redux + NodeJS 开发一个在线聊天室
最近工作比较闲,所以学了点React和Redux相关的东西,然后又做了个简单的在线聊天室练手, 现在就记录下如何用React和Redux来构建一个简单的在线聊天室。
项目在线演示地址:https://murmuring-brook-22426.herokuapp.com/ (heroku可以用来免费部署一些自己的小项目玩,还是不错的。 缺点是国内的网访问有点慢。)
源码地址: Github (之后有时间也会陆续加点新功能、完善原有代码)
效果图:
1.前端展示层:React 2.前端数据流管理:Redux 3.前端样式:Less 4.后端: Node.js Express框架 5.前后端通信: WebSocket开源库socket.io 6.JS语法: ES6, 用Babel编译 7.包管理: NPM 8.项目打包: Webpack
(因为考虑到后端实现可以很方便切换成别的语言实现,所以并没有把前后端做成一个同构(isomorphic)应用。)
目录结构:
client目录下分src和build两个文件夹。
build文件夹是项目打包后生成的。
src下面分html、js、less三个文件夹, html存放页面最原始的html,less存放样式文件,js目录下则是主要的前端逻辑。
- 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很简单:
/** * 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 }); } }); });对相应的请求路径返回相应的内容: 对"/"默认路径返回index.html, 同时定义static路径(static路径在html中用到)对应源代码中的哪个文件夹。 利用socket.io库,监听websocket连接,对连接发出的事件进行响应,广播给别的连接知晓。
<!--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> ); } }
就这样,一个在线聊天室就完成了。
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。