지금까지 우리는 React의 다양한 기능과 렌더링 방식에 대해 집중적으로 탐구했습니다. 오늘은 조금 다른 관점에서 React를 바라보려고 합니다.
바로 컴퓨터 과학의 기본 중의 기본,
자료구조(Data Structures)
입니다.
"프론트엔드 개발자가 자료구조를 알아야 하나요?" 라고 생각하실 수도 있습니다.
하지만 우리가 작성하는 React 코드, 그리고 React 라이브러리 자체의 내부 동작 방식 깊숙한 곳에는 다양한 자료구조가 효율성을 위해 사용되고 있습니다.
자료구조에 대한 이해는 단순히 이론적인 지식을 넘어, 우리가 왜 특정 방식으로 코드를 작성해야 하는지(예: 배열 업데이트 시 불변성 유지), React가 내부적으로 어떻게 상태를 관리하고 UI를 업데이트하는지 더 깊이 이해하는 데 큰 도움을 줍니다.
오늘은 React와 밀접하게 관련된 주요 자료구조들을 살펴보고, 이것들이 실제 개발과 React 내부에 어떻게 녹아 있는지 함께 알아보겠습니다.
React 컴포넌트에서 배열 데이터를 사용하여 UI 목록을 생성할 때, JavaScript 배열의 map 메서드를 사용하는 것이 가장 일반적입니다.
map 메서드는 배열의 각 요소를 순회하면서 주어진 함수를 실행하고, 그 결과로 새로운 배열을 만들어 반환합니다.
React는 이 새로운 배열(주로 JSX 요소들로 구성된)을 받아 화면에 렌더링합니다.
import React from 'react';interface Todo { id: number; text: string; completed: boolean;}const initialTodos: Todo[] = [ { id: 1, text: 'React 공부하기', completed: true }, { id: 2, text: '자료구조 복습하기', completed: false }, { id: 3, text: '블로그 글쓰기', completed: false },];function TodoList() { const [todos, setTodos] = React.useState<Todo[]>(initialTodos); return ( <ul> {/* 배열의 map 함수를 사용하여 li 요소 배열 생성 */} {todos.map(todo => ( // 각 항목에 고유한 key prop 전달이 필수! <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </li> ))} </ul> );}
은 이 key prop을 사용하여 어떤 항목이 추가, 삭제, 또는 이동되었는지 효율적으로 파악합니다.
만약 key prop이 없거나 배열의 인덱스를 key로 사용하면, 리스트 중간에 항목이 추가되거나 삭제될 때 React는 변경 사항을 정확히 추적하기 어려워 불필요한 DOM 조작을 수행할 수 있습니다. 이는 성능 저하로 이어질 수 있습니다.
항상 데이터 자체의 고유 ID를 key로 사용하는 것이 좋습니다.
을 유지하는 것은 매우 중요합니다.
배열 상태를 업데이트할 때는 원본 배열을 직접 수정하는 push, pop, splice 등의 메서드 대신, 새로운 배열을 반환하는 map, filter, reduce나 스프레드 연산자(...)를 사용해야 합니다.
React는 상태 객체나
배열의 참조
(주소값)가 변경되었는지를 비교하여 리렌더링 여부를 결정하는 경우가 많기 때문입니다(얕은 비교).
원본 배열을 직접 수정하면 참조가 변경되지 않아 React가 변화를 감지하지 못하고 리렌더링이 일어나지 않을 수 있습니다.
// ❌ 잘못된 예시: 원본 배열 직접 수정newTodos.push({ id: 4, text: '새 할일', completed: false });setTodos(newTodos); // React가 변화 감지 못할 수 있음// ✅ 올바른 예시: 스프레드 연산자로 새 배열 생성setTodos([...todos, { id: 4, text: '새 할일', completed: false }]);// ✅ 올바른 예시: filter로 항목 삭제setTodos(todos.filter(todo => todo.id !== idToRemove));// ✅ 올바른 예시: map으로 특정 항목 업데이트setTodos(todos.map(todo => todo.id === idToToggle ? { ...todo, completed: !todo.completed } : todo));
React는 실제 DOM 트리를 직접 조작하는 대신, 메모리 상에 가상의 DOM 트리, 즉
Virtual DOM 트리
를 유지합니다 (1일차).
이 Virtual DOM 트리는 JavaScript 객체로 표현되어 실제 DOM 트리보다 훨씬 가볍고 빠르게 조작할 수 있습니다.
상태가 변경되면 React는 새로운 Virtual DOM 트리를 생성하고, 이전 Virtual DOM 트리와 비교(diffing)하여 변경된 부분만 실제 DOM 트리에 효율적으로 반영합니다.
웹팩(Webpack), Vite와 같은 모듈 번들러는 프로젝트 내의 파일(모듈)들이 서로 어떻게 의존하고 있는지(import/export 관계) 파악하기 위해 내부적으로
의존성 그래프
를 생성합니다.
이 그래프를 분석하여 필요한 코드만 효율적으로 묶고 최적화합니다.
Hook 의존성
useEffect, useMemo, useCallback 훅의 의존성 배열은 해당 훅이 어떤 값(상태, props)에 의존하는지를 명시합니다.
React는 이 의존성 정보를 바탕으로 값이 변경되었을 때만 훅의 콜백 함수를 다시 실행하거나 값을 다시 계산합니다.
이를 개념적으로 의존성 관계로 볼 수 있습니다.
해시 테이블(또는 해시 맵)은 키(Key)를 고유한 해시 값으로 변환하고, 이 해시 값을 인덱스로 사용하여 값(Value)을 저장하거나 검색하는 자료구조입니다.
평균적으로 매우 빠른 검색, 삽입, 삭제 시간 복잡도(O(1))를 가지는 것이 특징입니다.
JavaScript에서는 Map 객체나 일반 객체(Object)를 통해 유사한 기능을 활용할 수 있습니다.