上一篇: React 体验开箱即用
实现步骤 - 目录定义
基于 antd.design-pro 脚手架
- 在
layouts
添加聊天界面布局 - 在
pages
目录添加聊天界面
文件结构
├── src
│ ├── layouts
│ │ ├── layouts
│ │ ├── ChatLayout.jsx # 聊天布局
│ │ ├── ChatLayout.less # 聊天布局样式
│ ├── pages
│ │ ├── Im
│ │ │ ├── chat
│ │ │ │ ├── index.jsx # 聊天界面
│ │ │ │ ├── idnex.less # 聊天界面样式
│ │ │ ├── index.jsx # 聊天初始化界面
配置访问路由 config/routes.js
{
path: '/im',
name: 'im',
icon: 'wechat', // 显示图标
routes: [
{
path: '/im', // 访问路由地址
component: '../layouts/ChatLayout', // 聊天界面使用聊天布局
routes: [
{
path: '/im', // 初始化界面
component: './Im',
},
{
path: '/im/chat', // 具体聊天界面显示
name: 'chat',
component: './Im/chat', // 对应组件
},
],
},
],
},
添加国际化 locales/zh-CN/menu.js
'menu.im': '即时通讯',
'menu.chat': '聊天',
实现步骤 - Mock 数据准备
使用 dva 状态数据管理
dva 不熟悉可以查看介绍使用 React 常用开发框架
在 models
目录下创建 chat.js
const ChatModel = {
namespace: "chat",
state: {
// 当前聊天对象
chatUserInfo: {},
// 输入聊天信息
content: "",
// 聊天详情
messageList: [
{
msgId: "7751131831315698898",
sendId: "775113183131074580",
content: "你吃饭了吗?",
},
{
msgId: "7751133655565656565",
sendId: "801901013480247300",
content: "黄河之水天上来。",
},
],
// 最近聊天列表
chatRecordList: [
{
msgId: "775113183131069599",
sendId: "801901013480247300",
receiveId: "775113183131074580",
type: "1",
chatType: "1",
sendTime: "2021-03-09",
content: "黄河之水天上来。",
},
],
},
effects: {},
reducers: {
// 当前和那个聊天
chatCurrentUser(state, { payload: { chatUserInfo } }) {
return {
...state,
chatUserInfo,
};
},
// 刷新聊天列表
refreshChatList(state, { payload: { messageList } }) {
return {
...state,
messageList: messageList,
};
},
// 输入消息改变时
chatInputMessageChange(state, { payload: { message } }) {
return {
...state,
content: message,
};
},
},
subscriptions: {
setup({ history }) {
history.listen(({ pathname, search }) => {});
},
},
};
export default ChatModel;
实现步骤 - 布局
ChatLayout 代码
import { Link } from "umi";
import { connect } from "dva";
import ChatRecordItem from "@/components/ChatRecordItem";
import styles from "./ChatLayout.less";
const ChatLayout = ({ dispatch, children, chat }) => {
const { chatUserInfo, chatRecordList } = chat;
// 点击切换当前聊天对象
// model/chat 中定义 `chat/chatCurrentUser`
const onChangeChatCurrentUser = (item) => {
dispatch({
type: "chat/chatCurrentUser",
payload: {
chatUserInfo: item,
},
});
};
return (
<div className={styles["chat-layout-container"]}>
<div className={styles["chat-message-list"]}>
{chatRecordList.map((item) => {
return (
<Link to="/im/chat" key={item.msgId}>
<ChatRecordItem
{...item}
// 选中聊天对象
selected={item.sendId === chatUserInfo.sendId}
onClick={() => onChangeChatCurrentUser(item)}
/>
</Link>
);
})}
</div>
<div className={styles["chat-message-content"]}>{children}</div>
</div>
);
};
export default connect(({ chat }) => ({
chat,
}))(ChatLayout);
ChatLayout 代码说明
没有选择聊天对象显示
import { Card, Empty } from "antd";
export default () => (
<Card>
<Empty description="请选择聊天对象" />
</Card>
);
-
antd.design-pro Layout
定义 - 单独创建聊天布局
- 在
route.js
中使用- 配置访问路由 ->
config/routes.js
- 配置访问路由 ->
聊天布局
- 分为左右,左边显示最近聊天记录,点击最近聊天对象, 右边显示聊天对象详细信息
ChatRecordItem
为封装的组件,显示每一天最近聊天记录
-
initUserList
为 mock 数据,存放 mock 用户信息(用户名称、头像)export const initUserList = [ { userId: "801901013480247300", userName: "李白", avatar: "https://gitee.com/shizidada/moose-resource/raw/master/blog/default-avatar.png", }, ];
classnames
模块可以构建使用 className 添加样式
import { Avatar, Divider } from "antd";
import { initUserList } from "@/mock/userList";
import cls from "classnames";
import styles from "./index.less";
const ChatRecordItem = ({ sendId, sendTime, content, selected, onClick }) => {
const [userInfo] = initUserList.filter((item) => item.userId === sendId);
if (!userInfo) return null;
let selectedClassName = styles["chat-record-item-selected"];
return (
<div>
<div
className={cls(styles["chat-record-item"], {
[selectedClassName]: selected,
})}
onClick={onClick}
>
<div className={styles["chat-user-avatar"]}>
<Avatar src={userInfo.avatar} />
</div>
<div className={styles["chat-user-info"]}>
<div className={styles.top}>
<p className={styles["chat-user-name"]}>
{userInfo.userName || ""}
</p>
<span className={styles.time}>{sendTime}</span>
</div>
<div className={styles["chat-message-detail-item"]}>
<p className={styles["chat-message-detail"]}> {content}</p>
</div>
</div>
</div>
<Divider style={{ margin: 0 }} />
</div>
);
};
export default ChatRecordItem;
聊天
选择聊天对象显示
- Im -> chat -> index.jsx
代码地址
https://gitee.com/shizidada/moose-react-learn/blob/master/src/pages/Im/chat/index.jsx
详情显示
{
messageList.map((item, index) => {
return item.sendId !== userId ? (
<div className={styles["chat-item"]} key={item.msgId || index}>
<div className={styles["chat-receiver"]}>
{/* receiver */}
<div className={styles["avatar-wrap"]}>
<div className={styles.avatar}>
<Avatar
size="large"
style={{ backgroundColor: "#005EFF", verticalAlign: "middle" }}
>
{toUserName}
</Avatar>
</div>
</div>
<div className={styles.content}>{item.content}</div>
</div>
</div>
) : (
<div className={styles["chat-item"]} key={item.messageId || index}>
<div className={styles["chat-sender"]}>
{/* sender */}
<div className={styles.content}>{item.content}</div>
<div className={styles["avatar-wrap"]}>
<div className={styles.avatar}>
<Avatar
size="large"
style={{ backgroundColor: "#005EFF", verticalAlign: "middle" }}
>
{userName}
</Avatar>
</div>
</div>
</div>
</div>
);
});
}
可以对显示发送的消息和接收的消息进行组件化封装
聊天操作
<div className={styles["chat-input-area"]}>
<Row>
<Input.TextArea
placeholder="请输入消息"
autoSize={{ minRows: 4, maxRows: 5 }}
value={content}
onChange={(e) =>
dispatch({
type: "chat/chatInputMessageChange",
payload: {
message: e.target.value,
},
})
}
/>
</Row>
<Row type="flex" justify="end" style={{ marginTop: 10, marginRight: 10 }}>
<Col>
<Button type="primary" onClick={onSendMessage} disabled={!content}>
发送
</Button>
{/* for test */}
{/* <Button type="primary" onClick={onMessageScroll}>滚动</Button> */}
</Col>
</Row>
</div>
发送消息
- 必须选择聊天对象
- 内容必须输入
- 触发 ChatModel 定义 reducers;添加消息,重新刷新当前视图
const onSendMessage = () => {
if (!receiveId) {
message.error("请选择聊天对象");
return;
}
if (!content) {
return;
}
let messageTemplate = {
type: "MS:TEXT",
chatType: "CT:SINGLE",
content,
sendId: userId,
receiveId: receiveId,
};
const temp = messageList.concat();
temp.push(messageTemplate);
dispatch({
type: "chat/refreshChatList",
payload: {
messageList: temp,
},
});
dispatch({
type: "chat/chatInputMessageChange",
payload: {
message: null,
},
});
};
超过消息容器,自动滚动至底部
- 可以使用原生 js 实现
- 当前使用
react-scroll
模块实现
使用 react-scroll
-
npm install react-scroll --save
-
添加
react-scroll
代码
import { Element } from "react-scroll";
- 在遍历完消息后面添加
<Element name="bottomElement"></Element>
- 调用
react-scroll
API
import { scroller } from "react-scroll";
// scrollId 为 Element name 定义
scroller.scrollTo(`scrollId`, {
duration: 800,
delay: 0,
smooth: true,
containerId: `containerId`,
offset: 50,
});
封装 react-scroll
API
- utils/scroller.js
import { scroller } from "react-scroll";
export const scrollToBottom = (scrollId, containerId) => {
scroller.scrollTo(scrollId, {
duration: 800,
delay: 0,
smooth: true,
containerId: containerId,
offset: 50,
});
};
更多
react-scroll
API 参考react-scroll
模块文档 https://www.npmjs.com/package/react-scroll
使用
- 选中聊天对象调用
scrollToBottom
- 在发送消息完成之后调用
scrollToBottom
const onMessageScroll = () => {
scrollToBottom("bottomElement", "chatItems");
};
// 发送消息
...
dispatch({
type: 'chat/chatInputMessageChange',
payload: {
message: null,
},
});
onMessageScroll(); // 调用 scrollToBottom
};
....
// 进入页面
useEffect(() => {
// 没有选中聊天对象,返回 聊天初始化界面
if (!receiveId) {
history.replace({ pathname: '/im' });
return;
}
onMessageScroll();
return () => {};
}, []);
关注公众号 「全栈技术部」
,不断学习更多有趣的技术知识。