taro 多端实践仿微信聊天|taro+react 聊天室 - V2EX
如果想在 V2EX 获得更好的推广效果,欢迎了解 PRO 会员机制:
pro/about

如果你经常使用铜币置顶主题,持有 V2EX Solana Token 会在每日签到时获得额外铜币:
solana
xiaoyan2017

taro 多端实践仿微信聊天|taro+react 聊天室

  •  
  •   xiaoyan2017 Dec 16, 2019 4110 views
    This topic created in 2341 days ago, the information mentioned may be changed or developed.

    Taro 开发三端实例 taro-chatroom 仿微信聊天 App 界面 (H5 + 小程序 + App 端)

    之前有使用uni-app 多端技术开发过仿抖音短视频 /陌陌直播实例,今天给大家分享的是基于 taro+react+redux+rn 等技术开发的仿微信界面聊天项目,基本实现了消息发送、动图表情、图片预览、长按菜单、红包 /朋友圈等功能。

    支持编译到多端 h5/小程序 /app 效果如下:

    技术栈:

    • 编码 /技术:Vscode + react/taro/redux/react-native
    • iconfont 图标:阿里字体图标库
    • 自定义导航栏 Navigation + 底部 Tabbar
    • 弹窗组件:taroPop (基于 Taro 封装自定义模态框)
    • 支持编译:H5 端 + 小程序 + RN 端

    入口页面 app.jsx

    引入公共样式、组件页面及态管理

    /** * @desc Taro 入口页面 app.jsx * @about Q:282310962 wx:xy190310 */ 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')) 

    自定义配置导航栏+tabbar

    自定义导航栏配置非常简单,只需在 window 选项下配置 navigationStyle: 'custom' ,只要不设置 tabbar 参数就可以自定义底部组件了。

    cOnfig= { pages: [ 'pages/auth/login/index', 'pages/auth/register/index', 'pages/index/index', ... ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'TaroChat', navigationBarTextStyle: 'black', navigationStyle: 'custom' } } 

    这里不详细介绍如何实现自定义 Navbar+tabbar,可以去参看:

    taro 自定义 Navigation、tabbar 组件

    taro 自定义弹窗 taroPop 组件

    taro 实现表单登录验证|状态管理|本地存储

    <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.pn')} 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> 

    由于 taro 中 ReactNative 端不支持同步存储,只能使用异步存储实现

    /** * @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' import TouchView from '@components/touchView' import Navigation from '@components/navigation' // 引入自定义弹窗 taroPop import TaroPop from '@components/taroPop' import storage from '@utils/storage' import util from '@utils/util' 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.taroPop let { tel, pwd } = this.state if(!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 }) ... } } // 去注册 GoToRegister = () => { Taro.navigateTo({url: '/pages/auth/register/index'}) } render () { ... } } const mapStateToProps = (state) => { return {...state.auth} } export default connect(mapStateToProps, { ...actions })(Login) 

    对于一些兼容样式,不支持编译到 RN 端,则可通过如下代码包裹实现 /*postcss-pxtransform rn eject enable*/ /*postcss-pxtransform rn eject disable*/

    滚动聊天信息底部

    taro 中实现聊天消息滚动到最底部,由于 RN 端不支持 createSelectorQuery,需另做兼容处理

    // 滚动至聊天底部 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 = this this._timer = setTimeout(() => { that.refs.ScrollViewRN.scrollToEnd({animated: false}) }, t ? 16 : 0) } 
    componentDidMount() { if(process.env.TARO_ENV === 'rn') { this.scrollMsgBottomRN() }else { this.scrollMsgBottom() } } 

    聊天中表情模块则是使用 emoj 表情符,实现起来较简单,这里不介绍了

    ... // 点击聊天消息区域 msgPanelClicked = () => { if(!this.state.showFootToolbar) return this.setState({ showFootToolbar: false }) } // 表情、选择区切换 swtEmojChooseView = (index) => { this.setState({ showFootToolbar: true, showFootViewIndex: index }) } // 底部表情 tab 切换 swtEmojTab = (index) => { let lists = this.state.emotionJson for(var i = 0, len = lists.length; i < len; i++) { lists[i].selected = false } lists[index].selected = true this.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.state let lastCursor = editorLastCursor ? editorLastCursor : editorText.length let startStr = editorText.substr(0, lastCursor) let endStr = editorText.substr(lastCursor) this.setState({ editorText: startStr + `${emoj} ` + endStr }) } ... 

    到这里 taro 聊天项目就基本介绍完了,后续也会继续分享一些实战案例~~

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

    vue 仿微信网页版|vue+web 端聊天室|仿微信客户端 vue 版

    3 replies    2020-01-06 12:52:10 +08:00
    evilhero
        1
    evilhero  
       Dec 16, 2019 via Android
    我就想问问图友和图控还可以进去吗
    gunavy
        2
    gunavy  
       Dec 16, 2019
    taro 算是狗长里不错的东西了
    Whittaker
        3
    Whittaker  
       Jan 6, 2020
    楼主有 demo 么
    About     Help     Advertise     Blog     API     FAQ     Solana     3283 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 80ms UTC 13:43 PVG 21:43 LAX 06:43 JFK 09:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86