青行 發表於 2019-9-1 03:13:00

react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

<h3 id="一项目简介">一、前言</h3>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">9月,又到开学的季节。为每个一直默默努力的自己点赞!最近都沉浸在react native原生app开发中,之前也有使用vue/react/angular等技术开发过聊天室项目,另外还使用RN技术做了个自定义模态弹窗rnPop组件。</span></p>
<h3 id="一项目简介">一、项目简述</h3>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif; background-color: rgba(197, 226, 255, 1)">基于react+react-native+react-navigation+react-redux+react-native-swiper+rnPop等技术开发的仿微信原生App界面聊天室——RN_ChatRoom,实现了原生app启动页、AsyncStorage本地存储登录拦截、集成rnPop模态框功能(仿微信popupWindow弹窗菜单)、消息触摸列表、发送消息、表情(动图),图片预览,拍摄图片、发红包、仿微信朋友圈等功能。</span></p>
<h3 id="三技术栈">二、技术点</h3>
<ul>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">MVVM框架:react / react-native / react-native-cli</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">状态管理:react-redux / redux</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">页面导航:react-navigation</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">rn弹窗组件:rnPop</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">打包工具:webpack 2.0</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">轮播组件:react-native-swiper</span></li>
<li><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">图片/相册:react-native-image-picker</span></li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"name": "RN_ChatRoom"<span style="color: rgba(0, 0, 0, 1)">,
</span>"version": "0.0.1"<span style="color: rgba(0, 0, 0, 1)">,
</span>"aboutMe": "QQ:282310962 、 wx:xy190310"<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-native": "0.60.4"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"devDependencies"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"@babel/core": "^7.5.5"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"@babel/runtime": "^7.5.5"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"@react-native-community/async-storage": "^1.6.1"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"@react-native-community/eslint-config": "^0.0.5"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"babel-jest": "^24.8.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"eslint": "^6.1.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"jest": "^24.8.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"metro-react-native-babel-preset": "^0.55.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-native-gesture-handler": "^1.3.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-native-image-picker": "^1.0.2"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-native-swiper": "^1.5.14"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-navigation": "^3.11.1"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-redux": "^7.1.0"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"react-test-renderer": "16.8.6"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"redux": "^4.0.4"<span style="color: rgba(0, 0, 0, 1)">,
    </span>"redux-thunk": "^2.3.0"<span style="color: rgba(0, 0, 0, 1)">
},
</span>"jest"<span style="color: rgba(0, 0, 0, 1)">: {
    </span>"preset": "react-native"<span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020226604-1636401225.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020329292-576645984.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020337206-971510651.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020400853-1080167912.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020407817-1408915175.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020419228-902414874.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020443778-1400722731.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020505355-58622143.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020517165-922667486.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020536711-1169838086.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020550609-1803325661.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020609634-564843761.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020714477-1190976121.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020645007-2045524134.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020700887-1344354569.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901020706443-1812194592.png"></p>
<p><strong>◆ App全屏幕启动页splash模板</strong></p>
<p><span style="font-size: 12px">react-native如何全屏启动? 设置StatusBar顶部条背景为透明 translucent={true},并配合RN动画Animated</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* @desc 启动页面
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">

import React, { Component } from </span>'react'<span style="color: rgba(0, 0, 0, 1)">
import { StatusBar, Animated, View, Text, Image } from </span>'react-native'<span style="color: rgba(0, 0, 0, 1)">

export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class Splash 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)"> {
            animFadeIn: </span><span style="color: rgba(0, 0, 255, 1)">new</span> Animated.Value(0<span style="color: rgba(0, 0, 0, 1)">),
            animFadeOut: </span><span style="color: rgba(0, 0, 255, 1)">new</span> Animated.Value(1<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;Animated.View style={}&gt;
                &lt;StatusBar backgroundColor='transparent' barStyle='light-content' translucent={<span style="color: rgba(0, 0, 255, 1)">true</span>} /&gt;

                &lt;View style={GStyle.flex1_a_j}&gt;
                  &lt;Image source={require('../assets/img/ic_default.jpg')} style={{borderRadius: 100, width: 100, height: 100}} /&gt;
                &lt;/View&gt;
                &lt;View style={}&gt;
                  &lt;Text style={{color: '#dbdbdb', fontSize: 12, textAlign: 'center',}}&gt;RN-ChatRoom v1.0.0&lt;/Text&gt;
                &lt;/View&gt;
            &lt;/Animated.View&gt;
<span style="color: rgba(0, 0, 0, 1)">      )
    }

    componentDidMount(){
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断是否登录</span>
      storage.get('hasLogin', (err, object) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            setTimeout(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                Animated.timing(
                  </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.animFadeOut, {duration: 300, toValue: 0<span style="color: rgba(0, 0, 0, 1)">}
                ).start(()</span>=&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>
                  util.navigationReset(<span style="color: rgba(0, 0, 255, 1)">this</span>.props.navigation, (!err &amp;&amp; object &amp;&amp; object.hasLogin) ? 'Index' : 'Login'<span style="color: rgba(0, 0, 0, 1)">)
                })
            }, </span>1500<span style="color: rgba(0, 0, 0, 1)">);
      })
    }
}</span></pre>
</div>
<p><strong>◆ RN本地存储技术async-storage</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 本地存储函数
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">

import AsyncStorage from </span>'@react-native-community/async-storage'<span style="color: rgba(0, 0, 0, 1)">

export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class Storage{
    static get(key, callback){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> AsyncStorage.getItem(key, (err, object) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            callback(err, JSON.parse(object))
      })
    }

    static set(key, data, callback){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> AsyncStorage.setItem(key, JSON.stringify(data), callback)
    }

    static del(key){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> AsyncStorage.removeItem(key)
    }

    static clear(){
      AsyncStorage.clear()
    }
}

global.storage </span>= Storage</pre>
</div>
<p><span style="font-size: 12px">声明全局global变量,只需在App.js页面一次引入、多个页面均可调用。</span></p>
<p><span style="font-size: 12px; color: rgba(0, 0, 0, 1); background-color: rgba(224, 224, 224, 1)">storage.set('hasLogin', { hasLogin: true })</span><br><span style="font-size: 12px; color: rgba(0, 0, 0, 1); background-color: rgba(224, 224, 224, 1)">storage.get('hasLogin', (err, object) =&gt; { ... })</span></p>
<p><strong>◆ App主页面模板及全局引入组件</strong></p>
<div class="cnblogs_code">
<pre>import React, { Fragment, Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">
import { StatusBar } from </span>'react-native'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入公共js</span>
import './src/utils/util'<span style="color: rgba(0, 0, 0, 1)">
import </span>'./src/utils/storage'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入样式</span>
import './src/assets/css/common'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导入rnPop弹窗</span>
import './src/assets/js/rnPop/rnPop.js'

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入页面路由</span>
import PageRouter from './src/router'<span style="color: rgba(0, 0, 0, 1)">

class App extends Component{
render(){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;Fragment&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)"> &lt;StatusBar backgroundColor={GStyle.headerBackgroundColor} barStyle='light-content' /&gt; </span><span style="color: rgba(0, 128, 0, 1)">*/</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><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">}
      </span>&lt;PageRouter /&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;RNPop /&gt;
      &lt;/Fragment&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> App</pre>
</div>
<p><strong>◆ react-navigation页面导航器/地址路由、底部tabbar</strong></p>
<p><span style="font-size: 12px">由于react-navigation官方顶部导航器不能满足需求,如是自己封装了一个,功能效果有些类似微信导航。</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901023717223-800709554.png"></p>
<div class="cnblogs_code">
<pre>export <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class HeaderBar 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)"> {
            searchInput: </span>''<span style="color: rgba(0, 0, 0, 1)">
      }
    }

    render() {
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
         * 更新
         * @param { navigation | 页面导航 }
         * @param { title | 标题 }
         * @param { center | 标题是否居中 }
         * @param { search | 是否显示搜索 }
         * @param { headerRight | 右侧Icon按钮 }
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
      let{ navigation, title, bg, center, search, headerRight } </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props

      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
            </span>&lt;View style={GStyle.flex_col}&gt;
                &lt;StatusBar backgroundColor={bg ? bg : GStyle.headerBackgroundColor} barStyle='light-content' translucent={<span style="color: rgba(0, 0, 255, 1)">true</span>} /&gt;
                &lt;View style={}&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;TouchableOpacity style={} activeOpacity={.5} onPress={<span style="color: rgba(0, 0, 255, 1)">this</span>.goBack}&gt;&lt;Text style={}&gt;&amp;#xe63f;&lt;/Text&gt;&lt;/TouchableOpacity&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>!search &amp;&amp; center ? &lt;View style={GStyle.flex1} /&gt; : null }
<span style="color: rgba(0, 0, 0, 1)">                  {
                        search </span>?<span style="color: rgba(0, 0, 0, 1)">
                        (
                            </span>&lt;View style={}&gt;
                              &lt;TextInput onChangeText={text=&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.setState({searchInput: text})}} style={styles.barSearchText} placeholder='搜索' placeholderTextColor='rgba(255,255,255,.6)' /&gt;
                            &lt;/View&gt;
<span style="color: rgba(0, 0, 0, 1)">                        )
                        :
                        (
                            </span>&lt;View style={}&gt;<span style="color: rgba(0, 0, 0, 1)">
                              { title </span>? &lt;Text style={}&gt;{title}&lt;/Text&gt; : null }
                            &lt;/View&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;View style={}&gt;<span style="color: rgba(0, 0, 0, 1)">
                        {
                            </span>!headerRight ? <span style="color: rgba(0, 0, 255, 1)">null</span> : headerRight.map((item, index) =&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)">(
                                    </span>&lt;TouchableOpacity style={} activeOpacity={.5} key={index} onPress={()=&gt;item.press ? item.press(<span style="color: rgba(0, 0, 255, 1)">this</span>.state.searchInput) : <span style="color: rgba(0, 0, 255, 1)">null</span>}&gt;<span style="color: rgba(0, 0, 0, 1)">
                                        {
                                          item.type </span>=== 'iconfont' ?<span style="color: rgba(0, 0, 0, 1)"> item.title : (
                                                </span><span style="color: rgba(0, 0, 255, 1)">typeof</span> item.title === 'string' ?
                                                &lt;Text style={item.style ? item.style : <span style="color: rgba(0, 0, 255, 1)">null</span>}&gt;{`${item.title}`}&lt;/Text&gt;
<span style="color: rgba(0, 0, 0, 1)">                                                :
                                                </span>&lt;Image source={item.title} style={{width: 24, height: 24, resizeMode: 'contain'}} /&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)">}
                                        { item.badge </span>? &lt;View style={}&gt;&lt;Text style={GStyle.badge_text}&gt;{item.badge}&lt;/Text&gt;&lt;/View&gt; : <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)"> }
                                        { item.badgeDot </span>? &lt;View style={}&gt;&lt;/View&gt; : null }
                                    &lt;/TouchableOpacity&gt;
<span style="color: rgba(0, 0, 0, 1)">                              )
                            })
                        }
                  </span>&lt;/View&gt;
                &lt;/View&gt;
            &lt;/View&gt;
<span style="color: rgba(0, 0, 0, 1)">      )
    }

    goBack </span>= () =&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)">.props.navigation.goBack()
    }
}</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建底部TabBar</span>
const tabNavigator =<span style="color: rgba(0, 0, 0, 1)"> createBottomTabNavigator(
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tabbar路由(消息、通讯录、我)</span>
<span style="color: rgba(0, 0, 0, 1)">    {
      Index: {
            screen: Index,
            navigationOptions: ({navigation}) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> ({
                tabBarLabel: </span>'消息'<span style="color: rgba(0, 0, 0, 1)">,
                tabBarIcon: ({focused, tintColor}) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;View&gt;
                        &lt;Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}&gt;&amp;#xe642;&lt;/Text&gt;
                        &lt;View style={}&gt;&lt;Text style={GStyle.badge_text}&gt;12&lt;/Text&gt;&lt;/View&gt;
                  &lt;/View&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            })
      },
      Contact: {
            screen: Contact,
            navigationOptions: {
                tabBarLabel: </span>'通讯录'<span style="color: rgba(0, 0, 0, 1)">,
                tabBarIcon: ({focused, tintColor}) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;View&gt;
                        &lt;Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}&gt;&amp;#xe640;&lt;/Text&gt;
                  &lt;/View&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            }
      },
      Ucenter: {
            screen: Ucenter,
            navigationOptions: {
                tabBarLabel: </span>'我'<span style="color: rgba(0, 0, 0, 1)">,
                tabBarIcon: ({focused, tintColor}) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                  </span>&lt;View&gt;
                        &lt;Text style={[ GStyle.iconfont, GStyle.fs_20, {color: (focused ? tintColor : '#999')} ]}&gt;&amp;#xe61e;&lt;/Text&gt;
                        &lt;View style={}&gt;&lt;/View&gt;
                  &lt;/View&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)"> tabbar配置</span>
<span style="color: rgba(0, 0, 0, 1)">    {
      ...
    }
)</span></pre>
</div>
<p><strong>◆ RN聊天页面功能模块</strong></p>
<p><span style="font-size: 12px">1、表情处理:原本是想着使用图片表情gif,可是在RN里面textInput文本框不能插入图片,只能通过定义一些特殊字符 :66: (:12 [奋斗] 解析表情,处理起来有些麻烦,而且图片多了影响性能,如是就改用emoj表情符。</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901024905606-200901246.png">&nbsp;<img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901024942836-332295079.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">faceList: [
    {
      nodes: [
            </span>'🙂','😁','😋','😎','😍','😘','😗'<span style="color: rgba(0, 0, 0, 1)">,
            </span>'😃','😂','🤣','😅','😉','😊','🤗'<span style="color: rgba(0, 0, 0, 1)">,
            </span>'🤔','😐','😑','😶','🙄','😏','del'<span style="color: rgba(0, 0, 0, 1)">,
      ]
    },
    ...
    {
      nodes: [
            </span>'👓','👄','💋','👕','👙','👜','👠'<span style="color: rgba(0, 0, 0, 1)">,
            </span>'👑','🎓','💄','💍','🌂','👧🏼','👨🏼'<span style="color: rgba(0, 0, 0, 1)">,
            </span>'👵🏻','👴🏻','👨‍🌾','👨🏼‍🍳','👩🏻‍🍳','👨🏽‍✈️','del'<span style="color: rgba(0, 0, 0, 1)">,
      ]
    },
    ...
]</span></pre>
</div>
<p><span style="font-size: 12px">2、光标定位:在指定光标处插入内容,textInput提供了光标起始位置</span></p>
<p><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">let selection = this.textInput._lastNativeSelection || null;</span><br><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">this.textInput.setNativeProps({</span><br><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">          selection : { start : xxx, end : xxx}</span><br><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">})</span></p>
<p><span style="font-size: 12px">3、textInput判断内容是否为空,过滤空格、回车</span></p>
<p><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">isEmpty = (html) =&gt; {</span><br><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">          return html.replace(/\r\n|\n|\r/, "").replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, "") == ""</span><br><span style="font-size: 12px; background-color: rgba(224, 224, 224, 1)">}</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 聊天模块JS----------------------------------------------------
</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>
scrollToBottom = (t) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    let that </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>._timer = setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      that.refs.scrollView.scrollToEnd({animated: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">})
    }, t </span>? 16 : 0<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>
hideKeyboard = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    Keyboard </span>&amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> Keyboard.dismiss()
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 点击表情</span>
handlePressEmotion = (img) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(img === 'del') <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">

    let selection </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.editorInput._lastNativeSelection || <span style="color: rgba(0, 0, 255, 1)">null</span><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, 0, 1)">selection){
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
            editorText : </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state.editorText +<span style="color: rgba(0, 0, 0, 1)"> `${img}`,
            lastRange: </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.editorText.length
      })
    }
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      let startStr </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.state.editorText.substr(0 , <span style="color: rgba(0, 0, 255, 1)">this</span>.state.lastRange ? <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.lastRange : selection.start)
      let endStr </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.state.editorText.substr(<span style="color: rgba(0, 0, 255, 1)">this</span>.state.lastRange ? <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.lastRange : selection.end)
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
            editorText : startStr </span>+ `${img}` +<span style="color: rgba(0, 0, 0, 1)"> endStr,
            lastRange: (startStr </span>+<span style="color: rgba(0, 0, 0, 1)"> `${img}`).length
      })
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送消息</span>
isEmpty = (html) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> html.replace(/\r\n|\n|\r/, "").replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, "") == ""<span style="color: rgba(0, 0, 0, 1)">
}
handleSubmit </span>= () =&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, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">this</span>.isEmpty(<span style="color: rgba(0, 0, 255, 1)">this</span>.state.editorText)) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">

    let _msg </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.__messageJson
    let _len </span>=<span style="color: rgba(0, 0, 0, 1)"> _msg.length
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 消息队列</span>
    let _data =<span style="color: rgba(0, 0, 0, 1)"> {
      id: `msg${</span>++<span style="color: rgba(0, 0, 0, 1)">_len}`,
      msgtype: </span>3<span style="color: rgba(0, 0, 0, 1)">,
      isme: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
      avator: require(</span>'../../../assets/img/uimg/u__chat_img11.jpg'<span style="color: rgba(0, 0, 0, 1)">),
      author: </span>'王梅(Fine)'<span style="color: rgba(0, 0, 0, 1)">,
      msg: </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.editorText,
      imgsrc: </span>''<span style="color: rgba(0, 0, 0, 1)">,
      videosrc: </span>''<span style="color: rgba(0, 0, 0, 1)">
    }
    _msg </span>=<span style="color: rgba(0, 0, 0, 1)"> _msg.concat(_data)
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({ __messageJson: _msg, editorText: ''<span style="color: rgba(0, 0, 0, 1)"> })

    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.scrollToBottom(<span style="color: rgba(0, 0, 255, 1)">true</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)"> &gt;&gt;&gt; 【选择区功能模块】------------------------------------------</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 选择图片</span>
handleLaunchImage = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    let that </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">

    ImagePicker.launchImageLibrary({
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> title: '请选择图片来源',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> cancelButtonTitle: '取消',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> takePhotoButtonTitle: '拍照',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> chooseFromLibraryButtonTitle: '相册图片',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> customButtons: [</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">   {name: 'baidu', title: 'baidu.com图片'},</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, 128, 0, 1)"> cameraType: 'back',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> mediaType: 'photo',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> videoQuality: 'high',</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> maxWidth: 300,</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> maxHeight: 300,</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> quality: .8,</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> noData: true,</span>
<span style="color: rgba(0, 0, 0, 1)">      storageOptions: {
            skipBackup: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
      },
    }, (response) </span>=&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)"> console.log(response)</span>

      <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(response.didCancel){
            console.log(</span>'user cancelled'<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, 0, 1)">(response.error){
            console.log(</span>'ImagePicker Error'<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)">{
            let source </span>=<span style="color: rgba(0, 0, 0, 1)"> { uri: response.uri }
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> let source = {uri: 'data:image/jpeg;base64,' + response.data}</span>
<span style="color: rgba(0, 0, 0, 1)">            that.setState({ imgsrc: source })

            that.scrollToBottom(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
      }
    })
}</span></pre>
</div>
<p><strong>◆ 附上vue/react/angular聊天室IM实例</strong></p>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">Vue网页版聊天室:https://www.cnblogs.com/xiaoyan2017/p/10793728.html<br></span></p>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">React版聊天室:https://www.cnblogs.com/xiaoyan2017/p/11106246.html<br></span></p>
<p><span style="font-size: 12px; font-family: &quot;comic sans ms&quot;, sans-serif">Angular聊天室:https://www.cnblogs.com/xiaoyan2017/p/11194828.html</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1289798/201909/1289798-20190901031003881-1407468291.jpg"></p>
<p>&nbsp;</p>

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