nodejs和react實(shí)現(xiàn)即時(shí)通訊簡(jiǎn)易聊天室功能
npx create-react-app socketio-demo
進(jìn)入socketio-demo目錄 運(yùn)行eject進(jìn)行拆包,本項(xiàng)目也可以不拆,這是個(gè)人習(xí)慣。 注意如果運(yùn)行eject命令最好在項(xiàng)目初始階段執(zhí)行,已經(jīng)開始編寫后不要再使用容易出現(xiàn)bug,新人謹(jǐn)慎使用eject命令
yarn eject
項(xiàng)目拆包后創(chuàng)建服務(wù)器文件夾和文件
mkdir server type null>index.js
創(chuàng)建完成后目錄如下
編寫即時(shí)通訊(聊天室)后臺(tái)
安裝nodejs插件
npm i express http socket.io nodemon
進(jìn)入server文件夾下的index.js頁(yè)面開始編寫后臺(tái)程序
const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);
//設(shè)置端口9093
server.listen(9093);
//創(chuàng)建socket.io連接
io.on('connection', function (socket) {
//獲取messages事件
socket.on('messages', function (data) {
//向所有連接進(jìn)行廣播
socket.broadcast.emit('messages', data)
//對(duì)發(fā)出者進(jìn)行廣播,用戶名加上我
data.user=data.user+'[我]'
socket.emit('messages', data)
});
});
編寫即時(shí)通訊(聊天室)前臺(tái)
后臺(tái)編寫完畢,可以在src目錄中編寫前臺(tái)內(nèi)容 安裝需要用到的react-router和redux依賴
npm i redux react-redux react-router react-router-dom
在src中創(chuàng)建io文件夾 在io文件夾中創(chuàng)建所需要的文件
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
創(chuàng)建完成后目錄如下
這里auth.js文件是用來判斷用戶是否輸入昵稱,如已輸入昵稱可以進(jìn)入聊天室,如沒有輸入昵稱則跳回登錄界面要求輸入昵稱
本項(xiàng)目當(dāng)中我們把昵稱存在redux里實(shí)現(xiàn)登錄界面和聊天室界面的共用,當(dāng)然現(xiàn)這個(gè)項(xiàng)目比較小,如果想用localStorage存在本地也可以,不過考慮到后期的擴(kuò)展性以及加深對(duì)redux的理解我還是選擇存在redux當(dāng)中
src文件夾下創(chuàng)建redux.js文件
src文件夾下創(chuàng)建redux文件夾,在redux文件夾下創(chuàng)建user.redux.js文件
cd src type null>redux.js mkdir redux cd redux type null>user.redux.js
新建目錄如下
在redux文件夾下的user.redux.js中創(chuàng)建存儲(chǔ)用戶昵稱的reducer
const SET_USERNAME='SET_USERNAME'
//初始化倉(cāng)庫(kù)
const initState={user:''}
//根據(jù)動(dòng)作改變倉(cāng)庫(kù)
export function User(state = initState, action) {
switch (action.type) {
case SET_USERNAME:
return {...state,user:action.payload}
default:
return state
}
}
//寫入昵稱動(dòng)作
export function setUserName(user) {
return {
type:SET_USERNAME,
payload:user
}
}
在src/redux.js文件中創(chuàng)建倉(cāng)庫(kù) combineReducers用于多個(gè)reducer的合并,這個(gè)項(xiàng)目中也可以不加,單為了后期擴(kuò)展加入使用
import { combineReducers, createStore } from 'redux'
import {User} from './redux/user.redux'
//window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 用于chrome redux的擴(kuò)展項(xiàng)
let reducer = combineReducers({ User })
let store = createStore(
reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
export default store
這樣就可以在頁(yè)面當(dāng)中使用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;
在寫頁(yè)面之前我們先安裝修飾符插件
npm i babel-plugin-transform-decorators-legacy
Babel >= 7.x 時(shí)安裝 @babel/plugin-proposal-decorators
npm i @babel/plugin-proposal-decorators
在package.json中babel項(xiàng)中配置,注意plugins放在presets前否則容易報(bào)錯(cuò)
"babel": {
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }]
],
"presets": [
"react-app"
]
}
好了這樣就可以使用裝飾付了
下面我們來編寫判斷是否設(shè)置用戶名的程序 打開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() {
//如果有用戶名就跳到聊天頁(yè),如沒有則跳到登陸頁(yè)。
if(this.props.User.user){
this.props.history.push('/talk')
}else {
this.props.history.push('/')
}
}
render() {
return null
}
}
export default Auth
編寫輸入昵稱并跳轉(zhuǎn)步驟 打開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)
}
//鍵盤點(diǎn)擊跳轉(zhuǎn)
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
})
}
//賦值并跳轉(zhuǎn)到聊天室頁(yè)面
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}>進(jìn)入聊天室</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連接后臺(tái)
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)
}
//鼠標(biāo)回車事件
onKeyDown(e){
switch (e.keyCode) {
case 13:
this.send();
return; default:
return;
}
}
//向后臺(tái)發(fā)送信息
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='請(qǐng)?jiān)诖溯斎肓奶煨畔?
onChange={v=>this.handleChange('message',v)}
value={this.state.message}
/>
<button onClick={this.send}>發(fā)送鏈接</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"
},
- 運(yùn)行后臺(tái) yarn server
- 運(yùn)行前臺(tái) yarn start
啟動(dòng)程序
總結(jié)
以上所述是小編給大家介紹的nodejs和react實(shí)現(xiàn)即時(shí)通訊簡(jiǎn)易聊天室功能,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
相關(guān)文章
create-react-app安裝出錯(cuò)問題解決方法
這篇文章主要介紹了create-react-app安裝出錯(cuò)問題解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09
Ant?Design?組件庫(kù)之步驟條實(shí)現(xiàn)
這篇文章主要為大家介紹了Ant?Design組件庫(kù)之步驟條實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
React中useEffect原理的代碼簡(jiǎn)單實(shí)現(xiàn)詳解
React的useEffect鉤子是React函數(shù)組件中處理副作用,本文將通過一個(gè)簡(jiǎn)單的例子解釋如何用代碼實(shí)現(xiàn)useEffect的基本原理,感興趣的小伙伴可以了解下2023-12-12
react-native android狀態(tài)欄的實(shí)現(xiàn)
這篇文章主要介紹了react-native android狀態(tài)欄的實(shí)現(xiàn),使?fàn)顟B(tài)欄顏色與App顏色一致,使用戶界面更加整體。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
ReactNative頁(yè)面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼
本篇文章主要介紹了ReactNative頁(yè)面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
React組件實(shí)例三大核心屬性State props Refs詳解
組件實(shí)例的三大核心屬性是:State、Props、Refs。類組件中這三大屬性都存在。函數(shù)式組件中訪問不到 this,也就不存在組件實(shí)例這種說法,但由于它的特殊性(函數(shù)可以接收參數(shù)),所以存在Props這種屬性2022-12-12

