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-09Ant?Design?組件庫(kù)之步驟條實(shí)現(xiàn)
這篇文章主要為大家介紹了Ant?Design組件庫(kù)之步驟條實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08React中useEffect原理的代碼簡(jiǎn)單實(shí)現(xiàn)詳解
React的useEffect鉤子是React函數(shù)組件中處理副作用,本文將通過一個(gè)簡(jiǎn)單的例子解釋如何用代碼實(shí)現(xiàn)useEffect的基本原理,感興趣的小伙伴可以了解下2023-12-12react-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-06ReactNative頁(yè)面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼
本篇文章主要介紹了ReactNative頁(yè)面跳轉(zhuǎn)Navigator實(shí)現(xiàn)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08React組件實(shí)例三大核心屬性State props Refs詳解
組件實(shí)例的三大核心屬性是:State、Props、Refs。類組件中這三大屬性都存在。函數(shù)式組件中訪問不到 this,也就不存在組件實(shí)例這種說法,但由于它的特殊性(函數(shù)可以接收參數(shù)),所以存在Props這種屬性2022-12-12