TaroChatroom多端聊天室是基於taro+react+redux+reactNative+taroPop等技術開發的仿微信App聊天室例項,支援編譯到多端(h5+小程式+RN端)。
這是第二個多端實踐專案,不過這次使用的是react技術,上次分享的uniapp+vue仿抖音短視訊/陌陌直播聊天室是基於vue技術開發的多端專案。
基於vue+uniapp直播專案|uni-app仿抖音/陌陌直播室
- 編碼器/技術:Vscode + react/taro/redux/react-native
- 字型圖示:阿里iconfont字型圖示庫
- 自定義導航欄 + 底部Tabbar
- 彈窗元件:taroPop(Taro封裝自定義Modal框)
- 支援編譯:H5端 + 小程式 + app端
/**
* @desc Taro入口頁面 app.jsx
*/
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均採用自定義元件模式,由於之前有分享文章,這裡不詳細介紹了。
Taro自定義Modal對話方塊元件|taro仿微信、android彈窗
在taro中獲取多個表單值,方法還是比較簡單的,像下面的方法就能簡單獲取多個input值了
<Input placeholder="請輸入手機號/暱稱" onInput={this.handleInput.bind(this, 'tel')} />
<Input placeholder="請輸入密碼" password onInput={this.handleInput.bind(this, 'pwd')} />
this.state = {
tel: '',
pwd: '',
}
handleInput = (key, e) => {
this.setState({ [key]: e.detail.value })
}
下面就詳細展示如何獲取表單並驗證、本地儲存處理
return (
<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.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
})
...
}
}
render () {
...
}
}
const mapStateToProps = (state) => {
return {...state.auth}
}
export default connect(mapStateToProps, {
...actions
})(Login)
在開發中需要注意官網提供的api支援性,由於taro中rn端不支援同步儲存,只能改為setStorageSync非同步儲存
/**
* @desc Taro本地儲存
*/
import Taro from '@tarojs/taro'
export default class Storage {
static get(key) {
return Taro.getStorage({ key }).then(res => res.data).catch(() => '')
}
static set(key, data){
return Taro.setStorage({key: key, data: data}).then(res => res)
}
static del(key){
Taro.removeStorage({key: key}).then(res => res)
}
static clear(){
Taro.clearStorage()
}
}
在開發中對於一些不相容rn端樣式,不希望編譯到rn端,則可通過如下程式碼包裹即可
/*postcss-pxtransform rn eject enable*/
/*postcss-pxtransform rn eject disable*/
下面整理了一些rn端相容樣式處理,對於不相容樣式,可通過@mixins統一處理下
/**
* 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*/
}
在taro中實現聊天資訊滾動到最底部,在H5/小程式端則可通過獲取createSelectorQuery 來實現滾動到聊天底部,由於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()
}
}
另外在開發中需要特別注意rn端flex佈局和h5、小程式端差異。。
constructor(props) {
super(props)
this.state = {
scrollTop: 0,
showFootToolbar: false,
showFootViewIndex: 0,
editorText: '',
editorLastCursor: 0,
// 訊息記錄
msgJson: msgList,
// 表情json
emotionJson: emotionList,
// 預覽圖片陣列
previewImgArray: [],
}
}
componentDidMount() {
if(process.env.TARO_ENV === 'rn') {
this.scrollMsgBottomRN()
}else {
this.scrollMsgBottom()
}
}
...
// 點選聊天訊息區域
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開發聊天室例項分享介紹,希望能有點點幫助~~??
最後分享兩個最近例項專案,希望能喜歡。