# 前言

上文介绍了数据层SDK和UI组件的开发,大家了解了系统的分层,分别知道各个层如何实现,这章主要和读者一起串联各个模块,介绍里面串联的设计技巧,完整的搭建一个原型聊天系统,感兴趣的同学可以在这基础上扩展。

# 技术栈准备

  1. Alibab/RAX 技术栈,开源地址
  2. Yeoman 使用 自建 generator-rax-component 脚手架构建聊天项目环境
  3. 数据SDK开发章节封装的 tbms-brandsdk-yunxin NPM包,提供标准SDK。
  4. UI组件章节封装的 tbms-components 的NPM包,提供UI组件。

# 相关技术栈源码

generator-rax-component: github.com/ge-tbms/gen…

tbms-brandsdk-yunxin: github.com/ge-tbms/tbm…

tbms-components: github.com/ge-tbms/tbm…

# 数据和UI层结合

整体结合思路,通过数据层SDK的标准数据结构和回调,通过 dispatch 和 UI层组件结合起来,在封装消息HOC组件, 实现所见即所得。

数据层 + UI层 => 聊天系统

image-20210214204409184

聊天系统源码地址:

github.com/ge-tbms/tbm…

# 标准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方面,拆分了单元化组件,最后再把两者结合起来构建了一个聊天系统。

# 复盘提升

这是一次完整的项目实践过程,用到了前端各种技术栈和思考模型。同时小册精讲了各个知识点,我们需要辩证的来看各个知识点解决的问题,最重要的是通过项目实践,形成体系化的思考模型。

如下图所示:原先我们是发散性思维模式到现在的体系化思维模式。

image-20210214204431618

阅读全文