基于Taro+react+redux+RN+taroPop等技术开发的跨端聊天App实例,支持编译到多端H5+小程序+RN端,界面仿制微信聊天界面,实现了消息发送、表情、图片预览、长按菜单、红包、朋友圈等功能。
Taro三端统一聊天应用:taro-chatroom (仿微信界面聊天App)
技术栈:
- 编码/技术:Vscode / react+taro+react-redux+ReactNative
- 字体图标:阿里iconfont字体图标库
- 自定义顶部导航条 + 底部Tabbar
- 弹窗组件:taroPop(taro封装自定义模态框)
- 支持编译:H5端 + 小程序 + App端
为了展示更丰富的导航条功能,顶部导航栏和底部Tabbar均采用自定义模式,且测试兼容三端。
具体介绍可看看这篇:Taro+react自定义顶部导航 + tabbar菜单
另外项目中用到的弹窗效果也是基于taro自定义Modal实现,可参看:taro自定义对话框
入口页面App.jsx
项目中使用redux进行状态管理,具体用法和react里面一样。
yarn add redux redux-thunk @tarojs/redux
import Taro, { Component } from '@tarojs/taro'
import Index from './pages/index'// 引入状态管理redux
import { Provider } from '@tarojs/redux'
import { store } from './store'// 引入样式
import './app.scss'
import './styles/fonts/iconfont.css'
import './styles/reset.scss'class App extends Component {config = {pages: ['pages/auth/login/index','pages/auth/register/index','pages/index/index',...],window: {backgroundTextStyle: 'light',navigationBarBackgroundColor: '#fff',navigationBarTitleText: 'TaroChat',navigationBarTextStyle: 'black',navigationStyle: 'custom'}}// 在 App 类中的 render() 函数没有实际作用// 请勿修改此函数render () {return (<Provider store={store}><Index /></Provider>)}
}Taro.render(<App />, document.getElementById('app'))
登录/注册验证模块
taro中表单操作、redux状态管理及本地存储实现
<View className="taro__container flexDC bg-eef1f5"><Navigation background='#eef1f5' fixed /><ScrollView className="taro__scrollview flex1" scrollY><View className="auth-lgreg">{/* logo */}<View className="auth-lgreg__slogan"><View className="auth-lgreg__slogan-logo"><Image className="auth-lgreg__slogan-logo__img" src={require('../../../assets/taro.png')} mode="aspectFit" /></View><Text className="auth-lgreg__slogan-text">欢迎来到Taro-Chatroom</Text></View>{/* 表单 */}<View className="auth-lgreg__forms"><View className="auth-lgreg__forms-wrap"><View className="auth-lgreg__forms-item"><Input className="auth-lgreg__forms-iptxt flex1" placeholder="请输入手机号/昵称" onInput={this.handleInput.bind(this, 'tel')} /></View><View className="auth-lgreg__forms-item"><Input className="auth-lgreg__forms-iptxt flex1" placeholder="请输入密码" password onInput={this.handleInput.bind(this, 'pwd')} /></View></View><View className="auth-lgreg__forms-action"><TouchView onClick={this.handleSubmit}><Text className="auth-lgreg__forms-action__btn">登录</Text></TouchView></View><View className="auth-lgreg__forms-link"><Text className="auth-lgreg__forms-link__nav">忘记密码</Text><Text className="auth-lgreg__forms-link__nav" onClick={this.GoToRegister}>注册账号</Text></View></View></View></ScrollView><TaroPop ref="taroPop" />
</View>
/*** @tpl 登录模块*/import Taro from '@tarojs/taro'
import { View, Text, ScrollView, Image, Input, Button } from '@tarojs/components'import './index.scss'import { connect } from '@tarojs/redux'
import * as actions from '../../../store/action'...class Login extends Taro.Component {config = {navigationBarTitleText: '登录'}constructor(props) {super(props)this.state = {tel: '',pwd: '',}}componentWillMount() {// 判断是否登录storage.get('hasLogin').then(res => {if(res && res.hasLogin) {Taro.navigateTo({url: '/pages/index/index'})}})}// 提交表单handleSubmit = () => {let taroPop = this.refs.taroPoplet { tel, pwd } = this.stateif(!tel) {taroPop.show({content: '手机号不能为空', time: 2})}else if(!util.checkTel(tel)) {taroPop.show({content: '手机号格式有误', time: 2})}else if(!pwd) {taroPop.show({content: '密码不能为空', time: 2})}else {// ...接口数据...storage.set('hasLogin', { hasLogin: true })storage.set('user', { username: tel })storage.set('token', { token: util.setToken() })taroPop.show({skin: 'toast',content: '登录成功',icon: 'success',time: 2})...}}render () {...}
}const mapStateToProps = (state) => {return {...state.auth}
}export default connect(mapStateToProps, {...actions
})(Login)
项目开发中,尤其需要注意RN端样式问题,对于一些兼容处理,不希望编译到RN端,则可通过如下实现
/*postcss-pxtransform rn eject enable*//*postcss-pxtransform rn eject disable*/
/*** RN 不支持针对某一边设置 style,即 border-bottom-style 会报错* 那么 border-bottom: 1px 就需要写成如下形式: border: 0 style color; border-bottom-width: 1px;*/
@mixin border($dir, $width, $style, $color) {border: 0 $style $color;@each $d in $dir {#{border-#{$d}-width}: $width;}
}/*** NOTE RN 无法通过 text-overflow 实现省略号,这些代码不会编译到 RN 中*/
@mixin ellipsis {/*postcss-pxtransform rn eject enable*/overflow: hidden; white-space: nowrap; text-overflow: ellipsis;/*postcss-pxtransform rn eject disable*/
}/*** NOTE 实现多行文本省略,RN 用 Text 标签的 numberOfLines={2},H5/小程序用 -webkit-line-clamp*/
@mixin clamp($line) {/*postcss-pxtransform rn eject enable*/display: -webkit-box;overflow: hidden;-webkit-line-clamp:$line;/* autoprefixer: ignore next */-webkit-box-orient: vertical;/*postcss-pxtransform rn eject disable*/
}/*** 对于不能打包到 RN 的样式,可以用 postcss 方式引入*/@mixin eject($attr, $value) {/*postcss-pxtransform rn eject enable*/#{$attr}: $value;/*postcss-pxtransform rn eject disable*/
}
聊天时发送消息滚动到最底部,则需要进行兼容处理,因为ReactNative端不支持createSelectorQuery
componentDidMount() {if(process.env.TARO_ENV === 'rn') {this.scrollMsgBottomRN()}else {this.scrollMsgBottom()}
}
// 滚动至聊天底部
scrollMsgBottom = () => {let query = Taro.createSelectorQuery()query.select('#scrollview').boundingClientRect()query.select('#msglistview').boundingClientRect()query.exec((res) => {// console.log(res)if(res[1].height > res[0].height) {this.setState({ scrollTop: res[1].height - res[0].height })}})
}
scrollMsgBottomRN = (t) => {let that = thisthis._timer = setTimeout(() => {that.refs.ScrollViewRN.scrollToEnd({animated: false})}, t ? 16 : 0)
}
另外聊天表情使用emoj表情符,这个实现比较简单,就不介绍了。
...// 点击聊天消息区域
msgPanelClicked = () => {if(!this.state.showFootToolbar) returnthis.setState({ showFootToolbar: false })
}// 表情、选择区切换
swtEmojChooseView = (index) => {this.setState({ showFootToolbar: true, showFootViewIndex: index })
}// 底部表情tab切换
swtEmojTab = (index) => {let lists = this.state.emotionJsonfor(var i = 0, len = lists.length; i < len; i++) {lists[i].selected = false}lists[index].selected = truethis.setState({ emotionJson: lists })
}/* >>> 【编辑器/表情处理模块】------------------------------------- */
bindEditorInput = (e) => {this.setState({editorText: e.detail.value,editorLastCursor: e.detail.cursor})
}
bindEditorFocus = (e) => {this.setState({ editorLastCursor: e.detail.cursor })
}
bindEditorBlur = (e) => {this.setState({ editorLastCursor: e.detail.cursor })
}handleEmotionTaped = (emoj) => {if(emoj == 'del') return// 在光标处插入表情let { editorText, editorLastCursor } = this.statelet lastCursor = editorLastCursor ? editorLastCursor : editorText.lengthlet startStr = editorText.substr(0, lastCursor)let endStr = editorText.substr(lastCursor)this.setState({editorText: startStr + `${emoj} ` + endStr})
}...
好了,到这里taro开发聊天app的分享介绍就差不多了,希望能有些帮助!!
最后附上两个基于vue开发的实例项目
vue网页端聊天:https://blog.csdn.net/yanxinyun1990/article/details/89735778
vue+uniapp仿抖音小视频:https://blog.csdn.net/yanxinyun1990/article/details/103012086