본문 바로가기

전체 글

(53)
prev와 batch를 통한 상태 업데이트 React로 상태를 업데이트하다 보면 한 번쯤 이런 코드를 써본 적이 있을 것이다.setState(state + 1);setState(state + 1); 두 번 더했으니까 2가 오르겠지? 라고 기대하지만, 실제로는 그렇지 않은 경우가 많다.이 글에서는 React의 batching(배칭) 특성과 그로 인해 생기는 stale state 문제, 그리고 이를 해결하는 함수형 업데이트(prev) 패턴을, 실무에서 겪은 필터 UI 예제를 통해 정리한다. 예제 시나리오: 필터 자동 선택 + 자동 pin다음과 같은 필터 리스트가 있다고 하자.type Filter = { id: number; label: string; selected: boolean; pinned: boolean;};const initialFi..
표출할 데이터가 많은 경우 drag 버벅거리는 이슈 React Flow로 캔버스에서 테이블 컴포넌트를 드래그 하는 기능을 구현했다. 데이터가 없이 빈 컴포넌트를 드래그 하는데 아무 문제가 없었다. 그런데 데이터가 조금만 많아져도 드래그 시 성능 문제가 드러났다. 노드를 드래그할 때 마우스 커서는 잘 움직이는데, 노드는 몇 박자씩 늦게 따라오고 있었다. 즉, 드래그 속도가 크게 버벅이는 문제가 있었다. 원인: 빈번하게 렌더되는 무거운 컴포넌트노드 하나가 통째로 무거운 DOM 덩어리였다. 테이블 컴포넌트는 대략 이런 구조였다. {rows.map((row, rowIndex) => ( {columns.map((column) => ( {formatCellValue(row[column.field])} ..
key로 컴포넌트 안의 state를 초기화 하기 React는 렌더링 시점마다 Virtual DOM을 비교해서 변경된 부분만 업데이트한다. 이때 React는 이 컴포넌트가 이전에 있던 그 컴포넌트인가를 판단해야 하는데, 그 판단 기준 중 하나가 key다. 일반적으로 key는 리스트 렌더링에서 아이템을 고유하게 식별하는 용도로 많이 쓰인다. 하지만 key는 리스트 외에도 컴포넌트의 정체성을 강제로 바꾸는 트리거로 사용할 수 있다. 즉, key가 바뀌면 React는 기존 컴포넌트를 같은 것으로 보지 않고, 완전히 다른 새 컴포넌트로 취급한다. 따라서 key가 바뀌는 순간 React는 다음을 수행한다.기존 컴포넌트를 unmount한다.새로운 컴포넌트를 mount한다.그 과정에서 내부 state는 모두 초기화된다.useEffect 등 라이프사이클 로직도 다시 ..
React Query에서 두 번째 요청이 더 빠르게 느껴지는 이유 컴포넌트를 클릭하면 데이터를 요청하는 UI를 구현하면서 같은 컴포넌트를 클릭해도 첫 번째 클릭과 두 번째 클릭의 체감 속도가 완전히 다르다는 점을 알게 되었다.첫 번째 더블클릭: 로딩 스피너만 돌고, 로딩 스피너 뒤의 화면은 텅 비어 있다가 나중에 데이터 등장같은 항목을 다시 더블클릭: 스피너는 계속 도는데, 스피너 뒤에 이전 데이터가 먼저 보이고 곧 최신 데이터로 갱신됨 React Query의 상태: isLoading vs isFetchingReact Query는 로딩 단계를 두 가지로 나눈다.isLoading: 캐시에 데이터가 없어 처음 로딩 중이다.isFetching: 데이터는 있지만, 최신화를 위해 백그라운드 요청 중이다. 첫 번째 요청에서의 상태isLoading: trueisFetching: t..
mutateAsync 도입기 상황프로젝트에는 A 탭과 B 탭, 두 개의 탭이 있다. 두 탭 모두 React Flow로 구성된 캔버스를 가지고 있고, 각 캔버스의 상태를 서버에 스냅샷 형태로 저장하고 불러오도록 설계했었다.초기 구현은 단순했다. 컴포넌트가 mount될 때 서버에 GET 요청을 보내 스냅샷을 가져오고, 그 데이터를 노드와 엣지 상태로 복원한다. 그리고 컴포넌트가 unmount될 때는 현재 React Flow의 상태를 toObject()로 스냅샷 형태로 만든 뒤, 이를 POST로 서버에 저장한다.코드는 대략 다음과 같은 형태였다.useEffect(() => { // mount 시 GET refetch() .then((res) => { ... }) .catch((err) => { .....
Exclude 활용법 최근 프로젝트에서 드래그 앤 드롭 기능을 구현하면서 Exclude를 활용해볼 수 있었다. 당시의 exclude 로직은 아래와 같다.export type LogicDragPayload = | { type: Exclude } | { type: typeof BoardNodeType.TABLE.type; tableId: string; name: string }; 이 코드가 등장한 이유부터 이야기해보려 한다. 하나만 다르게 처리해야 한다 내가 진행하고 있는 프로젝트에서는 여러 종류의 컴포넌트를 드래그해 캔버스에 노드를 생성할 수 있다. 각 컴포넌트는 드래그 시 특정 데이터(Node type)를 동반하며, 대부분의 컴포넌트는 동일한 구조를 가지고 있다.하지만 TABLE 타입은 예외였다. 이 노드는 일반적인 데이..
Strict Mode와 useEffect의 반복 실행 이슈 정리 Next.js로 개발을 하다가 이상한 현상을 하나 발견했다. 분명히 컴포넌트가 한 번만 마운트될 거라 생각했는데, 콘솔에 로그가 세 번이나 찍힌 것이다. 상황다음은 테스트로 작성했던 코드다.const q1 = useTestDataQuery(1, { enabled: false });const q2 = useTestDataQuery(2, { enabled: false });useEffect(() => { // onMount -> 추후 q1 대신 GET 호출 예정 q1.refetch(); return () => { // onUnmount -> 추후 q2 대신 POST 호출 예정 q2.refetch(); };}, []); 단순히 마운트될 때 1번, 언마운트될 때 1번만 호출될 거라 예상했지만,..
const assertions 이해하기 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions TypeScript를 쓰다 보면 객체나 배열 뒤에 붙은 as const를 종종 보게 된다. 처음 봤을 때는 “그냥 const로 선언했는데 또 as const는 뭐지?” 하는 의문이 들었다. 나 역시 이 키워드가 정확히 어떤 역할을 하는지 몰랐고, 그냥 붙이는 습관처럼 사용하곤 했다. 하지만 알고 나면, as const는 타입 안정성을 높이는 데 매우 강력한 도구다. 문제 상황 다음 코드를 보자.const NodeType = { GROUP: "Groups", TABLE: "Table",}; 겉보기엔 단순한 상수 객체처럼 보이지만, TypeScript는 내부..