1. 담당
페이지
- 로그인
- 회원가입
- 채팅
컴포넌트
- 로딩 스피너
- Input
- 채팅 item 컴포넌트
- 온라인 사용자
- 전체 사용자
1. 로그인/회원가입
라이브러리를 사용하지 않고 직접 구현
Input 컴포넌트 설계
(1) Input 컴포넌트의 기능
- 에러 메시지가 있는 경우
- 에러 메시지가 없는 경우
- 아이콘이 필요한 경우
고민: 구체적으로 UI 디자인을 설정하지 않아서 추가적인 UI 수정이 필요했고, 기능을 미리 정의하지 않아서 발생
▶ 예: label과 placeholder글자 크기 차이
▶ 예: 에러 메시지를 보여주는 시점
(1) 한글자씩 입력할 때마다 (2) 입력을 완료한 후 focus를 잃은 시점 (3) 폼 submit 클릭 후, 서버에 전송하기 전
▶ 예: 에러 메시지를 Input 컴포넌트 안에 위치할지, form 에서 관리할지
▶ 예: 에러 메시지가 있을 때와 동일하게, 에러 메시지가 없을때에도 form이 움직이지 않도록 여분의 여백이 필요
(2) 렌더링 횟수 줄이기 위한 방법
- 디바운싱
- 제어 컴포넌트와 비제어 컴포넌트
고민: 리렌더링을 줄이기 위해 적용한 디바운싱. 입력 내용이 즉시 반영되어야 하는 Input 특성과 디바운싱은 어울리지 않았다.
▶ 예: timeout 시간 내에 전송 버튼을 (매우 빠르게) 클릭하는 경우 입력한 내용과 제출 내용이 상이해지는 문제 발생
해결: 디바운싱 코드를 삭제하고, 내용을 작성할 때마다 검사
사용자 입장에서는 어떤 방법이 좋을까? 를 고민하면서
즉각적으로 피드백을 하는게 편리할 것 같다는 생각에서 작업했다.
그러나
form이 커진다면.. 그리고 form의 요소가 다른 요소에도 영향을 미치게 된다면 현재의 방식은 렌더링에 문제가 생길 것으로 예상된다.
수정을 한다면, state로 관리하지 않고 ref를 사용해서 onBlur일 때 검사하는 것으로 수정해볼 수 있을 것 같다.
추가적으로
라이브러리는 어떻게 구현되어있는지 궁금해서
react-hook-form 및 formik 라이브러리의 코드 확인하면서 form 탐구
https://npmtrends.com/formik-vs-react-hook-form
https://ko.react.dev/learn/sharing-state-between-components
(3) state의 타입 (string ? object? object라면 어떤 상태끼리 묶을 것인지?)
https://ko.react.dev/learn/choosing-the-state-structure
- 이름, 이메일, 비밀번호, 비밀번호 확인 각각을 string 타입의 state로 관리
- 이름, 이메일, 비밀번호, 비밀번호 확인 모두를 하나의 객체로 관리
▶event 객체로 target.name을 받기 때문에, 이름, 이메일, 비밀번호, 비밀번호 확인 handler를 하나로 통합할 수 있다.
▶예: setState({ ...사용자입력, [event.target.name]: event.target.value})
- 이름, 이메일, 비밀번호, 비밀번호 확인 및 에러 메시지를 어떤 구조로 관리할지
- 다양한 에러 메시지 렌더링 방법: 우선순위 적용
▶ 예:
(1) 빈 값인 경우 "입력해 주세요"
(2) 유효성 검사 에러 메시지 "올바르지 않은 이메일 형식이에요"
(3) 비밀번호 상호 체크 에러 메시지 "비밀번호가 일치하지 않아요"
- 특히 빈 값인 경우, 가장 처음에는 모두 빈 값이지만 오류 메시지가 화면에서 없어야 했다.
2. 로딩 스피너
디자인적 감각이 없던 나로서는 컴포넌트를 사용하는 사람이 자유롭게 사용하도록 커스텀이 가능하게 props를 지정했다.
- (수정 전) 자유도가 높았던 컴포넌트
▶ 예: 원의 크기, 두께, 색상, 회전 속도 모두 개발자가 props로 설정 "가능"
하지만, 크기에 따라 몇가지 규격(sm, lg 등)의 컴포넌트를 만들어야
서비스 내에서 크기에 대한 일관성이 생기고 사용하는 입장에서도 편리하다는 것을 배웠다.
- (수정 후) 컴포넌트의 타입을 규정. 예: 'sm', 'lg'
추후에도 컴포넌트를 미리 어떤 상황에서 쓰이는지 정의하고 크기를 규정하면 좋을 것 같다.
(버튼)컴포넌트 안에 들어가는 스피너와 달리,
- 페이지 로딩의 경우에는 position을 지정해서 렌더링 해야 할 필요성이 있었다.
- 서버에서 받은 데이터의 상태에 따라(loading, success 등) 로딩 스피너가 필요했는데, 레이아웃 시프팅 발생하기 때문에 dom에서의 위치에 종속되면 안되었다.
3. 리액트 답게 코드를 작성하는 것에 대한 고민
- 코드리뷰 중, document.appendChild 등의 메서드에 대한 의문
- 리액트 VDOM을 활용하지 못하는 것 같다는 생각이 들었다.
- DOM 요소를 추가하는 건 DOM을 직접 조작하는 것인데, 그렇다면 리액트가 예측하지 못하는 상황이 발생하지 않을까?
그렇다면 모달을 DOM에 어떻게 붙이지?를 떠올리면서
리액트에서의 렌더링이란 무엇일까를 새삼스럽게 깨달았던 지점이었는데,
JSX로 단순히 DOM 요소와 연결하는 건 직관적이어도 모달의 경우에는? createPortal로 body에 붙이면 된다.
버튼과 함께, JSX에서의 위치는 크게 상관하지 않았다.
https://ko.react.dev/reference/react-dom/createPortal
4. 채팅 페이지
https://ko.react.dev/reference/react/forwardRef
- textarea태그를 사용할 때, 입력 할 때마다 리렌더링을 방지하기 위해 ref로 html element 값을 가져왔다.
- API 호출로 수행해야 하는 것
(1) 내가 입력한 내용을 서버에 전달
(2) 내용 읽음 처리 전달
(3) 전체 데이터 목록 가져오기
- 호출은 비동기로 동작했기 때문에, 어떤 내용이 먼저 완료되어야 하는지 예측할 필요가 있다고 생각했고
- async await으로 순서대로 실행
(1) API 호출 순서
[O] 메시지 읽음 처리는 내가 상대방의 메시지를 보기 전에 처리되기
[X] 메시지 읽음 처리는 내가 상대방의 메시지를 본 후에 처리되기
[O] 메시지 전송은 전체 데이터 목록을 보기 전에 처리되기
[X] 메시지 전송은 전체 데이터 목록을 본 후에 처리되기
[X] 전체 데이터 목록 호출은 내가 상대방에게 메시지를 보내기 전에 처리되기
[O] 전체 데이터 목록 호출은 내가 상대방에게 메시지를 보낸 후에 처리되기
그리고 setInterval로 데이터를 최신화 한다.
(2) 스크롤
추가로 데이터가 새로 추가되었을 때 스크롤이 가장 하단으로 이동하도록 기능을 추가했다.
이 때 스크롤의 위치는 client, scroll의 길이를 계산한 결과여야 했는데
중요한건 "어떤 시점"에 스크롤을 최 하단으로 내릴 것인지를 결정해야 했다.
그렇지 않으면 interval마다 스크롤이 하단으로 내려와서 이전에 데이터를 매우 빠르게... 스크롤 해야 보였기 때문
그래서, 메시지 길이가 이전과 같다면 스크롤 하지 않고, 메시지 길이가 이전과 다른 경우에만 조건을 분기해서 스크롤을 이동했다.
이는 렌더링시마다 변하는 state로 저장하지 않고 ref로 저장해서 해당 길이를 기록했다.
https://www.w3schools.com/react/react_useref.asp
이전에 봤던 위 예제를 조금 응용
(3) 로딩 상태
채팅을 하면서도 로딩 상태일 때 스피너를 붙였었는데..
컴포넌트가 dom에서 position을 갖게 되면 레이아웃 시프팅으로 인해 화면이 움직였다.
그래서, 스피너의 경우 position을 dom 위치에 영향을 받지 않게, 분리해야 겠다는 생각을 하게 된 계기가 되었다.
5. 현재 온라인 상태인 사용자/ 전체 사용자
6. 그 외
custom hook
커스텀 훅 안에 커스텀 훅이 있는 경우
이 부분은, 데이터가 반영이 안 되었던 이슈가 있었다.
처음엔 의아했지만 곧바로 이전에 공식 문서를 읽으면서 봤던 내용을 떠올려서 금방 해결할 수 있었다.
const [request, data, .. ] = useRequest()
const handler = () => {
request()
setState(data)
}
request와 data는 이전의 스냅샷을 참조한다.
따라서, request를 통해 data가 갱신되었다고 하더라도, handler는 이전의 data를 바라보기 때문에 set이 되지 않았던 문제.
useEffect를 사용하면서 해결할 수 있었다.
렌더링
궁금증은 커스텀 훅 내부에 사용되는 상태가 변경되면? 에서 시작했다.
해당 상태를 정의했던 컴포넌트가 먼저 실행될까?
해당 상태를 반환 받아 사용하는 부모 컴포넌트에서 실행될까?
(1) 렌더링의 순서
(2) 호출 순서
결론을 말하자면,
렌더링, 즉 상태가 바뀌면 과연 어디서부터 렌더링이 시작되는 걸까?
▶ 해당 상태를 사용하는 가장 상위의 부모 컴포넌트부터 리렌더링 된다.
▶ 그리고 차례차례로 커스텀 훅.. 들이 리렌더링 되고, (함수 내부가 실행되는 순서와 같다.)
▶ 리렌더링이 모두 끝난 후에는 useEffect가 호출된다. useEffect도 당연하게도 가장 위에 있는 useEffect부터 호출된다.
공식 문서에서는 useEffect를 사용하지 않을 수 있다면 사용하지 말라는 섹션이 있다.
https://ko.react.dev/learn/you-might-not-need-an-effect
코드의 흐름을 예측하기 어렵기 때문인데
이번에 굉장히 헷갈렸던 부분...
컴포넌트의 state 유지가 가능한 이유
그리고 또 궁금했던 부분이 컴포넌트의 데이터가 유지되는 이유, 그리고 key의 역할이었다.
https://ko.react.dev/learn/preserving-and-resetting-state
글을 쭉 읽어보면서 아래 둘의 차이점이 특이했다.
axios의 interceptor
- token
recoil
prettier setting
- 팀원 전체의 vscode prettier 설정이 일관되게 동작하기 위해 .vscode 폴더 생성
- settings.json 파일에 prettier.requireConfig 설정 추가
컨벤션
- commit tag 자주 사용되는 태그들을 열거해서, 팀원들과 상의하여 선택할 수 있도록 준비
- Issue, PR Template 작성
- GitHub의 project로 칸반보드 형식의 Task 관리 제안
페이지 분기처리
초기에 호출할 때 custom hook에서 반환된 초기 데이터 null에 대한 분기 처리
Fragment 와 '<></>'
차이는 key
https://ko.react.dev/reference/react/Fragment
rem
'개발 활동 > 프로그래머스' 카테고리의 다른 글
3차 프로젝트 회고 (0) | 2024.01.31 |
---|---|
MIL 12-1월 (1) | 2024.01.23 |
12월 MIL (0) | 2023.12.25 |
유효성 검사 함수 리팩토링 과정 (1) | 2023.11.26 |
[MIL] 2번째 (0) | 2023.11.22 |