05. Re: Props부터 시작하는 새로고침하지 않는 생활

Gardener·2024년 2월 28일

Prog_PRJ

목록 보기
5/6

위의 페이지들은 내가 구성한 업무와 회고 페이지이다. 이 페이지들은 생각보다 더 복잡하게 구성이 되어있는데,, Component화가 무조건 장땡인 줄 알고, 거의 아주 사소한 단위의 부분들을 다 Component화 시켰기 때문이다..!

위 사진이 이제 내가 만든 무척이나 많은 Component들이다.. React의 가장 큰 특징 중 하나라면 바로 SPA 를 들 수 있을 것이다. Single Page Application이라는 뜻을 가진 React는 하나의 데이터가 등록이 되었을 때, 페이지의 UI는 바로 반영되어야한다. 하지만 나는 이제 실제 데이터를 받아보다가 하나의 개인적 위기를 봉착하고 말았는데, 바로 CRUD 작업을 진행했을 때 데이터가 실시간으로 반영되지 않는 것이였다. (하지만, 새로고침을 하면 된다,, 무조건 될 수 밖에 없지만)

문제

하나의 페이지를 여러개의 Component로 구성했을 때, CRUD를 진행했을 때, 데이터가 바로 바인딩 되지 않는 현상

Props에 대한 숙련도 부족과, 기획 및 설계하지 않고 무턱대고 시작해버리는, 안 좋은 습관들 때문에 이러한 문제가 발생했다고 생각한다. 사실 이렇게 Component화 시키지 않고, Hook들로 따로 뺀다거나, 한 페이지 안에 합쳐 버리는 것도 존재했었다. 압도적인 Component를 제작하는 방식이 좋다고 판단했음.

1차적 해결

1차적 해결으로는 역시 CRUD가 실행된 뒤, 강제 새로고침을 하는 명령을 내리는것이었다.

const writeKPT = async () => {
		if (profile && projectId && detailCodesData && detailCodesData.length > 0) {
			// 수정: 상태 검사를 배열 길이로 변경
			const selectedDetailCode = detailCodesData.find(
				(d: DetailCodeData) => d.detailName === selectedSection,
			) as DetailCodeData;
			if (!selectedDetailCode) {
				return;
			}

			const numPId = projectId;

			const KPTdata = {
				projectId: numPId,
				memberId: profile.id,
				kptCode: selectedDetailCode.id,
				week: 1,
				content: textareaValue,
			};

			try {
				await axiosInstance.post('/retrospects', KPTdata);
				closeModalAndClearText();
				window.location.reload();
              	// onKPTUpdate(); <- 사실 props drilling 을 통해 내린 이 부분이 답이다. 현재는 잠시 주석
			} catch (error) {}
		}
	};

이를 사용했을 때, 어색하게 화면이 깜빡거리며 새로고침되어 UI가 변경되는 현상이 있었다. 하지만 이 방법은 팀원들에게 너무 티가 나는 방식이었다!! 나는 바로 들켜버렸음. 또한 CRUD 명령이 일어날 때마다 화면을 새로고침하는 방식은 SPA에서 권장하지 않는 방식이었다.

최종 해결

이제는 시연이 얼마 남지 않아, 확실한 방법을 찾을 때가 되었다. 방법은 알고 있었지만 (바로 상태관리), 지금은 프로젝트의 마지막 주간. 상태관리를 급하게 공부할 시간이 없었다. 금방 찾을 수 있었다. 답은 Props Drilling이었다.

Props Drilling : React에서 데아터를 전달하기 위해 필요한 과정
데이터를 하위 컴포넌트로 계속해서 전달해주기 위해 중간 컴포넌트를 통해 프로퍼티를 계속해서 내려주는 것.

파악한 현재의 문제 : CRUD 작업을 진행한 후에 -> getKPT()를 통해서, 현재의 회고를 바로바로 불러와야 했음. 하지만 컴포넌트를 너무 많이 생성한 나머지, 중간 컴포넌트가 많아졌다.

그렇다면, 나는 Props Drilling을 통해, 데이터를 내려줘야했다. 과정을 간단하게 설명하겠다~
먼저 최상위 컴포넌트에서, props로 내릴 data를 선택한다.

// retrospectPage.tsx
<div className='flex grow kp-box'>
	<KeepBoard memos={kptData.Keep} onKPTUpdate={getKPT} />
	<ProblemBoard memos={kptData.Problem} onKPTUpdate={getKPT} />
</div>
<div className={'mt-8 t-box'}>
	<TryBoard memos={kptData.Try} onKPTUpdate={getKPT} />
	<KPTMemo modalOpen={modalOpen} setModalOpen={setModalOpen} onKPTUpdate={getKPT} />
</div>

이 구문이 존재하는 위치는 최상위 retrospectMemo.tsx 파일이다. 나는 회고 페이지를 OOOBoard들로 구분했고, 각각의 Board안에는 OOODetail로 구성되어있다. 이 OOODetail. 페이지에서 데이터에 대한 CRUD 작업이 이루어지도록 로직을 구성했다. 그래서 Props를 설정하여 계속해서 데이터를 주고받지 않으면 컴포넌트간 데이터 교환이 이루어지지 않아 즉각적인 SPA의 UI 반영이 이루어지지 않았던 것. 그럼 이제부터 props의 흐름을 따라가보자.

코드 구문들의 일부이다.

//keepBoard.tsx
interface BoardProps {
	memos: Memo[];
	onKPTUpdate: () => void;
}

<KeepDetail
	isOpen={showDetailModal}
	onClose={() => setShowDetailModal(false)}
	memos={memos}
	onKPTUpdate={onKPTUpdate}
/>
//keepDetail.tsx
interface KeepDetailProps {
	isOpen: boolean;
	onClose: () => void;
	memos: Memo[];
	onKPTUpdate: () => void;
}

	const modifyMutation = useMutation({
		mutationFn: (data: { retrospectId: number; text: string }) => modifyKPT(data),
		onSuccess: () => {
			queryClient.invalidateQueries({ queryKey: ['memos'] }).then(() => {
				onKPTUpdate();
			});
			setEditingId(null); // 수정이 성공적으로 완료된 후 편집 모드 종료
		},
	});

	const deleteMutation = useMutation({
		mutationFn: (retrospectId: number) => deleteKPT(retrospectId),
		onSuccess: () => {
			queryClient.invalidateQueries({ queryKey: ['memos'] }).then(() => {
				onKPTUpdate();
			});
		},
	});

이제 props를 통해 모두 전달했다. 그렇다면 다시 최상단의 getKPT()를 고칠시간. window.location.reload를 지우고, onKPTUpdate()를 추가해주면 간단하다. 이 함수를 계속해서 불러오기 위해서 데이터를 그렇게 내렸던 것.

// 다시 retrospectMemo.tsx
	const getKPT = async ()=> {
		if (!isAuthenticated) return; // 인증이 완료되지 않았다면 함수를 종료한다.

		try {
			const response = await axiosInstance.get(`/retrospects/${projectId}`);
			setKptData(response.data.data);
		} catch (error) {}
	};

	useEffect(() => {
		if (!isLoading) {
			getKPT();
		}
	}, [isLoading, isAuthenticated, projectId]); // 의존성 배열에 isLoading과 isAuthenticated 추가

이제 getKPT() 는 useEffect를 통해 projectId에 변화가 생길 때마다 새로 렌더링될 수 있다.

요약하자면, 상위 컴포넌트에서 하위 컴포넌트까지 필요한 data (ex : 함수)를 props 시키고, 이를 typeScript를 통해서도 지정해준다. 보통 반환값이 없다면 () => void.
이는 새롭게 렌더링하기 위해서 useEffect를 활용하여 상태가 달라질 때마다, 새롭게 렌더링될 수 있도록 한다.

느낀 점

  1. 사실 2번째 프로젝트에서 잠시 널널한 기획 및 설계 주간에 이 글을 작성하고 있는데, 팀원들과 이야기해보면서 내가 TypeScript에 대한 이해도가 낮다고 생각하게 되었다.
  2. 또한, 이러한 전체적인 과정은 상태관리에 대한 이해도가 있다면, 내가 간편히 처리할 수 있는 것들이었다. 이런 복잡한 과정을 일일이 기술하지 않기 위해 상태관리도구가 있기도 하고,, 이에 대한 공부가 부족해서 발생한 현상이기에, 이번 프로젝트에서 확실히 해보자!!

타입스크립트와 리액트 상태관리를 확실하게 공부하는 것이 2번째 프로젝트의 목표이다.

profile
영혼의 정원수

0개의 댓글