美文网首页
用nodejs和react实现即时通讯简易聊天室功能

用nodejs和react实现即时通讯简易聊天室功能

作者: 小盐_814e | 来源:发表于2019-08-16 15:40 被阅读0次

    命令行创建react 项目

    npx create-react-app socketio-demo
    

    进入socketio-demo目录
    运行eject进行拆包,本项目也可以不拆,这是个人习惯。
    注意如果运行eject命令最好在项目初始阶段执行,已经开始编写后不要再使用容易出现bug,新人谨慎使用eject命令

    yarn eject
    

    项目拆包后创建服务器文件夹和文件

    mkdir server
    type null>index.js
    

    创建完成后目录如下


    index.png

    编写即时通讯(聊天室)后台

    安装nodejs插件

    npm i express http socket.io nodemon
    

    进入server文件夹下的index.js页面开始编写后台程序

    const app = require('express')();  
    const server = require('http').Server(app);  
    const io = require('socket.io')(server);  
    //设置端口9093  
    server.listen(9093);  
    
    //创建socket.io连接  
    io.on('connection', function (socket) {  
      //获取messages事件  
      socket.on('messages', function (data) {  
        //向所有连接进行广播  
      socket.broadcast.emit('messages', data)  
        //对发出者进行广播,用户名加上我  
      data.user=data.user+'[我]'  
      socket.emit('messages', data)  
      });  
    });
    

    编写即时通讯(聊天室)前台

    后台编写完毕,可以在src目录中编写前台内容
    安装需要用到的react-router和redux依赖

    npm i redux react-redux react-router react-router-dom
    

    在src中创建io文件夹
    在io文件夹中创建所需要的文件

    cd src
    mkdir io
    cd io
    type null>login.js
    type null>socket-demo.js
    type null>socket-demo.css
    mkdir auth
    cd auth
    type null>auth.js
    

    创建完成后目录如下


    index.png

    这里auth.js文件是用来判断用户是否输入昵称,如已输入昵称可以进入聊天室,如没有输入昵称则跳回登录界面要求输入昵称

    本项目当中我们把昵称存在redux里实现登录界面和聊天室界面的共用,当然现这个项目比较小,如果想用localStorage存在本地也可以,不过考虑到后期的扩展性以及加深对redux的理解我还是选择存在redux当中

    • src文件夹下创建redux.js文件
    • src文件夹下创建redux文件夹,在redux文件夹下创建user.redux.js文件
    cd src
    type null>redux.js
    mkdir redux
    cd redux
    type null>user.redux.js
    

    新建目录如下


    index.png

    在redux文件夹下的user.redux.js中创建存储用户昵称的reducer

    const SET_USERNAME='SET_USERNAME'  
    //初始化仓库  
    const initState={user:''}  
    //根据动作改变仓库    
    export function User(state = initState, action) {  
      switch (action.type) {  
        case SET_USERNAME:  
          return {...state,user:action.payload}  
        default:  
          return state  
      }  
    }  
    //写入昵称动作  
    export function setUserName(user) {  
      return {  
        type:SET_USERNAME,  
      payload:user  
      }  
    }
    

    在src/redux.js文件中创建仓库
    combineReducers用于多个reducer的合并,这个项目中也可以不加,单为了后期扩展加入使用

    import { combineReducers, createStore } from 'redux'  
    import {User} from './redux/user.redux'  
    //window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 用于chrome redux的扩展项
    let reducer = combineReducers({ User })  
    let store = createStore(  
      reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())  
      
    export default store
    

    这样就可以在页面当中使用redux了

    下一步在app.js中引入redux,并把路由搭建起来
    在src/app.js中写入

    import React from 'react';
    import {HashRouter as Router,Route,Switch} from 'react-router-dom'
    import Login from "./io/login";
    import SocketDemo from "./io/socket-demo";
    import {Provider} from 'react-redux'
    import store from './redux'
    import Auth from "./io/auth/auth";
    
    
    function App() {
      return (
        <Provider store={store}>
          <Router>
            <Auth></Auth>
            <Switch>
              <Route exact path='/' component={Login}/>
              <Route exact path='/talk' component={SocketDemo}/>
            </Switch>
          </Router>
        </Provider>
      );
    }
    
    export default App;
    
    

    在写页面之前我们先安装修饰符插件

    npm i babel-plugin-transform-decorators-legacy
    

    Babel >= 7.x 时安装 @babel/plugin-proposal-decorators

    npm i @babel/plugin-proposal-decorators
    

    在package.json中babel项中配置,注意plugins放在presets前否则容易报错

    "babel": {  
      "plugins": [  
        ["@babel/plugin-proposal-decorators", { "legacy": true }]  
      ],  
      "presets": [  
        "react-app"  
      ]  
    }
    

    好了这样就可以使用装饰付了

    下面我们来编写判断是否设置用户名的程序
    打开src/io/auth下的auth.js文件

    import React from 'react';  
    import {connect} from 'react-redux'  
    import {withRouter} from 'react-router-dom'  
      
    //获取reducer  
    @connect(  
      state=>state,  
      {}  
    )  
    //获取router  
    @withRouter  
    class Auth extends React.Component{  
      componentDidMount() {  
        //如果有用户名就跳到聊天页,如没有则跳到登陆页。  
      if(this.props.User.user){  
          this.props.history.push('/talk')  
        }else {  
          this.props.history.push('/')  
        }  
      }  
      
      render() {  
        return null  
      }  
    }  
      
    export default Auth
    

    编写输入昵称并跳转步骤
    打开src/io/login.js文件

    import React from 'react';
    import './socket-demo.css';
    import {connect} from 'react-redux'
    import {setUserName} from '../redux/user.redux'
    
    @connect(
      null,
      {setUserName}
    )
    class Login extends React.Component{
      constructor(props) {
        super(props);
        this.state={
          user:''
        }
    
        this.login=this.login.bind(this)
        this.onKeyDown=this.onKeyDown.bind(this)
      }
    
      //键盘点击跳转  
      onKeyDown(e){
        switch (e.keyCode) {
          case 13:
            this.login();
            return;
          default:
            return;
        }
      }
    
      //添加键盘事件  
      componentDidMount() {
        document.addEventListener("keydown", this.onKeyDown)
      }
    
      //赋值state  
      handleChange(title,target){
        this.setState({
          [title]:target.target.value
        })
      }
    
      //赋值并跳转到聊天室页面  
      login(){
        let {user}=this.state;
        if(user!==null && user.trim()!==''){
          this.props.setUserName(user);
          this.props.history.push('/talk')
        }
      }
    
      render() {
        return (
          <div className='loginDiv'>
            <input type='text' placeholder='输入昵称' onChange={v=>this.handleChange('user',v)} />
            <button onClick={this.login}>进入聊天室</button>
          </div>  );
      }
    }
    
    export default Login
    

    下面是重头戏,聊天室的前端展示的核心代码
    打开src/iosocket-demo.js文件

    import React from 'react'
    import io from 'socket.io-client'
    import {connect} from 'react-redux'
    
    import './socket-demo.css'
    
    const url='ws://localhost:9093'
    const socket = io(url);
    @connect(
      state=>state,
      {}
    )
    class SocketDemo extends React.Component{
      constructor(props) {
        super(props);
        this.state={
          message:'',
          user:this.props.User.user,
          messages:[]
        }
        this.send=this.send.bind(this)
        this.login=this.login.bind(this)
        this.onKeyDown=this.onKeyDown.bind(this)
      }
    
      componentDidMount() {
        //输入欢迎信息  
        this.login()
        //增加回车事件  
        document.addEventListener("keydown", this.onKeyDown)
        //socket.io连接后台  
        io(url).on('connect', ()=>{
          console.log('connect');
          socket.on('messages', data => {
            //返回用户列表  
            this.setState({
              messages:[...this.state.messages,data]
            })
            if(this.refs.showDiv){
              this.refs.showDiv.scrollTop=2000
            }
            
          });
        });
      }
      componentWillUnmount() {
        //断开socket io连接  
        io('ws://localhost:9093').on('disconnect', function(){
          console.log('disconntect');
        });
    
        document.removeEventListener("keydown", this.onKeyDown)
      }
    
      //鼠标回车事件  
      onKeyDown(e){
        switch (e.keyCode) {
          case 13:
            this.send();
            return; default:
            return;
        }
      }
    
      //向后台发送信息  
      send(){
        let {user,message}=this.state;
        console.log(this.refs.showDiv);
    
        socket.emit('messages', {user,message});
        this.setState({
          message:''
        })
      }
    
      login(){
        let user=this.props.User.user;
        const obj={user:'作者',message:`欢迎${user}来到聊天室`}
        if(user.trim()!==''){
          this.setState({
            user:user,
            messages:[obj]
          })
        }
      }
    
      //赋值state  
      handleChange(title,target){
        this.setState({
          [title]:target.target.value
        })
      }
      render() {
        let cn='showInfo'
        return (
          <div>
            <div className='talkDiv'>
              <div className='operatingDiv'>
                <input  type='text'
                        placeholder='请在此输入聊天信息'
                        onChange={v=>this.handleChange('message',v)}
                        value={this.state.message}
                />
                <button onClick={this.send}>发送链接</button>
              </div> <div ref='showDiv' className='showDiv'>
              {
    
                this.state.messages.map((v,index)=>{
                  if(index===0){
                    cn='titleInfo'
                  }else{
                    cn='showInfo'
                  }
                  return (
                    <div className={cn} key={index}>
                      <span>{v.user}:</span>
                      <span>{v.message}</span>
                    </div>  )
                })
              }
            </div>
            </div>
          </div>  );
      }
    }
    
    export default SocketDemo;
    

    最后加上src/iosocket-demo.css

    body{  
        background: #008DB7;  
      font-family: 'Microsoft YaHei UI';  
      
    }  
    .loginDiv{  
        text-align: center;  
      margin: 150px auto 0;  
      width: 250px;  
    }  
    .loginDiv input[type='text']{  
        display: inline-block;  
      box-sizing: border-box;  
      border-radius: 5px;  
      padding-left: 5px;  
      border: none;  
      width: 250px;  
      height: 35px;  
      line-height: 35px;  
    }  
    .loginDiv button{  
        display: inline-block;  
      box-sizing: border-box;  
      border-radius: 5px;  
      padding-left: 5px;  
      border: none;  
      width: 250px;  
      height: 35px;  
      line-height: 35px;  
      margin-top: 10px;  
      background: #0067A2;  
      color: #ffffff;  
    }  
      
    .talkDiv{  
        position: fixed;  
      top: 0;  
      left: 0;  
      right: 0;  
      bottom: 0;  
    }  
      
    .talkDiv .operatingDiv{  
        position: fixed;  
      bottom: 0;  
      left: 0;  
      right: 0;  
      height: 40px;  
      display: flex;  
    }  
      
    .talkDiv .operatingDiv input[type='text']{  
        flex: 1;  
      height: 40px;  
      line-height: 40px;  
      box-sizing: border-box;  
      padding-left: 10px;  
    }  
    .talkDiv .operatingDiv button{  
        display: inline-block;  
      box-sizing: border-box;  
      border-radius: 5px;  
      border: none;  
      width: 250px;  
      height: 40px;  
      line-height: 40px;  
      background: #0067A2;  
      color: #ffffff;  
    }  
      
    .talkDiv .showDiv{  
        position: fixed;  
      bottom: 40px;  
      left: 0;  
      right: 0;  
      top: 0;  
      font-size: 16px;  
      color: #ffffff;  
      overflow: auto;  
    }  
    .talkDiv .showDiv .titleInfo{  
        padding: 10px;  
      color: yellow;  
      font-size: 20px;  
    }  
    .talkDiv .showDiv .showInfo{  
        padding: 10px;  
    }
    

    在package.json中加入命令行

    "scripts": {  
      "start": "node scripts/start.js",  
      "build": "node scripts/build.js",  
      "server": "nodemon server/index.js"
    },
    
    • 运行后台 yarn server
    • 运行前台 yarn start

    启动程序

    源码地址

    https://gitee.com/melissayan/sample-chat-demo

    预览地址

    http://106.12.186.15:9003

    相关文章

      网友评论

          本文标题:用nodejs和react实现即时通讯简易聊天室功能

          本文链接:https://www.haomeiwen.com/subject/shffsctx.html