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


Posted in NodeJs onAugust 21, 2019

npx create-react-app socketio-demo

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

yarn eject

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

mkdir server
type null>index.js

创建完成后目录如下

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

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

安装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

创建完成后目录如下

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

这里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

新建目录如下

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

在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

启动程序

总结

以上所述是小编给大家介绍的nodejs和react实现即时通讯简易聊天室功能,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

NodeJs 相关文章推荐
nodejs分页类代码分享
Jun 17 NodeJs
nodejs 实现钉钉ISV接入的加密解密方法
Jan 16 NodeJs
nodejs读写json文件的简单方法(必看)
Mar 09 NodeJs
详解nodejs微信公众号开发——1.接入微信公众号
Apr 10 NodeJs
Nodejs搭建wss服务器教程
May 24 NodeJs
nodeJS微信分享
Dec 20 NodeJs
NodeJs搭建本地服务器之使用手机访问的实例讲解
May 12 NodeJs
M2实现Nodejs项目自动部署的方法步骤
May 05 NodeJs
NodeJs实现简易WEB上传下载服务器
Aug 10 NodeJs
nodejs一个简单的文件服务器的创建方法
Sep 13 NodeJs
nodejs处理tcp连接的核心流程
Feb 26 NodeJs
Nodejs 识别图片类型的方法
Aug 15 #NodeJs
NodeJs实现简易WEB上传下载服务器
Aug 10 #NodeJs
NodeJs 实现简单WebSocket即时通讯的示例代码
Aug 05 #NodeJs
Nodejs监听日志文件的变化的过程解析
Aug 04 #NodeJs
nodejs对项目下所有空文件夹创建gitkeep的方法
Aug 02 #NodeJs
nodejs读取图片返回给浏览器显示
Jul 25 #NodeJs
关于NodeJS中的循环引用详解
Jul 23 #NodeJs
You might like
php zend解密软件绿色版测试可用
2008/04/14 PHP
PHP中feof()函数实例测试
2014/08/23 PHP
Laravel中扩展Memcached缓存驱动实现使用阿里云OCS缓存
2015/02/10 PHP
javascript firefox不显示本地预览图片问题的解决方法
2008/11/12 Javascript
js/html光标定位的实现代码
2013/09/23 Javascript
使用CSS和jQuery模拟select并附提交后取得数据的代码
2013/10/18 Javascript
javascript loadScript异步加载脚本示例讲解
2013/11/14 Javascript
JS常用字符串处理方法应用总结
2014/05/22 Javascript
jquery实现显示已选用户
2014/07/21 Javascript
js 左右悬浮对联广告特效代码
2014/12/12 Javascript
jquery使用hide方法隐藏指定id的元素
2015/03/30 Javascript
JavaScript返回上一页的三种方法及区别介绍
2015/07/04 Javascript
JS实现在状态栏显示打字效果完整实例
2015/11/02 Javascript
javascript 用函数实现继承详解
2016/05/28 Javascript
JS实现图片剪裁并预览效果
2016/08/12 Javascript
JS中作用域和变量提升(hoisting)的深入理解
2016/10/31 Javascript
jQuery Easyui datagrid editor为combobox时指定数据源实例
2016/12/19 Javascript
js实现定时进度条完成后切换图片
2017/01/04 Javascript
原生JavaScript实现Tooltip浮动提示框特效
2017/03/07 Javascript
用vue和node写的简易购物车实现
2017/04/25 Javascript
express express-session的使用小结
2018/12/12 Javascript
使用element-ui table expand展开行实现手风琴效果
2019/03/15 Javascript
功能完善的小程序日历组件的实现
2020/03/31 Javascript
Python迭代器与生成器基本用法分析
2018/07/26 Python
wxpython自定义下拉列表框过程图解
2020/02/14 Python
pytorch实现从本地加载 .pth 格式模型
2020/02/14 Python
HTML5声音录制/播放功能的实现代码
2018/05/03 HTML / CSS
移动端Html5中百度地图的点击事件
2019/01/31 HTML / CSS
HTML5跳转小程序wx-open-launch-weapp的示例代码
2020/07/16 HTML / CSS
澳大利亚运动鞋商店:Platypus Shoes
2019/09/27 全球购物
Guess荷兰官网:美国服饰品牌
2020/01/22 全球购物
什么是符号链接,什么是硬链接?符号链接与硬链接的区别是什么?
2014/01/19 面试题
餐饮营销方案
2014/02/23 职场文书
预备党员入党自我评价范文
2014/03/10 职场文书
蟋蟀的住宅教学反思
2014/04/26 职场文书
2014年仓库管理员工作总结
2014/11/18 职场文书