向上滚动加载旧消息时反应js聊天

React js chat when scrolling up load old messages

我正在开发一个chat,从图片可以看出

当聊天打开时,聊天 scrolls down 显示最新消息。

我想做的是,当用户 scrolls up 到达最后一条消息(即聊天中最旧的消息)时,调用 oldMessage 函数,使 http call 传递 current page 以尝试将 previous messages 检索到顶部显示的最后一个。

不知道我讲清楚没有。

你能帮帮我吗?

Link: codesandbox

import React, { useState, useRef, useEffect } from "react";
import { makeStyles } from "@material-ui/core/styles";
import {
  Card,
  Typography,
  Icon,
  useTheme,
  TextField,
  IconButton,
  Avatar,
  Paper
} from "@material-ui/core";
import Moment from "react-moment";
import clsx from "clsx";
import moment from "moment/moment";

const message = [
  {
    id: 1,
    createdAt: "",
    message: "Hi, James!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 2,
    createdAt: "",
    message: "Hi, Vesper!",
    senderId: {
      _id: 1,
      name: "James",
      surname: "Bond"
    }
  },
  {
    id: 3,
    createdAt: "",
    message: "Quickly come to the meeting room 1B, we have a big server issue",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 4,
    createdAt: "",
    message: "I’m having breakfast right now, can’t you wait for 10 minutes?",
    senderId: {
      _id: 1,
      name: "James",
      surname: "Bond"
    }
  },
  {
    id: 5,
    createdAt: "",
    message: "I’m having breakfast right now, can’t you wait for 10 minutes?",
    senderId: {
      _id: 1,
      name: "James",
      surname: "Bond"
    }
  },
  {
    id: 6,
    createdAt: "",
    message: "We are losing money! Quick!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 7,
    createdAt: "",
    message:
      "It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.",
    senderId: {
      _id: 1,
      name: "James",
      surname: "Bond"
    }
  },
  {
    id: 8,
    createdAt: "",
    message: "You are the worst!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 9,
    createdAt: "",
    message: "We are losing money! Quick!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 10,
    createdAt: "",
    message: "You are the worst!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 11,
    createdAt: "",
    message: "We are losing money! Quick!",
    senderId: {
      _id: 2,
      name: "Vesper",
      surname: "Lynd"
    }
  },
  {
    id: 12,
    createdAt: "",
    message:
      "It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.",
    senderId: {
      _id: 1,
      name: "James",
      surname: "Bond"
    }
  }
];

const useStyles = makeStyles((theme) => ({
  root: {
    "& > *": {
      margin: theme.spacing(1)
    }
  },
  messageRow: {
    position: "relative",
    display: "flex",
    flexDirection: "column",
    alignItems: "flex-start",
    justifyContent: "flex-end",
    padding: "0 16px 4px 16px",
    flex: "0 0 auto",
    "&.contact": {
      "& $bubble": {
        backgroundColor: theme.palette.background.paper,
        color: theme.palette.getContrastText(theme.palette.background.paper),
        borderTopLeftRadius: 5,
        borderBottomLeftRadius: 5,
        borderTopRightRadius: 20,
        borderBottomRightRadius: 20,
        marginLeft: 28,
        "& $time": {
          marginLeft: 12
        }
      },
      "&.first-of-group": {
        "& $bubble": {
          borderTopLeftRadius: 20
        }
      },
      "&.last-of-group": {
        "& $bubble": {
          borderBottomLeftRadius: 20
        }
      }
    },
    "&.me": {
      paddingLeft: 40,

      "& $avatar": {
        order: 2,
        margin: "0 0 0 16px"
      },

      "& $bubble": {
        marginLeft: "auto",
        backgroundColor: theme.palette.primary.main,
        color: theme.palette.primary.contrastText,
        borderTopLeftRadius: 20,
        borderBottomLeftRadius: 20,
        borderTopRightRadius: 5,
        borderBottomRightRadius: 5,
        "& $time": {
          justifyContent: "flex-end",
          right: 0,
          marginRight: 12
        }
      },
      "&.first-of-group": {
        "& $bubble": {
          borderTopRightRadius: 20
        }
      },

      "&.last-of-group": {
        "& $bubble": {
          borderBottomRightRadius: 20
        }
      }
    },
    "&.contact + .me, &.me + .contact": {
      paddingTop: 20,
      marginTop: 20
    },
    "&.first-of-group": {
      "& $bubble": {
        borderTopLeftRadius: 20,
        paddingTop: 13
      }
    },
    "&.last-of-group": {
      "& $bubble": {
        borderBottomLeftRadius: 20,
        paddingBottom: 13,
        "& $time": {
          display: "flex"
        }
      }
    }
  },
  avatar: {
    position: "absolute",
    left: 0,
    margin: 0
  },
  bubble: {
    position: "relative",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: 12,
    maxWidth: "100%",
    boxShadow: theme.shadows[1]
  },
  message: {
    whiteSpace: "pre-wrap",
    lineHeight: 1.2
  },
  time: {
    position: "absolute",
    display: "none",
    width: "100%",
    fontSize: 11,
    marginTop: 8,
    top: "100%",
    left: 0,
    whiteSpace: "nowrap"
  },
  bottom: {
    // background: theme.palette.background.default,
    // borderTop: '1px solid rgba(0, 0, 0, 0.13)'
  },
  inputWrapper: {
    borderRadius: 24
  }
}));

export default function App() {
  const classes = useStyles();

  const [state, setState] = useState({
    userMyInfo: {
      id: 1,
      name: "James",
      surname: "Bond"
    },
    chat: message,
    msgState: "",
    pag: 0
  });

  const { userMyInfo, chat, msgState } = state;

  const sendMessage = () => {};

  const oldMessage = () => {
    //http request
    fetch("")
      .then((response) => response.json())
      .then((message) => {
        setState(...(prev) => ({ ...prev, chat: [...message, ...prev.chat] }));
      })
      .catch((error) => {
        console.error(error);
      });
  };

  const messagesEndRef = useRef(null);
  const scrollToBottom = () => {
    messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
  };
  useEffect(scrollToBottom, []);

  const shouldShowContactAvatar = (item, i) => {
    return (
      (chat[i + 1] && chat[i].senderId._id !== chat[i + 1].senderId._id) ||
      !chat[i + 1]
    );
  };

  const isFirstMessageOfGroup = (item, i) => {
    return (
      i === 0 || (chat[i - 1] && chat[i - 1].senderId._id !== item.senderId._id)
    );
  };

  const isLastMessageOfGroup = (item, i) => {
    return (
      i === chat.length - 1 ||
      (chat[i + 1] && chat[i + 1].senderId._id !== item.senderId._id)
    );
  };

  return (
    <Paper
      elevation={3}
      className={clsx(classes.root, "flex flex-col relative pb-64")}
    >
      <Card elevation={1} className="flex flex-col h-512 rounded-8">
        <div
          className="flex flex-shrink-0 items-center justify-between px-24 h-64"
          style={{
            background: "#607d8b"
            //color: theme.palette.getContrastText('#607d8b')
          }}
        >
          <Typography className="text-center text-16 font-400">Chat</Typography>
        </div>
        <div style={{ flex: 1, overflowY: "auto" }}>
          {state.chat.length === 0 ? (
            <div style={{ textAlign: "center" }}>
              Al momento non ci sono messaggi
            </div>
          ) : (
            state.chat.map((item, key) => (
              <div
                key={key}
                className={clsx(
                  classes.messageRow,
                  { me: item.senderId._id === userMyInfo.id },
                  { contact: item.senderId._id !== userMyInfo.id },
                  { "first-of-group": isFirstMessageOfGroup(item, key) },
                  { "last-of-group": isLastMessageOfGroup(item, key) }
                )}
              >
                {item.senderId._id !== userMyInfo.id &&
                  shouldShowContactAvatar(item, key) && (
                    <Avatar className={classes.avatar}>
                      {item.senderId.name[0]} {item.senderId.surname[0]}
                    </Avatar>
                  )}
                <div className={classes.bubble}>
                  <div className={classes.message}>{item.message}</div>
                  <Typography className={classes.time} color="textSecondary">
                    {moment(item.time).format("MMMM Do YYYY, h:mm:ss a")}
                  </Typography>
                </div>
              </div>
            ))
          )}
          <div ref={messagesEndRef} />
        </div>
        <div style={{ padding: 5, display: "flex", flexDirection: "row" }}>
          <TextField
            required
            id="outlined-required"
            label="Message"
            //inputRef={textInput}
            placeholder="Message"
            //onChange={handleChange}
            variant="outlined"
            fullWidth
          />
          <IconButton onClick={() => sendMessage()} disabled={msgState === ""}>
            <Icon>send</Icon>
          </IconButton>
        </div>
      </Card>
    </Paper>
  );
}

您可以做的两件事是

  1. 使用 react 的 onScroll 监视元素上的滚动事件

    <ScrollableComponent onScroll={this.handleScroll} />

  2. 使用 windows 滚动事件处理程序并检测用户何时位于页面顶部

    useEffect(() => { window.addEventListener('scroll', this.handleScroll); return () => window.removeEventListener('scroll', this.handleScroll); }</p> <p>const handleScroll = (事件) => { // 代码在这里 }

您需要为滚动添加一个事件处理程序并检查您是否在容器的顶部

const  handleScroll = e => {
   let element = e.target;
   if (element.scrollTop===0) {
     //fetch messages
   }
}

 <div style={{ flex: 1, overflowY: "auto"}} onScroll={ handleScroll}>