정규표현식으로 문자열 변환하기
채팅전송 멘션 하이라이트 효과,
정규표현식 Regular Expression : 패턴이 있는 부분 구현, 성능이 조금 안좋음
`npm i regexify-string`
/(여기에 적음)/g
g : global Flag 모두 찾겠다
\ : escape 정규식 특수기호 무력화. []각각 정규식 역할을 무력화 하고, 문자열로서 정규식을 만든다.
. + ? : (모든글자) (1개이상) (0개나 1개)
\d + ? : (숫자) (1개이상) (0개나 1개)
* : 0개이상
() : 그루핑, 묶인 값이 배열에 추가됨 arr[1], arr[2]...
regexifyString({
input: data.content,
pattern: /@\[(.+?)]\((\d+?)\)|\n/g,
//비교
decorator(match, index) {
const arr: string[] | null = match.match(/@\[(.+?)]\((\d+?)\)/)!;
//arr과 일치하면
if (arr) {
return (
<Link key={match + index} to={`/workspace/${workspace}/dm/${arr[2]}`}>
@{arr[1]}
</Link>
);
}
return <br key={index} />;
},
})
<Link> 멘션시 하이라이트, 클릭하면 메시지 전송할 수 있게 링크
정규식 규칙을 챗지피티에게 물어보면 개쩔게 바꿔준다는 설명을 들어서!!! 직접 물어봐 보았다.
전화번호는 손쉽게 정규식으로 변환해주시만!
제로초쌤과 함께 정한 정규식 규칙은 보다 좀더 널널하게 설정되었다
유용하지만, 필요할때 개발자들이 추가로 수정해가며 사용하는게 맞는 것 같다 (똑똑한 챗지피티.. 바보...)
react 문자 컨텐츠 싹 줄바꿈. 채팅 시 엔터치는 문자열을 화면에 br태그로 구현함
regexifyString({
input: data.content,
pattern: /\n/g,
decorator(match, index) {
return <br key={index} />;
},
})
js 연산자
& a //nested selector
a?.b; //optional chaining
a??b; // nullish coalescing
input text 글자 작성할때 멘션+보내진 채팅 리렌더링 발생! 방지하려면, 컴포넌트 분리
말단 컴포넌트 memo로 감싸기
memo : 컴포넌트 캐싱. props가 똑같으면 부모가 바뀌어도 자식을 리렌더링하지 않
useMemo : 값을 캐싱
const result = useMemo(
() =>
(
//
//...
//
),
[workspace, data.content],
);
날짜별로 묶어주기(position: sticky)
알고리즘 --> 2 구현
1. 서버에서 그룹화 해서 전송 -> cpu작업 과부화
2. 서버는 채팅데이터만 전송, 프론트가 (날짜별)그룹화, 구역을 나눔
makeSection.ts
날짜기준 분류 : dayjs
채팅데이터 반복문 돌면서 확인
export default function makeSection(chatList: (IDM | IChat)[]) {
const sections: { [key: string]: (IDM | IChat)[] } = {}; //TS 빈배열 설정
chatList.forEach((chat) => {
const monthDate = dayjs(chat.createdAt).format('YYYY-MM-DD');
if (Array.isArray(sections[monthDate])) {
sections[monthDate].push(chat); //이미 채팅데이터 있는 경우 push
} else {
sections[monthDate] = [chat]; //new 채팅 데이터
}
});
return sections;
}
최신 채팅이 아래로 올라오게 함
DirectMessage.ts
const chatSections = makeSection(chatData ? [...chatData].reverse() : []);
immutable 메서드. 빈 배열에 chatData 합쳐서 새로운 배열 생성. 그거를 reverse.
`[...chatData]` 생략가능
데이터 처리 관련 오류 발생해서 밑에서 수정!
객체 반복처리(map처리하는 방법) `Object-Entries`
{Object.entries(chatSections).map(([date, chats]) => {
return (
<Section className={`section-${date}`} key={date}>
<StickyHeader>
<button>{date}</button>
</StickyHeader>
{chats.map((chat) => (
<Chat key={chat.id} data={chat} />
))}
</Section>
);
})}
css chrome에서만 돌아가는
positon: sticky; // 평소에는 일반 요소처럼 있다가, 특정 높이가 되면 fixed처럼 바뀜. 조상컨테이너를 기준으로 top, right, bottom, left의 값에 따라 오프셋을 적용
리버스 인피니트 스크롤링(useSWRInfinite)
무한 스크롤링(default 하단 스크롤링. sleact 상단)
const onScroll = useCallback(
(values) => {
if (values.scrollTop === 0 && !isReachingEnd) {
console.log('가장 위');
setSize((prevSize) => prevSize + 1).then(() => {
// 스크롤 위치 유지
const current = (scrollRef as MutableRefObject<Scrollbars>)?.current;
if (current) {
current.scrollTop(current.getScrollHeight() - values.scrollHeight);
}
});
forwardRef : 다른 컴포넌트로 ref를 전달할 때 사용되는 기능. 주로 하위 컴포넌트에서 상위 컴포넌트의 DOM 요소에 직접적으로 접근해야 할 때 유용함
const ChatList = forwardRef<Scrollbars, Props>(({ chatSections, setSize, isReachingEnd }, scrollRef) => {
const onScroll = useCallback(
//...
<Scrollbars autoHide ref={scrollRef} onScrollFrame={onScroll}>
useSWRInfinite : 무한 스크롤링 메서드. SWR에서 제공
const { data: chatData, mutate: mutateChat, revalidate, setSize } = useSWRInfinite<IDM[]>(
//함수 => index 페이지수
(index) => `/api/workspaces/${workspace}/dms/${id}/chats?perPage=20&page=${index + 1}`,
fetcher,
);
`index + 1` 페이지 카운트, `setSize` 페이지 수 바꿔줌
isEmpty : 현재 로드된 데이터가 비어있는지 여부
isReachingEnd : 사용자가 스크롤하여 페이지의 끝에 도달했는지 데이터를 추가적으로 불러와야 할지 여부를 결정
-> 45 -> 20+20+5 : isReacginEnd
-> 40 -> 20+20+0 : isEmpty
<ChatList chatSections={chatSections} ref={scrollbarRef} setSize={setSize} isReachingEnd={isReachingEnd} />
setState 비슷한 기능. 스크롤 위치 유지 기능을 then 안에 작
채팅데이터 2차원 배열로 저장함(useSWRInfinite)
ㄴ> [...chatData].reverse 1차원 배열 reverse . 문제발생
해결? 2차원 배열을 1차원 배열로 만든 후, reverse
const chatSections = makeSection(chatData ? chatData.flat().reverse() : []);
스크롤바 조절하기 + optimistic ui
로딩 시 스크롤바 제일 아래로
useEffect(() => {
if (chatData?.length === 1) {
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 100);
}
}, [chatData]);
채팅 쳤을때 스크롤바 제일 아래로
채팅 딜레이 : 서버 post 하고, 응답다는 시간. 호출될때 딜레이 발생
-> optimistic ui : 화면에 반영 후, 서버에 요청을 보냄. (ex. 인스타그램 하트) 안정성보다 사용성을 더 중시하는 성질
mutateChat((prevChatData) => {
prevChatData?.[0].unshift({
id: (chatData[0][0]?.id || 0) + 1,
content: savedChat,
SenderId: myData.id,
Sender: myData,
ReceiverId: userData.id,
Receiver: userData,
createdAt: new Date(),
});
return prevChatData;
}, false)
2차원 배열, DM객체. shouldRevalidate : false여야함
먼저 mutateChat하고, axios 작성
현재 스크롤 높이에서 value값 빼기 -> 스크롤 위치 유지
DM 채팅하기
소켓통신 연결
useEffect(() => {
socket?.on('dm', onMessage);
return () => {
socket?.off('dm', onMessage);
};
}, [socket, onMessage]);
소켓.io로 데이터 전송
mutateChat 최신배열에 최신 데이터 넣기
Revalidate : false로 처리.
const onMessage = useCallback((data: IDM) => {
// id는 상대방 아이디
if (data.SenderId === Number(id) && myData.id !== Number(id)) {
mutateChat((chatData) => {
chatData?.[0].unshift(data);
return chatData;
}, false).then(() => {
if (scrollbarRef.current) {
if (
scrollbarRef.current.getScrollHeight() <
scrollbarRef.current.getClientHeight() + scrollbarRef.current.getScrollTop() + 150
) {
console.log('scrollToBottom!', scrollbarRef.current?.getValues());
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 50);
}
}
});
}
}, []);
scrollbar 조정 : 내가 채팅을 보내면 밑을 붙는데, 타인이 채팅 보내면 스크롤바가 내려가면 안된다
=> 일정 Height 이상 올렸을때, 스크롤바가 내려가지 않는 코드, 이하일경우 내려
탭을 두개 켜서 테스트하면, focus 클릭을 해야 화면에 반영된다? 해결방법
느낀점?
FE 개발자는 어떤 라이브러리를 얼마나 잘 활용하는가가 프로젝트의 디테일을 만든다고 생각된다.
소켓통신은 처리 알고리즘이 아직 어려운 것 같아서.. 프론트에서 소켓통신하는 간단한 플젝을 직접 준비하며 연습하고 싶다.
이번강의는 정규식이나, 라이브러리, 파라미터, 연산자 에 대한 설명이 많았던 것 같다.