React 实践 - 构建聊天界面

上一篇: React 体验开箱即用

React 实践 - 构建聊天界面_第1张图片

实现步骤 - 目录定义

基于 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 () => (
  
    
  
);
  • antd.design-pro Layout 定义
  • 单独创建聊天布局
  • route.js 中使用
    • 配置访问路由 -> config/routes.js
聊天布局
  • 分为左右,左边显示最近聊天记录,点击最近聊天对象, 右边显示聊天对象详细信息

React 实践 - 构建聊天界面_第2张图片

ChatRecordItem 为封装的组件,显示每一天最近聊天记录

  • initUserList 为 mock 数据,存放 mock 用户信息(用户名称、头像)

    export const initUserList = [
      {
        userId: "801901013480247300",
        userName: "李白",
        avatar:
          "https://gitee.com/shizidada/moose-resource/raw/master/blog/default-avatar.png",
      },
    ];
    
  • 后期对接 SpringBoot 集成 netty-socketio

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 (
    

{userInfo.userName || ""}

{sendTime}

{content}

); }; export default ChatRecordItem;

聊天

选择聊天对象显示

  • Im -> chat -> index.jsx

    代码地址
    https://gitee.com/shizidada/moose-react-learn/blob/master/src/pages/Im/chat/index.jsx

详情显示

React 实践 - 构建聊天界面_第3张图片

{
  messageList.map((item, index) => {
    return item.sendId !== userId ? (
      
{/* receiver */}
{toUserName}
{item.content}
) : (
{/* sender */}
{item.content}
{userName}
); }); }

可以对显示发送的消息和接收的消息进行组件化封装

聊天操作

dispatch({ type: "chat/chatInputMessageChange", payload: { message: e.target.value, }, }) } /> {/* for test */} {/* */}
发送消息
  • 必须选择聊天对象
  • 内容必须输入
  • 触发 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";
  • 在遍历完消息后面添加

  • 调用 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 () => {};
}, []);

React 实践 - 构建聊天界面_第4张图片
关注公众号 「全栈技术部」,不断学习更多有趣的技术知识。

你可能感兴趣的:(React,react)