개발조각

[React] 채팅 데이터로 채팅 스크롤 및 화면 구현 본문

React

[React] 채팅 데이터로 채팅 스크롤 및 화면 구현

개발조각 2023. 12. 19. 14:34
728x90
반응형

최종 코드

채팅 스크롤 및 채팅 화면의 특징

  1. 스크롤 맨 아래가 최신 채팅 내용, 스크롤을 위로 올릴수록 예전 채팅 내용이 나온다.
  2. 스크롤을 최상단으로 올리면 더 이전의 채팅 내용이 나온다.
  3. 이전데이터가 나오면 스크롤 위치마지막으로 본 채팅 내용 위치로 간다.
  4. 채팅화면 입장시 스크롤이 맨 아래에 있어야 된다.
  5. 채팅 중간마다 날짜가 바뀌면 년/월/일 표시해준다.

일반적으로 백엔드에게서 채팅 내용들을 API로 받으면 아래와 같은 모양으로 구성이 되어 있습니다.

{
	"page": 1,
	"content": [
    	{
            "question": "안녕??",
            "answer": "반가워~",
            "created_at": "2023-13-30T06:00:00"
        },
        ...
    ]
}

get을 사용하여 page를 query string으로 넘겨 사용하게 됩니다.

 

1. 스크롤 맨 아래가 최신 채팅 내용, 스크롤을 위로 올릴수록 예전 채팅 내용이 나온다.

만약 1개의 page에 5개의 채팅이 있다 하면 예시로 숫자료 표시하겠습니다.

(숫자가 클수록 이전 내용, 숫자가 작을수록 최신 내용)

{"page":1 "content": [1, 2, 3, 4, 5]}
{"page":2 "content": [6, 7, 8, 9, 10]}
{"page":3 "content": [11, 12, 13, 14, 15]}

이럴 경우 채팅화면에 보여야 되는 모습은 아래와 같이 돼야 되는데,

예전 채팅 내용
15
14
13
12
11
// 최상단이면 이전 데이터 가져오기 [11, 12, 13, 14, 15]
10
9
8
7
6
// 최상단이면 이전 데이터 가져오기 [6, 7, 8, 9, 10]
5
4
3
2
1
최신 채팅 내용

 

map으로 채팅 리스트를 돌려보면 map은 index 0부터 마지막까지 반복합니다.

최종적인 채팅리스트는 [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 이렇게 되어야 됩니다.

 

[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 이렇게 되기 위해서 나누어 보자면 아래와 같이 됩니다.

[ ...[15, 14, 13, 12, 11] ...[10, 9, 6, 7, 6] ...[5, 4, 3, 2, 1] ]
  page: 3 page: 2 page: 1  

이 말은 데이터를 뒤집고, 기존데이터에 뒤에 추가되야 된다는 말입니다.

 

const [content, setContent] = useState([]); // 채팅리스트를 받을 곳
const [page, setPage] = useState(1);

// 스크롤이 최상단이면 작동
const chatFetch = async () => {
    try {
      const res = await API.chatList(page); page를 통해 받은 API 채팅 데이터
      const newData = res.data.content.reverse();

      setContent((prevContent) => [...newData, ...prevContent]);
      setPage((page) => page + 1);
    } catch (err) {
      console.log(err);
    }
};

이렇게 작성해야 원하는 모양의 채팅리스트를 만들 수 있습니다.

 

2. 스크롤을 최상단으로 올리면 더 이전의 채팅 내용이 나오기

이제 원하는 채팅리스트를 만들었으니 스크롤이 최상단일 경우 이전 채팅 내용이 나오도록 해야 됩니다.

스크롤이 최상단인지 판단하기 위해서는 useRef, 최상단인지 판단해 주는 state가 있으면 됩니다.

그리고 useEffect를 이용해서 최상단일 때 채팅데이터를 불러오면 됩니다.

// 스크롤 최상단 파악하기 위해
const chatRef = useRef<HTMLDivElement>(null);
const [isTop, setIsTop] = useState(false); // 최상단인지 파악

// 채팅리스트 가져오기
const [content, setContent] = useState([]); // 채팅리스트를 받을 곳
const [page, setPage] = useState(1);

// 채팅리스트 가져오기
const chatFetch = async () => {
    try {
      const res = await API.chatList(page); page를 통해 받은 API 채팅 데이터
      const newData = res.data.content.reverse();

      setContent((prevContent) => [...newData, ...prevContent]);
      setPage((page) => page + 1);
    } catch (err) {
      console.log(err);
    }
};

// 스크롤시 최상단인지 판별
const onScroll = () => {
    if (chatRef.current) {
      const { scrollTop, scrollHeight } = chatRef.current;
      if (scrollTop === 0) setIsTop(false);
    }
};

// isTop이 바뀔때마다 작동하고 isTop이 ture일때만 스크롤 제위치로 이동
useEffect(() => {
    if (isTop) {
      chatFetch(); // 해당 채팅리스트 가져오기
    }
    setIsTop(false);
}, [isTop]);
  • scrollTop : 현재 스크롤 위치
  • scrollHeight : 전체 스크롤 가능한 높이
<div ref={chatRef}>
    {content?.map((chat, idx) => {
        return <채팅리스트 />
    })}
<div>

 

3. 스크롤 위치가 마지막으로 본 채팅 내용 위치로 간다.

if (chatRef.current) {
    const { scrollHeight } = chatRef.current;
    chatRef.current.scrollTop = scrollHeight - scroll;
}
  • scrollTop : 현재 스크롤 위치
  • scrollHeight : 전체 스크롤 가능한 높이

이 코드를 추가해 주면 된다.

 

// 스크롤 최상단 파악하기 위해
const chatRef = useRef<HTMLDivElement>(null);
const [isTop, setIsTop] = useState(false); // 최상단인지 파악

// 채팅리스트 가져오기
const [content, setContent] = useState([]); // 채팅리스트를 받을 곳
const [page, setPage] = useState(1);

// 채팅리스트 가져오기
const chatFetch = async () => {
    try {
      const res = await API.chatList(page); page를 통해 받은 API 채팅 데이터
      const newData = res.data.content.reverse();

      setContent((prevContent) => [...newData, ...prevContent]);
      setPage((page) => page + 1);
    } catch (err) {
      console.log(err);
    }
};

// 스크롤시 최상단인지 판별
const onScroll = () => {
    if (chatRef.current) {
      const { scrollTop, scrollHeight } = chatRef.current;
      if (scrollTop === 0) setIsTop(false);
    }
};

// isTop이 바뀔때마다 작동하고 isTop이 ture일때만 스크롤 제위치로 이동
useEffect(() => {
    if (isTop) {
      chatFetch(); // 해당 채팅리스트 가져오기
      // 추가한 코드/////스크롤 위치 이동
      if (chatRef.current) {
        const { scrollHeight } = chatRef.current;
        chatRef.current.scrollTop = scrollHeight - scroll;
      }
    }
    setIsTop(false);
}, [isTop]);

 

4. 채팅화면 입장 시 스크롤이 맨 아래에 있어야 된다.

위에 있는 코드를 작성해도 빈화면인걸 볼 수 있다.

사실 미리 useEffect를 사용해서 page=1일 때의 채팅리스트를 바로 보여줘야 된다.

이때 추가적으로 page=1일때의 채팅리스트를 보여주고 난 뒤 스크롤을 맨 밑으로 이동시켜야 됩니다.

  // 스크롤 맨 밑으로 이동
  const scrollBottom = () => {
    if (chatRef.current) {
      chatRef.current.scrollTop = chatRef.current.scrollHeight;
    }
  };

  // 채팅화면 입장 시 채팅목록 불러오고 스크롤 맨 밑으로 이동시키기
  useEffect(() => {
    chatFetch().then(() => {
      if (content) {
        scrollBottom();
      }
    });
  }, []);

이 코드를 추가시키면 됩니다.

 

chatFetch가 비동기이다 보니 아래와 같이 코드를 작성하면 scrollBottom()이 먼저 실행한 후, chatFetch()가 실행하게 됩니다.

그래서 스크롤이 최하단이 아닌 최상단에 있게 됩니다.

useEffect(() => {
    chatFetch()
    scrollBottom();
}, []);

그래서 then을 이용해서 해결해 주었습니다.

 

// 스크롤 최상단 파악하기 위해
const chatRef = useRef<HTMLDivElement>(null);
const [isTop, setIsTop] = useState(false); // 최상단인지 파악

// 채팅리스트 가져오기
const [content, setContent] = useState([]); // 채팅리스트를 받을 곳
const [page, setPage] = useState(1);

// 채팅리스트 가져오기
const chatFetch = async () => {
    try {
      const res = await API.chatList(page); page를 통해 받은 API 채팅 데이터
      const newData = res.data.content.reverse();

      setContent((prevContent) => [...newData, ...prevContent]);
      setPage((page) => page + 1);
    } catch (err) {
      console.log(err);
    }
};

// 코드 추가 부분 //////////
// 스크롤 맨 밑으로 이동
const scrollBottom = () => {
if (chatRef.current) {
  chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
};

// 채팅화면 입장 시 채팅목록 불러오고 스크롤 맨 밑으로 이동시키기
useEffect(() => {
chatFetch().then(() => {
  if (content) {
    scrollBottom();
  }
});
}, []);
// 코드 추가 부분 //////////

// 스크롤시 최상단인지 판별
const onScroll = () => {
    if (chatRef.current) {
      const { scrollTop, scrollHeight } = chatRef.current;
      if (scrollTop === 0) setIsTop(false);
    }
};

// isTop이 바뀔때마다 작동하고 isTop이 ture일때만 스크롤 제위치로 이동
useEffect(() => {
    if (isTop) {
      chatFetch(); // 해당 채팅리스트 가져오기
      // 스크롤 위치 이동
      if (chatRef.current) {
        const { scrollHeight } = chatRef.current;
        chatRef.current.scrollTop = scrollHeight - scroll;
      }
    }
    setIsTop(false);
}, [isTop]);

 

 

5. 채팅 중간마다 날짜가 바뀌면 년/월/일 표시해 준다.

마지막으로 년/월/일 표시입니다.

카톡을 보시면 "년/월/일"이 중복으로 나오지 않고 딱 한 번씩만 나옵니다.

그래서 map을 돌리면서 이전날짜 데이터, 현재 날짜 데이터를 비교했습니다.

(저는 day.js를 사용해서 비교해 주었습니다.)

<div ref={chatRef}>
    {content?.map((chat, idx) => {
    // 이전, 현재 날짜 비교 -> 년/월/일 체크
    const date = dayjs(chat.created_at).format("YYYY년 MM월 DD일");
    const prevDate = idx
    ? dayjs(content[idx - 1].created_at).format("YYYY년 MM월 DD일")
    : date;
        return <채팅리스트 />
    })}
<div>

 

 

최종코드

// 스크롤 최상단 파악하기 위해
const chatRef = useRef<HTMLDivElement>(null);
const [isTop, setIsTop] = useState(false); // 최상단인지 파악

// 채팅리스트 가져오기
const [content, setContent] = useState([]); // 채팅리스트를 받을 곳
const [page, setPage] = useState(1);

// 채팅리스트 가져오기
const chatFetch = async () => {
    try {
      const res = await API.chatList(page); page를 통해 받은 API 채팅 데이터
      const newData = res.data.content.reverse();

      setContent((prevContent) => [...newData, ...prevContent]);
      setPage((page) => page + 1);
    } catch (err) {
      console.log(err);
    }
};

// 스크롤 맨 밑으로 이동
const scrollBottom = () => {
if (chatRef.current) {
  chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
};

// 채팅화면 입장 시 채팅목록 불러오고 스크롤 맨 밑으로 이동시키기
useEffect(() => {
chatFetch().then(() => {
  if (content) {
    scrollBottom();
  }
});
}, []);

// 스크롤시 최상단인지 판별
const onScroll = () => {
    if (chatRef.current) {
      const { scrollTop, scrollHeight } = chatRef.current;
      if (scrollTop === 0) setIsTop(false);
    }
};

// isTop이 바뀔때마다 작동하고 isTop이 ture일때만 스크롤 제위치로 이동
useEffect(() => {
    if (isTop) {
      chatFetch(); // 해당 채팅리스트 가져오기
      // 스크롤 위치 이동
      if (chatRef.current) {
        const { scrollHeight } = chatRef.current;
        chatRef.current.scrollTop = scrollHeight - scroll;
      }
    }
    setIsTop(false);
}, [isTop]);
<div ref={chatRef}>
    {content?.map((chat, idx) => {
    // 이전, 현재 날짜 비교 -> 년/월/일 체크
    const date = dayjs(chat.created_at).format("YYYY년 MM월 DD일");
    const prevDate = idx
    ? dayjs(content[idx - 1].created_at).format("YYYY년 MM월 DD일")
    : date;
        return <채팅리스트 />
    })}
<div>
728x90
반응형

'React' 카테고리의 다른 글

[React] Custom Hooks  (0) 2023.09.20
[React] React.memo  (0) 2023.09.20
[React] useReducer  (0) 2023.09.19
[React] useCallback  (0) 2023.09.19
[React] useMemo  (0) 2023.09.15
Comments