用React + Redux + NodeJS 开发一个在线聊天室

前端之家收集整理的这篇文章主要介绍了用React + Redux + NodeJS 开发一个在线聊天室前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

最近工作比较闲,所以学了点React和Redux相关的东西,然后又做了个简单的在线聊天室练手, 现在就记录下如何用React和Redux来构建一个简单的在线聊天室。

项目在线演示地址:https://murmuring-brook-22426.herokuapp.com/(heroku可以用来免费部署一些自己的小项目玩,还是不错的。 缺点是国内的网访问有点慢。)

源码地址: Github(之后有时间也会陆续加点新功能、完善原有代码


一、技术栈


1.前端展示层: React
2.前端数据流管理: Redux
3.前端样式: Less
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.向服务器发送请求

<pre name="code" class="javascript">/**  
 * server/server.js
 *<span style="white-space:pre">	</span>
 * 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
            });
        }
    });

});
 
 
后台server.js很简单:
对相应的请求路径返回相应的内容: 对'/'默认路径返回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,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>
        );
    }
}

就这样,一个在线聊天室就完成了。

猜你在找的React相关文章