react聊天室|react+redux仿微信聊天IM实例|react仿微信界面
<h3 id="一项目简介">一、项目概况</h3><p><span style="font-size: 12px; font-family: tahoma, arial, helvetica, sans-serif; background-color: rgba(204, 255, 255, 1)">基于react+react-dom+react-router-dom+redux+react-redux+webpack2.0+react-photoswipe+swiper等技术混合开发的手机端仿微信界面聊天室——reactChatRoom,实现了聊天记录下拉刷新、发送消息、表情(动图),图片、视频预览,打赏、红包等功能。</span></p>
<h3 id="三技术栈">二、技术栈</h3>
<ul>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">MVVM框架:react / react-dom</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">状态管理:redux / react-redux</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">页面路由:react-router-dom</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">弹窗插件:wcPop</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">打包工具:webpack 2.0</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">环境配置:node.js + cnpm</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">图片预览:react-photoswipe</span></li>
<li><span style="font-family: "comic sans ms", sans-serif; font-size: 12px">轮播滑动:swiper</span></li>
</ul>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621001734138-602948179.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621001908665-1039231408.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621001918147-2069742027.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621001939056-418820727.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621001945845-1749002524.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002004585-1996341370.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002016551-870307174.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002047688-1731322193.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002057262-778954764.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002111099-1043958072.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002119392-1155225876.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002130521-495099450.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002146561-1827077654.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621002152326-789644431.png"></p>
<p><strong>◆ package.json依赖安装:</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"name": "react-chatroom"<span style="color: rgba(0, 0, 0, 1)">,
</span>"version": "0.1.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"private": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"author": "andy"<span style="color: rgba(0, 0, 0, 1)">,
</span>"dependencies"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"react": "^16.8.6"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-dom": "^16.8.6"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-redux": "^7.0.3"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-router-dom": "^5.0.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-scripts": "0.9.x"<span style="color: rgba(0, 0, 0, 1)">,
</span>"redux": "^4.0.1"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"devDependencies"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"jquery": "^2.2.3"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-loadable": "^5.5.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-photoswipe": "^1.3.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"react-pullload": "^1.2.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"redux-thunk": "^2.3.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"swiper": "^4.5.0"<span style="color: rgba(0, 0, 0, 1)">,
</span>"webpack": "^1.13.1"<span style="color: rgba(0, 0, 0, 1)">,
</span>"webpack-dev-server": "^1.12.0"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"scripts"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"start": "set HOST=localhost&&set PORT=3003 && react-scripts start"<span style="color: rgba(0, 0, 0, 1)">,
</span>"build": "react-scripts build"<span style="color: rgba(0, 0, 0, 1)">,
</span>"test": "react-scripts test --env=jsdom"<span style="color: rgba(0, 0, 0, 1)">,
</span>"eject": "react-scripts eject"<span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p><strong>◆ 入口页面index.js配置</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
*@desc 入口页面index.js
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
import React from </span>'react'<span style="color: rgba(0, 0, 0, 1)">;
import ReactDOM from </span>'react-dom'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> import {HashRouter as Router, Route} from 'react-router-dom'</span>
import App from './App'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入状态管理</span>
import {Provider} from 'react-redux'<span style="color: rgba(0, 0, 0, 1)">
import {store} from </span>'./store'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入公共样式</span>
import './assets/fonts/iconfont.css'<span style="color: rgba(0, 0, 0, 1)">
import </span>'./assets/css/reset.css'<span style="color: rgba(0, 0, 0, 1)">
import </span>'./assets/css/layout.css'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入wcPop弹窗样式</span>
import './assets/js/wcPop/skin/wcPop.css'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入js</span>
import './assets/js/fontSize'<span style="color: rgba(0, 0, 0, 1)">
ReactDOM.render(
</span><Provider store={store}>
<App />
</Provider>,
document.getElementById('app'<span style="color: rgba(0, 0, 0, 1)">)
);</span></pre>
</div>
<p><strong>◆ 页面App.js主模板</strong></p>
<div class="cnblogs_code">
<pre>import React, { Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import {HashRouter as Router, Route, Switch, Redirect} from </span>'react-router-dom'<span style="color: rgba(0, 0, 0, 1)">
import {connect} from </span>'react-redux'<span style="color: rgba(0, 0, 0, 1)">
import $ from </span>'jquery'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入wcPop弹窗插件</span>
import { wcPop } from './assets/js/wcPop/wcPop'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入地址路由</span>
import routers from './router'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入顶部、底部tabbar</span>
import HeaderBar from './components/header'<span style="color: rgba(0, 0, 0, 1)">
import TabBar from </span>'./components/tabbar'<span style="color: rgba(0, 0, 0, 1)">
class App extends Component {
constructor(props){
super(props)
console.log(</span>'App主页面参数:\n' + JSON.stringify(props, <span style="color: rgba(0, 0, 255, 1)">null</span>, 2<span style="color: rgba(0, 0, 0, 1)">))
}
render() {
let token </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.token
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><Router>
<div className="weChatIM__panel clearfix">
<div className="we__chatIM-wrapper flexbox flex__direction-column"><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 顶部 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><Switch>
<HeaderBar />
</Switch>
<span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 主页面 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><div className="wcim__container flex1"><span style="color: rgba(0, 0, 0, 1)">
{</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 路由容器 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
</span><Switch><span style="color: rgba(0, 0, 0, 1)">
{
routers.map((item, index) </span>=><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <Route key={index} path={item.path} exact render={props =><span style="color: rgba(0, 0, 0, 1)"> (
</span>!item.meta || !item.meta.requireAuth ? (<item.component {...props} />) : (
token ? <item.component {...props} /> : <Redirect to={{pathname: '/login'<span style="color: rgba(0, 0, 0, 1)">, state: {from: props.location}}} />
)
)} />
})
}
{/* 初始化页面跳转 */}
<Redirect push to="/index" />
</Switch>
</div>
{/* 底部tabbar */}
<Switch>
<TabBar />
</Switch>
</div>
</div>
</Router>
);
}
}
const mapStateToProps = (state) =>{
return {
...state.auth
}
}
export default connect(mapStateToProps)(App);</span></pre>
</div>
<p><strong>◆ react登录、注册模块 / react登录注册验证</strong></p>
<div class="cnblogs_code">
<pre>import React, { Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import { Link } from </span>'react-router-dom'<span style="color: rgba(0, 0, 0, 1)">
import { connect } from </span>'react-redux'<span style="color: rgba(0, 0, 0, 1)">;
import </span>* as actions from '../../store/action'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入wcPop弹窗插件</span>
import { wcPop } from '../../assets/js/wcPop/wcPop.js'<span style="color: rgba(0, 0, 0, 1)">
class Login extends Component {
constructor(props) {
super(props)
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
tel: </span>''<span style="color: rgba(0, 0, 0, 1)">,
pwd: </span>''<span style="color: rgba(0, 0, 0, 1)">,
vcode: </span>''<span style="color: rgba(0, 0, 0, 1)">,
vcodeText: </span>'获取验证码'<span style="color: rgba(0, 0, 0, 1)">,
disabled: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
time: </span>0<span style="color: rgba(0, 0, 0, 1)">
}
}
componentDidMount(){
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.token){
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.props.history.push('/'<span style="color: rgba(0, 0, 0, 1)">)
}
}
render() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><div className="wcim__lgregWrapper flexbox flex__direction-column"><span style="color: rgba(0, 0, 0, 1)">
......
</span></div>
<span style="color: rgba(0, 0, 0, 1)"> )
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交表单</span>
handleSubmit = (e) =><span style="color: rgba(0, 0, 0, 1)"> {
e.preventDefault();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> that = <span style="color: rgba(0, 0, 255, 1)">this</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.state.tel = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.refs.tel.value
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.pwd = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.refs.pwd.value
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.vcode = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.refs.vcode.value
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tel) {
wcPop({ content: </span>'手机号不能为空!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!checkTel(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tel)) {
wcPop({ content: </span>'手机号格式不正确!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.pwd) {
wcPop({ content: </span>'密码不能为空!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.vcode) {
wcPop({ content: </span>'验证码不能为空!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取登录之前的页面地址</span>
let redirectUrl = <span style="color: rgba(0, 0, 255, 1)">this</span>.props.location.state ? <span style="color: rgba(0, 0, 255, 1)">this</span>.props.location.state.from.pathname : '/'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置token</span>
<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.authToken(getToken())
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.props.authUser(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tel)
wcPop({
content: </span>'注册成功!', style: 'background:#41b883;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)">,
end: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
that.props.history.push(redirectUrl)
}
});
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 60s倒计时</span>
handleVcode = (e) =><span style="color: rgba(0, 0, 0, 1)"> {
e.preventDefault();
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.tel = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.refs.tel.value
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tel) {
wcPop({ content: </span>'手机号不能为空!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!checkTel(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.tel)) {
wcPop({ content: </span>'手机号格式不正确!', style: 'background:#ff3b30;color:#fff;', time: 2<span style="color: rgba(0, 0, 0, 1)"> });
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.time = 60
<span style="color: rgba(0, 0, 255, 1)">this</span>.state.disabled = <span style="color: rgba(0, 0, 255, 1)">true</span>
<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.countDown();
}
}
countDown </span>= (e) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">this</span>.state.time > 0<span style="color: rgba(0, 0, 0, 1)">){
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.time--
<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
vcodeText: </span>'获取验证码(' + <span style="color: rgba(0, 0, 255, 1)">this</span>.state.time + ')'<span style="color: rgba(0, 0, 0, 1)">
})
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> setTimeout(this.countDown, 1000);</span>
setTimeout(() =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.countDown()
}, </span>1000<span style="color: rgba(0, 0, 0, 1)">);
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
time: </span>0<span style="color: rgba(0, 0, 0, 1)">,
vcodeText: </span>'获取验证码'<span style="color: rgba(0, 0, 0, 1)">,
disabled: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
})
}
}
}
const mapStateToProps </span>= (state) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
...state.auth
}
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> connect(mapStateToProps, {
authToken: actions.setToken,
authUser: actions.setUser
})(Login)</span></pre>
</div>
<p><span style="font-size: 12px">React-Chat移动端聊天实例|react18 hooks仿微信App聊天界面</span></p>
<p><span style="font-size: 12px">react18-webchat网页聊天实例|React Hooks+Arco Design仿微信桌面端</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201906/1289798-20190621003840111-1165511516.jpg"></p>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)<br><br>
来源:https://www.cnblogs.com/xiaoyan2017/p/11062316.html
頁:
[1]