逆风方向 發表於 2019-6-21 00:44:00

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: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">MVVM框架:react / react-dom</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">状态管理:redux / react-redux</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">页面路由:react-router-dom</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">弹窗插件:wcPop</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">打包工具:webpack 2.0</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">环境配置:node.js + cnpm</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, sans-serif; font-size: 12px">图片预览:react-photoswipe</span></li>
<li><span style="font-family: &quot;comic sans ms&quot;, 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>◆&nbsp;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&amp;&amp;set PORT=3003 &amp;&amp; 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>&lt;Provider store={store}&gt;
    &lt;App /&gt;
&lt;/Provider&gt;,
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>&lt;Router&gt;
      &lt;div className="weChatIM__panel clearfix"&gt;
          &lt;div className="we__chatIM-wrapper flexbox flex__direction-column"&gt;<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>&lt;Switch&gt;
            &lt;HeaderBar /&gt;
            &lt;/Switch&gt;
<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>&lt;div className="wcim__container flex1"&gt;<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>&lt;Switch&gt;<span style="color: rgba(0, 0, 0, 1)">
                {
                  routers.map((item, index) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;Route key={index} path={item.path} exact render={props =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                      </span>!item.meta || !item.meta.requireAuth ? (&lt;item.component {...props} /&gt;) : (
                        token ? &lt;item.component {...props} /&gt; : &lt;Redirect to={{pathname: '/login'<span style="color: rgba(0, 0, 0, 1)">, state: {from: props.location}}} /&gt;
                      )
                  )} /&gt;
                  })
                }
                {/* 初始化页面跳转 */}
                &lt;Redirect push to="/index" /&gt;
            &lt;/Switch&gt;
            &lt;/div&gt;

            {/* 底部tabbar */}
            &lt;Switch&gt;
            &lt;TabBar /&gt;
            &lt;/Switch&gt;
          &lt;/div&gt;
      &lt;/div&gt;
      &lt;/Router&gt;
    );
}
}

const mapStateToProps = (state) =&gt;{
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>&lt;div className="wcim__lgregWrapper flexbox flex__direction-column"&gt;<span style="color: rgba(0, 0, 0, 1)">
                ......
            </span>&lt;/div&gt;
<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) =&gt;<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) =&gt;<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) =&gt;<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 &gt; 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(() =&gt;<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) =&gt;<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>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    本文为博主原创文章,未经博主允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)<br><br>
来源:https://www.cnblogs.com/xiaoyan2017/p/11062316.html
頁: [1]
查看完整版本: react聊天室|react+redux仿微信聊天IM实例|react仿微信界面