前后端分离实践分页请求历史聊天消息

Posted 全栈技术部

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前后端分离实践分页请求历史聊天消息相关的知识,希望对你有一定的参考价值。

分页加载原因

在一个系统数据很大,在接口数据交互,海量数据查询,服务器接口返回的数据不可能一次性返回。

数据量大,从数据库一次性查询,再到网络传输是要花费更多的时间客户端才能响应拿到数据进行 UI 界面渲染。

从接口拿到大量数据渲染,Web 端会造成界面卡顿,移动端处理大量数据,会出现 OOM。

所以获取数据可以通过分页加载的方式处理数据和UI交互。这样解决性能问题,让 UED 效果更好一点。

通常前端可以通过上拉刷新、下拉加载更多等方式。

解决当前聊天问题

  • 在单聊,会获取聊天历史记录,分页拉取

    • 获取聊天历史记录,其实就是一个下拉加载更多数据的操作
  • 更新聊天记录列表

    • 方法一、从新调用获取

    • 方法二、在接收到消息到时候更新

    • 方法三、查找现有列表,获取在列表位置,根据更新对应字段

解决方案监听

  • 监听滚动鼠标滚轮或滚动事件
  • 使用第三方模块 pullrefreshjs
  • ...

添加正在加载

修改 pages/Im/chat/index.jsx,在请求的时候才显示 Spin 组件,良好提示。

...
<div className={styles['chat-container']}>
  <div id="chatItems" className={styles['chat-items']}>
    {loading && (
      <div className={styles['chat-loading']}>
        <Spin />
      </div>
    )}
    
    ...

loading 属性可以从 chat  model 中获取,dva effects 封装了在发起请求都会维护一个 models 属性

export default connect(({ user, chat, loading }) => ({
  user,
  chat,
  loading: loading.models.chat, // if user ==> loading.models.user
}))(Chat);

监听滚动鼠标滚轮

监听聊天容器鼠标滚动

const onChatItemScroll = (e) => {
  
  // hasMore 标示加载最后一次,已经没有更多数据了
  // loading 表示正在加载中,不再加载,等上一次请求完成才能发起请求,避免请求过快
    if (!hasMore || loading) return;

  // 鼠标滚轮是否滚动
  
  // (e.wheelDelta > 0) 向上滚动
  // 否则向下滚动
    if (e.wheelDelta > 0) {
      
      let chatItems = document.getElementById('chatItems');
      // 聊天容器滚动至顶部的时候才发起请求
      if (chatItems && chatItems.scrollTop === 0) {
        // 获取当前聊天对象聊天记录
        let chatId = getDiffChatId(chatUserInfo, userId);
        // 发起请求 -->. dispatch model
        dispatch({ type'chat/getMessageChatList'payload: { chatId, pageNum: pageNum + 1 } });
      } else {
        console.log('follow @全栈技术部 thank');
      }
    }
  };

const addChatItemScrollListener = () => {
    let chatItems = document.getElementById('chatItems');
    chatItems && chatItems.addEventListener('mousewheel', onChatItemScroll, false);
  };

  const removeChatItemScrollListener = () => {
    let chatItems = document.getElementById('chatItems');
    chatItems && chatItems.removeEventListener('mousewheel', onChatItemScroll, false);
  };

  useEffect(() => {
    if (!receiveId) {
      history.replace({ pathname'/im' });
      return;
    }
    
    // 添加事件
    addChatItemScrollListener();
    return () => {
    // 移除事件
      removeChatItemScrollListener();
    };
    
    // 当以下属性发生变化,事件监听都需要获取当前最新的属性数据
  }, [pageNum, hasMore, loading]);

mousewheel 事件存在 浏览器兼容性问题

Chat  Model 逻辑处理

添加状态

state {
...
  // 请求页码
    pageNum1,

    // 标示 是否还有更多数据
    hasMoretrue
...
},
  
  effects{
    ...
  /**
     * 获取聊天请求
     */

    *getMessageChatList({ payload }, { call, put, select }) {
      const { chatId, pageNum } = payload;

      const chatState = yield select((state) => state['chat']);
      const { messageChatList = [] } = chatState;

      const response = yield call(getMessageChatList, { chatId, pageNum });
      if (response.code === 200) {
        
        // 响应数据
        const lists = (response.data && response.data.lists) || [];
        
        // 这个数据根据接口返回
        // 数据反转,无需遍历添加
        lists.reverse();

        // 加载下一页数据,继续往聊天记录中添加 concat
        let messageList = pageNum !== 1 ? lists.concat(messageChatList) : lists;
        yield put({
          type'updateMessageChatList',
          
          // dispatch 传递参数
          // 请求最后一次返回 lists 字段为空,表示没有很多数据了
          payload: { messageChatList: messageList, pageNum: pageNum, hasMore: lists.length > 0 },
        });
      }
    ...
}

发送消息更新聊天记录

*sendMessage({ payload }, { call, put, select }) {
      const { messageTemplate } = payload;
      const chatState = yield select((state) => state['chat']);

      const { messageChatList = [], socket, messageRecordList = [] } = chatState;

      const temp = messageChatList.concat();
      temp.push(messageTemplate);
      yield put({ type'refreshChatList'payload: { messageChatList: temp } });
      yield put({ type'chatInputMessageChange'payload: { contentnull } });

      if (!socket) console.warn('socket 不存在,需要重新登录,请检查 Socket 连接。');
      if (socket) socket.emit('SINGLE_CHAT', messageTemplate);

      // 更新聊天记录列表

      // 方法一、从新调用获取
      yield put({ type'getMessageRecordList' });

      // 方法二、在接收到消息到时候更新

      // 方法三、查找现有列表 更新对应字段
      // const { sendId, receiveId } = messageTemplate;
      // let recordIndex = -1;
      // messageRecordList.map((item, index) => {
      //   if (
      //     (item.sendId === sendId && item.receiveId === receiveId) ||
      //     (item.receiveId === sendId && item.sendId === receiveId)
      //   ) {
      //     recordIndex = index;
      //   }
      // });
      // if (recordIndex !== -1) {
      //   messageRecordList[recordIndex]
      // }
      // console.log(recordIndex);

      console.log(chatState);
    },

API 修改查看


效果图


end


以上是关于前后端分离实践分页请求历史聊天消息的主要内容,如果未能解决你的问题,请参考以下文章

前后端分离实践

前后端分离实践

第1176期前后端分离实践

前后端分离,最佳实践

老树发新芽-前后端分离实践

基于Vue的前后端分离项目实践