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

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

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

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

效果图:

用React + Redux + NodeJS 开发一个在线聊天室_第1张图片



一、技术栈


1.前端展示层: React
2.前端数据流管理: Redux
3.前端样式: Less
4.后端: Node.js  Express框架
5.前后端通信: WebSocket开源库socket.io
6.JS语法: ES6, 用 Babel编译
7.包管理: NPM
8.项目打包: Webpack

二、项目结构


用React + Redux + NodeJS 开发一个在线聊天室_第2张图片



(因为考虑到后端实现可以很方便切换成别的语言实现,所以并没有把前后端做成一个 同构(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连接,对连接发出的事件进行响应,广播给别的连接知晓。







    
    Chatting Room



用户在访问‘/’路径后,拿到的是如上的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(
    
        {Routes}
    ,
    $('.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 = (
    
        
        
    
);

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 (
            
        );
    }
}

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 (
            
    {messages.map( (message, index) => )}
); } }

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

你可能感兴趣的:(前端)