React se da cuenta del mensaje del historial de la sala de chat y la barra de desplazamiento se desliza con el mensaje

Cuando estemos desarrollando un chat de mensajería instantánea, encontraremos dos requisitos:
1. Cuando se carga un mensaje nuevo, deje que la barra de desplazamiento se deslice automáticamente hacia abajo
2. Desplácese hacia arriba para cargar el registro del historial y mantener la barra de desplazamiento en la posición original

Te mostré parte del código. Eliminé parte de la lógica vinculada al negocio. El código puede ser un poco complicado.
Los principales puntos a tener en cuenta son:

  • Use el método scrollIntoView () en el componente secundario. Este método desplazará el contenedor principal del elemento para hacer que el elemento llamado scrollIntoView () sea visible para el usuario, que logra la función de desplazarse hacia abajo a medida que se carga el mensaje.
  useEffect(() => {
    
    
    if (shouldScroll) {
    
    
      $container.current.scrollIntoView()
    }
  }, [])
  • Al cargar subcomponentes de mensajes en un bucle, no use el índice de índice para la clave, pero use el id, de modo que si dos elementos tienen la misma clave y satisfacen el mismo tipo de elemento, si los atributos del elemento cambian, React solo actualice el propiedad del componente, también asegurará que la barra de desplazamiento para cargar los mensajes del historial permanezca en la posición original en lugar de desplazarse hasta el final. (Por favor, comprenda el mecanismo de React Key usted mismo para principios específicos)

MessageBox.js

import React, {
    
     useRef, useEffect, memo } from 'react'
import PropTypes from 'prop-types'
import {
    
     Spin } from 'antd'
import {
    
     isEmpty } from 'lodash'
import * as api from '../../api'
import MessageItemBox from './MessageItemBox'

import '../index.scss'

const MessageBox = ({
    
    
  isEnd,
  queryChatRecord,
  loading,
  action = {
    
    },
  socket = {
    
    },
  focus,
  sessionList = {
    
    },
  customerInfo = {
    
    },
  messages = {
    
    },
}) => {
    
    
  const $containerEl = useRef()
  let isFetching = false // 判断是否是拉取数据操作

  // 上滑滚动加载
  const handleScroll = async e => {
    
    
    const {
    
     scrollHeight, clientHeight, scrollTop } = $containerEl.current || {
    
    }

    if (scrollTop + clientHeight === scrollHeight && sessionList[focus]?.messages?.hasNew) {
    
    
   	  //  监听当滑动到底去掉新消息提醒(业务相关,可忽略)
      action.clearMessageStatus({
    
     sessionId: focus })
    }

    if (isEnd) {
    
    
      return
    }
    if ($containerEl.current && e.target !== $containerEl.current) {
    
    
      return
    }
    if (isFetching) {
    
    
      return
    }

    const $div = e.target

    if ($div.scrollTop === 0 && $div.scrollHeight > $div.clientHeight && !loading) {
    
    
      isFetching = true
      queryChatRecord() // 拉取历史消息
      isFetching = false
    }
  }
  /**
   *
   * 按照消息发送时间排序
   * @param {*} session1
   * @param {*} session2
   * @return {*}
   */
  const sort = (session1, session2) => {
    
    
    return (session1.createTime) > (session2.createTime) ? 1 : -1
  }
  // 滚动到底部
  const handleScrollBottom = () => {
    
    
    const {
    
     scrollHeight, clientHeight } = $containerEl.current
    $containerEl.current.scrollTop = scrollHeight - clientHeight

	// 清除新消息提醒
    action.clearMessageStatus({
    
     sessionId: focus })
  }
  const renderMessage = (item, index) => {
    
    
    let shouldScroll = true
    const isSelf = item.from?.uid === customerInfo.uid
    // 【重点】
    if ($containerEl.current) {
    
    
      const {
    
     scrollHeight, clientHeight, scrollTop } = $containerEl.current
      shouldScroll = isSelf ||
        scrollHeight === clientHeight ||
        scrollTop === 0 ||
        scrollTop > scrollHeight - clientHeight * 2
    }

    return (
      <MessageItemBox
        key={
    
    item.imMsgId} //【重点】key必须使用数组内的唯一值,而不能使用index
        content={
    
    item.content}
        type={
    
    item.type}
        direction={
    
    item.direction || 'right'}
        shouldScroll={
    
    shouldScroll}
        avatar={
    
    item.from?.avatar}
        username={
    
    item.from?.name}
        createTime={
    
    item.createTime}
        loading={
    
    item.loading}
        success={
    
    item.success}
        sendContent={
    
    item.sendContent}
        focus={
    
    focus}
        imMsgId={
    
    item.imMsgId}
        socket={
    
    socket}
        action={
    
    action}
      />
    )
  }

  return (
    <>
      <div
        styleName='session-content-dialog'
        ref={
    
    $containerEl}
        onScroll={
    
    handleScroll}
      >
        {
    
    
          !isEnd && loading && <div className='flex-column' style={
    
    {
    
     width: '100%' }}> <Spin spinning={
    
    loading} /></div>
        }
        {
    
    !isEmpty(messages) && messagesInfo.sort(sort).map((item, index) =>
          renderMessage(item, index)
        )}

        {
    
    isEmpty(messages) && !loading ? (
          <div style={
    
    {
    
     textAlign: 'center', color: '#969696', marginTop: 30 }}>无记录</div>
        ) : (
          ''
        )}
      </div>
      {
    
     messages?.hasNew && (
        <div className='flex-row-reverse' style={
    
    {
    
     width: '100%' }} onClick={
    
    handleScrollBottom}>
          <div
            style={
    
    {
    
    
              backgroundColor: '#fff',
              textAlign: 'center',
              color: '#1890ff',
              display: 'inline-block',
              zIndex: 10,
              width: 100,
              padding: 5,
              borderRadius: 8,
              marginTop: '-34px',
              cursor: 'pointer',
            }}
          >你有新消息
          </div>
        </div>
      )
      }
    </>

  )
}

MessageBox.propTypes = {
    
    
  isEnd: PropTypes.bool,
  loading: PropTypes.bool,
  queryChatRecord: PropTypes.func,
  action: PropTypes.any,
  socket: PropTypes.any,
  messages: PropTypes.object,
  focus: PropTypes.string,
  sessionList: PropTypes.object,
  customerInfo: PropTypes.object,
}

export default memo(MessageBox)

MessageItemBox.js

import React, {
    
     useEffect, useRef, useState, memo, lazy, Suspense } from 'react'
import PropTypes from 'prop-types'
import {
    
     Icon, message } from 'antd'
import moment from 'moment'
import {
    
     post } from 'utils/request'
import {
    
     emojiData } from '../../config'
import '../index.scss'
const MediaMessage = lazy(() => import('./MediaMessage'))
const validKnowledge = payload => post('/im/imMessageService/validKnowledge', payload)

/** 客服相关 */
// 客服状态列表

const MessageItemBox = ({
    
    
  msgSource = 1,
  createTime,
  content = {
    
    },
  direction,
  avatar,
  type,
  shouldScroll,
  loading,
  success,
  username,
  sendContent = {
    
    },
  focus,
  imMsgId,
  action,
  socket,
}) => {
    
    
  const $container = useRef()
  // const action = useAction()
  // const socket = useSocket()
  const [curLoading, setCurLoading] = useState(loading)
  const [visible, setVisible] = useState(false)
  useEffect(() => {
    
    
    // 【重点】判断是否需要滚动,滚动条自动向下滑动
    if (shouldScroll) {
    
    
      $container.current.scrollIntoView()
    }
  }, [])

  useEffect(() => {
    
    
    setCurLoading(loading)
  }, [loading])

  const handleMedia = () => {
    
    
    setVisible(true)
  }
  const getContent = () => {
    
    
    switch (type) {
    
    
 	  ...
      default: {
    
    
        if (!content.msg) return ''
        const res = renderText(content.msg)

        return <>
          <div styleName='dialogue-arrow' />
			{
    
    content.msg}
        </>
      }
    }
  }

  /**
   * 重发消息
   */
  const handleReSend = async () => {
    
    
    setCurLoading(true)
    try {
    
    
      const res = await socket.send(sendContent)

      action.updateSessionMessage(focus, imMsgId, sendContent, res)
    } catch (error) {
    
    
      action.updateSessionMessage(focus, imMsgId, sendContent, {
    
     sendSuccess: false })
    }
    setCurLoading(false)
  }

  return (
    <div style={
    
    {
    
     textAlign: 'center', marginBottom: 10 }} ref={
    
    $container}>
      <div className='flex-column' style={
    
    {
    
     alignItems: direction === 'left' ? 'flex-start' : 'flex-end' }}>
        <div className='mb8'>
          {
    
     `${
      
      username}(${
      
      moment(createTime).format('YYYY-MM-DD HH:mm:ss')})`}
        </div>
        <div
          style={
    
    {
    
     display: 'flex', flexDirection: direction === 'left' ? 'row' : 'row-reverse', alignItems: 'center' }}
        >
          <div>
            <span styleName='dialogue-avatar'>
              <img src={
    
    avatar + '?imageView2/1/w/40/h/40'} />
            </span>
          </div>
          <div styleName={
    
    `dialogue-popover-${
      
      direction}`}>{
    
    getContent()}</div>
          {
    
    curLoading && <Icon type='loading' />}
          {
    
    !success && !curLoading &&
          <div onClick={
    
    handleReSend}><Icon type='exclamation' style={
    
    {
    
     color: 'red' }} /></div>
          }
        </div>
      </div>

      {
    
    visible &&
      <Suspense>
        <MediaMessage visible={
    
    visible} type={
    
    type} onCancel={
    
    () => setVisible(false)} src={
    
    content.url} />
      </Suspense>
      }
    </div>
  )
}

MessageItemBox.propTypes = {
    
    
  msgSource: PropTypes.number,
  createTime: PropTypes.number,
  type: PropTypes.number,
  content: PropTypes.object,
  shouldScroll: PropTypes.bool,
  avatar: PropTypes.string,
  direction: PropTypes.string,
  loading: PropTypes.bool,
  success: PropTypes.bool,
  sendContent: PropTypes.object,
  imMsgId: PropTypes.string,
  focus: PropTypes.string,
  username: PropTypes.string,
  action: PropTypes.any,
  socket: PropTypes.any,
}

export default memo(MessageItemBox)

Supongo que te gusta

Origin blog.csdn.net/zn740395858/article/details/113106801
Recomendado
Clasificación