前言
上文介绍了数据层SDK和UI组件的开发,大家了解了系统的分层,分别知道各个层如何实现,这章主要和读者一起串联各个模块,介绍里面串联的设计技巧,完整的搭建一个原型聊天系统,感兴趣的同学可以在这基础上扩展。
技术栈准备
- Alibab/RAX 技术栈,开源地址
- Yeoman 使用 自建
generator-rax-component
脚手架构建聊天项目环境 - 数据SDK开发章节封装的
tbms-brandsdk-yunxin
NPM包,提供标准SDK。 - UI组件章节封装的
tbms-components
的NPM包,提供UI组件。
相关技术栈源码
generator-rax-component: https://github.com/ge-tbms/generator-rax-component
tbms-brandsdk-yunxin: https://github.com/ge-tbms/tbms-brandsdk-yunxin
tbms-components: https://github.com/ge-tbms/tbms-components
数据和UI层结合
整体结合思路,通过数据层SDK的标准数据结构和回调,通过 dispatch
和 UI层组件结合起来,在封装消息HOC组件, 实现所见即所得。
数据层 + UI层 => 聊天系统
聊天系统源码地址:
标准SDK使用
初始化SDK
import {createElement, Component, render, findDOMNode} from 'rax';
// 1、数据层沉淀的 `tbms-brandsdk-yunxin` SDK NPM包
import YUNXINSDK from 'tbms-brandsdk-yunxin';
// 2、组件容器
class App extends Component {
constructor() {
super();
// 3、初始化SDK,初始化参数及标准事件回调
this.sdk = new YUNXINSDK({
uid: uid, // 登录用户ID
touid: touid, // 目标用户ID
onmsg: this.onMsg.bind(this), // 收到及时消息回调
onofflinemsg: this.onOfflineMsg.bind(this), // 收到离线消息回调
onerror: this.onError.bind(this), // 错误情况回调
onconversation: this.onConversation.bind(this), // 建立或者更改会话回调
onsystemmsg: this.onSystemMsg.bind(this), // 系统消息回调
onlogin: this.onLogin.bind(this) // 登录信息回调
});
}
}
注释:在组件构造器生命周期中初始化SDK,初始化参数,回调各种标准事件。
编写 onmsg 回调
// 通过scrollTop 设置滚动偏移高度
const SCROLLTOP = 100000;
class App extends Component {
onMsg(msg) {
const MessageList = this.state.MessageList;
componentParser.dispatch(msg, this.conversation).then(ctx => {
const ItemComponent = ctx.ItemComponent;
// 设置页面title
this.titleParse(ctx.message);
// push消息组件
MessageList.push(<ItemComponent {...ctx.message} />);
// 更新节点
this.setState({
MessageList
}, () => {
// 滚动到页面底部
this.horizontalScrollView.scrollTo({y: SCROLLTOP});
})
})
}
}
注释:1. 维护消息队列,通过上文提到解析器模块流转数据,从而在 then
中获取相应的组件。2. 设置标题,Push 消息组件到 MessageList 消息队列中。3. setState更新状态,在更新状态之后把导航栏至底部。
编写 offlinemsg 回调
class App extends Component {
onOfflineMsg(msg) {
const MessageList = this.state.MessageList;
if (_.isObject(msg)) {
componentParser.dispatch(msg, this.conversation).then((ctx) => {
const ItemComponent = ctx.ItemComponent;
// 1、通过unshift顶部队列
MessageList.unshift(<ItemComponent {...ctx.message} />);
// 2、更新节点
this.setState({
MessageList,
}, () => {
// 3、导航栏定位到相应位置
const scrollTop = findDOMNode(this.refs['body']).offsetHeight - this.containerHeight;
this.horizontalScrollView.scrollTo({y: scrollTop});
})
})
} else {
// 4、没有历史消息(离线消息),则为暂无消息
this.setState({
isEmpty: true
})
}
}
}
注释:和 onmsg 差别在于顶部更新消息队列,同时导航栏定位到相应位置。如果没有历史消息(离线消息)则设置为为空表现。
编写 onConversation,onLogin 和 onError 回调
import merge from 'lodash/merge';
import Toast from 'universal-toast';
class App extends Component {
onConversation(conversation) {
// 1、存储会话信息
this.conversation = merge(this.conversation, conversation);
}
onLogin() {
Toast.show('登入成功');
// 2、登入成功,则开始获取历史消息
this.sdk.getHistoryMessage({
scene: 'p2p',
to: this.conversation.touid,
})
}
onError(err) {
// 统一错误信息处理
Toast.show(JSON.stringify(err));
}
}
注释:onLogin 登录成功之后,拉取历史消息
UI层使用
整体UI结构
class App extends Component {
renderPlugin() {
// 1、插件选择,原型聊天实现 emoji 表情插件
if (this.state.pluginVisible === 'emoji') {
return <EmojiPlugin onChange={this.handleEmojiChange} onSend={this.handleSendText} type="ww" />;
} else {
return null;
}
}
render() {
return (
// 2、聊天View容器
<View style={styles.container}>
// 3、消息流滚动组件
<ScrollView
style={styles.containerBody}
ref={(scrollView) => {this.horizontalScrollView = scrollView;}}
>
<View ref="body">
{this.state.MessageList}
</View>
</ScrollView>
// 4、输入框组件
<InputText text={this.state.inputText}
onPluginChange={this.handlePluginChange}
onFocus={this.handleFocus}
onChange={this.handleChangeText}
onSubmit={this.handleSendText}
showPlugin={false}
/>
// 5、插件组件
{this.renderPlugin()}
</View>
);
}
}
注释:整体聊天UI结构主要分为三个区块:消息流区域,输入框区域和插件区域。
插件和输入框组件通过自定义组件包 rax-tbms-chat-plugin
引入。
消息发送
class App extends Component {
handleSendText = (text) => {
const content = (text || this.state.inputText).trim();
// 1、判断内容是否为空
if (content) {
// 2、 发送文本消息
this.sdk.sendMsg({
type: 'text',
content: content
});
this.setState({
inputText: ''
})
} else {
Toast.show('亲,输入内容不能为空哦!')
}
}
}
注释:通过 sdk 封装的 sendMsg 发送消息,同时也可以指定其他数据类型(例如image,card等数据类型)。
结语
这个章节把之前封装的模块进行了串联,总结起来,作者认为做好分层,明确每一层的职能和划分, 基本可以从容面对一个负责的系统。
就像IM聊天,我们把复杂的数据逻辑放在了SDK层,同时接口数据方面做了单元测试来保证稳定性。 在UI方面,拆分了单元化组件,最后再把两者结合起来构建了一个聊天系统。
复盘提升
这是一次完整的项目实践过程,用到了前端各种技术栈和思考模型。同时小册精讲了各个知识点,我们需要辩证的来看各个知识点解决的问题,最重要的是通过项目实践,形成体系化的思考模型。
如下图所示:原先我们是发散性思维模式到现在的体系化思维模式。