前后端分离实践、分页请求历史聊天消息
分页加载原因
在一个系统数据很大,在接口数据交互,海量数据查询,服务器接口返回的数据不可能一次性返回。
数据量大,从数据库一次性查询,再到网络传输是要花费更多的时间客户端才能响应拿到数据进行 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 {
...
// 请求页码
pageNum: 1,
// 标示 是否还有更多数据
hasMore: true
...
},
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: { content: null } });
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
全栈技术部
不断学习更多有趣的技术知识。
Official Account