메신저 웹(2/2)
🍄 이전 블로그 포스트 (1/2) 링크
💊 기능 구현 및 TIL
메시지 답장 기능
답장 기능의 경우, 답장하기 버튼을 클릭하면 특정 문자열이
textarea
에 입력되어야 했다.
이렇게 메시지 박스 안에 내가 답장하고자 하는 메시지의 기본 정보가 자동으로 textarea
에 입력되어야 한다.
이를 위해 해당 정보를 담기 위한 replyObj
라는 state
를 생성했고, state
의 컨트롤을 위한 action
과 reducer
를 생성했다.
내가 이 기능을 구현하기 위해 구상한 로직은 이하와 같다.
답장하고자 하는 메시지 내에서 답장 버튼을 클릭한다 (
onClick
)컴포넌트에서 해당 메시지의 기본 정보 (메시지 작성자 이름, 메시지 정보)를
action
의payload
에 담아 호출한다.replyObj
state
가 리듀서에 의해 업데이트 된다.1
2
3
4
5
6
7
8
9// redux/actions/setReplyContent.ts (Action)
import { ReplyUser } from 'types';
import { SET_REPLY_CONTENT } from './types';
export const setReplyContent = (replyObj: ReplyUser | null) => ({
type: SET_REPLY_CONTENT,
payload: replyObj,
});textarea
가 있는 컴포넌트에서replyObj
의 변화를 리스닝 하도록useEffect
를 활용한다.위의 회신 기본 템플릿을 상수에 담아 textarea에 담아준다.
그리고
replyObj
state
를null
로 업데이트 한다.1
2
3
4
5
6
7
8
9
10
11
12
13
14// components/ChatForm.tsx (textarea가 있는 컴포넌트)
useEffect(() => {
// 만약 replyObj가 null이 아니라면?
if (replyObj) {
// 1. 템플릿을 생성하고 (회신자 이름, 내용, (회신))
const replyTemplate = `${replyObj.userName}\n${replyObj.content.text}\n(회신)\n`;
// 2. 이를 textarea의 value와 연결된 state에 업데이트 시켜준다.
setText(replyTemplate);
// 3. 조건문의 단발성 실행을 위해 replyObj는 다시 null로 업데이트 해준다.
dispatch(setReplyContent(null));
return;
}
}, [replyObj, dispatch]);
textarea에서 Enter 개행 방지하기
쉬울 것 같았지만 생각보다 헤맨 부분이었다
textarea
에서는 기본적으로 Enter키와 Enter + Shift키를 통해 개행이 가능하다. 하지만 shift + enter를 통해서만 개행이 되도록 하고, Enter키를 눌렀을 때는 개행이 아닌 submit이 실행되도록 이벤트 핸들러 함수를 구현해야 했다.
따라서 Enter 키를 감지하기 위해 form태그에 onKeyDown이벤트에 대한 핸들링 함수를 구현해줬다.
1
<form onKeyDown={(e) => handleKeyPress}></form>
엔터키 입력 감지
1
2
3
4
5
6
7
8
9const handleKeyPress = (e: React.KeyboardEvent<HTMLFormElement>) => {
// 만약 엔터키가 눌렸고, shift키는 눌리지 않았다면? (즉, 엔터키만 눌린 상태라면?)
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
// 미리 선언해둔 submit 버튼에 대한 ref를 불러와 click()이벤트를 실행시킨다.
buttonRef.current?.click();
return;
}
};
textarea의 높이 동적으로 세팅하기
위에서의 답장 template이
textarea
에 세팅되었을 때, 그 템플릿의 라인 수 만큼textarea
의 높이가 높아지도록 구현해야 했다.
기존에 팀원 주영님이 textarea
에 값이 입력되며 개행이 될 때마다 textarea
의 높이가 높아지도록 구현을 해주셨으나, setState
에 의해 textarea
에 값이 입력되는 것은 onChange
로 인식되지 않는 것 같았다.
이 때문에 답장 버튼 클릭 후 템플릿이 입력됐을 때 템플릿의 라인 수 만큼 textarea
의 높이값을 세팅해주는 작업이 필요했던 것이다.
useRef
를 통해textArea
에 대한 ref를 생성해준다.1
const textAreaRef = useRef < HTMLTextAreaElement > null;
정규표현식을 사용하여 (개행되어야 하는 라인 수 * 인풋의 높이)만큼
height
값을 설정해준다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15useEffect(() => {
if (replyObj && textAreaRef.current) {
const replyTemplate = `${replyObj.userName}\n${replyObj.content.text}\n(회신)\n`;
setText(replyTemplate);
// 개행되는 라인 수를 카운트 하기 위해'\n'의 개수를 String.match()를 통해 계산해준다.
const lineBreakRegex = new RegExp('\\n', 'g');
const lineBreakCount = replyTemplate.match(lineBreakRegex)!.length;
// textarea가 자동으로 포커스 되도록!
textAreaRef.current.focus();
// 한 줄당 기본 hieght값인 20px * 개행되어야 하는 수 +1
textAreaRef.current.style.height = (lineBreakCount + 1) * 20 + 'px';
dispatch(setReplyContent(null));
return;
}
}, [replyObj, dispatch]);
reducer에서 state와 action의 type 지정하기
초기에 Redux에서 작업할 것들이 많아
action
에any
타입을 지정해준 뒤에 작업을 진행했었는데, 이를 수정하는 과정에서 많은 것들을 배웠다.
우리가 상수 또는 state
에 명확한 타입을 지정해 주듯이, action
의 type
과 payload
에 대해서도 명확한 타입을 지정해주어 오류 발생의 가능성을 최소화 해야 한다.
action
의 기본 구조는 이하와 같다.
위에서 설명했듯이, 이에는 type
과 payload
가 들어가 있기 때문에 해당 액션이 사용되는 reducer
에서 액션에 대한 명확한 타입지정을 해줘야 한다.
아래의
reducer
코드를 봐보자
1 | // action에 대한 인터페이스! |
위 코드를 보면 알 수 있듯이 GET_CONTENT
에 해당하는 액션의 모든 값들을 명확하게 타입지정 해줬다. 이렇게 타입을 지정해줘야 내가 액션을 실행할 때 원치 않는 데이터가 담겨져 state
가 의도하지 않은대로 업데이트 되는 오류를 최소화 할 수 있다.
근데 설명 없이 위의 솔루션 코드만 보면 “**type에 왜 typeof를 써줬지?**” 라는 의문이 들 수 있다.
위의 예시에서 사용한 GET_CONTENT
는 하나의 value이다. 즉 GET_CONTENT
자체는 하나의 value이지, string
이 아니기 때문에 GET_CONTENT
를 바로 선언해주면 안되고, 앞에 typeof
를 붙여줘야 인터페이스가 제 기능을 할 수 있는 것이다.
string
타입과 리터럴
타입 (GET_CONTENT
)는 명확히 다르다는 것을 분명히 짚고 넘어가야 한다…!!