회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
리액트는 프론트엔드 개발에서 가장 널리 사용되고 있는 자바스크립트 라이브러리입니다. 컴포넌트 기반 아키텍처와 가상 DOM 등의 개념을 도입하였으며, 여러 글로벌 기업이 리액트를 활용하여 웹 애플리케이션을 개발하고 있습니다. 이러한 리액트의 장점을 최대한 활용하기 위해서는 리액트의 기본 개념을 이해하고 적절한 성능 최적화 기법을 적용해야 합니다. 이에 본 글에서는 리액트 개발 시 알아야 할 기본 개념을 정리해 보고, 리액트 컴포넌트와 훅, 개발자 도구 및 성능 최적화 팁을 살펴보고자 합니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
리액트는 프론트엔드 개발에서 가장 널리 사용되고 있는 자바스크립트 라이브러리입니다. 컴포넌트 기반 아키텍처와 가상 DOM 등의 개념을 도입하였으며, 여러 글로벌 기업이 리액트를 활용하여 웹 애플리케이션을 개발하고 있습니다. 이러한 리액트의 장점을 최대한 활용하기 위해서는 리액트의 기본 개념을 이해하고 적절한 성능 최적화 기법을 적용해야 합니다. 이에 본 글에서는 리액트 개발 시 알아야 할 기본 개념을 정리해 보고, 리액트 컴포넌트와 훅, 개발자 도구 및 성능 최적화 팁을 살펴보고자 합니다.
컴포넌트 기반 아키텍처는 리액트의 핵심 개념 중 하나이며, 웹 애플리케이션의 복잡한 UI를 재사용 가능한 단위로 분할하는 방식을 말합니다. 컴포넌트는 자체적으로 상태와 속성을 가지고 있으며, 독립적으로 작동합니다. 각 컴포넌트는 특정 기능이나 UI의 한 부분을 책임지며, 이를 통해 개발자는 관심사를 분리하여 개발에 집중할 수 있습니다. 또한 UI를 계층적으로 구조화하여 코드 가독성을 높이고 유지보수를 용이하게 할 수 있습니다.
컴포넌트 기반 아키텍처를 설계할 때는 구성 요소 간의 의존성을 최소화하고, 각 컴포넌트는 한 가지 책임만 진다는 단일 책임 원칙을 염두에 두어야 합니다. 즉, 컴포넌트 복잡도를 낮추고 재사용성을 높여야 합니다. 아울러, 다른 개발자가 컴포넌트를 쉽게 이해하고 사용할 수 있도록 컴포넌트 속성과 반환값(return)을 일관되게 작성해야 합니다.
리액트에서는 JSX(JavaScript XML) 문법을 사용합니다. JSX는 자바스크립트를 확장한 문법으로 HTML과 유사한 형태이며, 컴포넌트 렌더링 로직과 마크업을 한 곳에서 관리할 수 있습니다. JSX를 작성할 때는 다음과 같은 규칙을 따라야 합니다.
3) 가상 DOM(Virtual DOM)
가상 DOM(Document Object Model)은 실제 DOM을 추상화한 DOM을 말합니다. 리액트에서는 컴포넌트가 처음 렌더링 될 때 가상 DOM 트리를 생성하고, 이후 상태나 속성이 변경되면 비교(Diffing)와 조정(Reconciliation) 절차를 통해 변경된 부분만 실제 DOM에 반영(Patch)합니다. 리액트는 이렇게 가상 DOM을 이용하여 불필요한 DOM 조작을 최소화합니다.
Props와 State는 속성과 상태를 나타내며, 리액트 컴포넌트에서 데이터를 관리하는 두 가지 주요 개념입니다. Props는 Properties의 약자로 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터를 말합니다. Props는 일반적으로 읽기 전용(read-only)이며, 자식 컴포넌트에서 직접 수정할 수 없습니다.
State는 컴포넌트 내부에서 관리되는 상태 데이터를 말합니다. 컴포넌트 내부에서 직접 수정할 수 있으며, State가 변경되면 컴포넌트는 자동으로 다시 렌더링(Re-rendering)됩니다. 리액트에서는 다양한 방법으로 State를 관리합니다. 그중에서 가장 기본적인 방법이 useState라는 리액트 훅(React Hook)을 사용하는 방법입니다.
리액트 훅은 리액트 16.8 버전부터 도입되었으며, 컴포넌트 내에서 다양한 리액트 기능을 사용할 수 있게 해주는 일종의 함수 API입니다. 훅을 사용하면 컴포넌트 로직을 더욱 간결하게 작성할 수 있을 뿐만 아니라, 컴포넌트 간에 상태를 공유하거나 불필요한 렌더링을 방지하여 성능을 최적화하는 등의 역할을 수행할 수 있습니다. 리액트에서 제공하는 훅에는 다양한 종류가 있으며, 대표적으로 자주 사용하는 훅으로는 useState, useRef, useEffect, useMemo, useReducer 등이 있습니다.
1. useState
useState는 컴포넌트에서 상태를 관리하기 위한 훅입니다. useState를 사용하면 상태 값과 상태를 업데이트하는 함수를 받습니다. 아래 예제에서는 useState로 count라는 상태 값을 다루고 있습니다. 여기서 setCount는 상태를 업데이트하는 함수입니다. useState(0)는 초기 상태 값을 0으로 설정한 것입니다. 이 컴포넌트에 클릭 이벤트가 발생하면 setCount를 통해 count 값이 변경되고, 해당 컴포넌트는 다시 렌더링됩니다.
2. useRef
useRef는 컴포넌트 내에서 특정 값을 저장하고 참조할 수 있게 해줍니다. useRef로 생성한 ref 객체는 컴포넌트 생명주기 동안 유지되며, 값이 변경되어도 컴포넌트가 다시 렌더링되지 않습니다. 주로 DOM 엘리먼트에 직접 접근해야 하거나 이전 값을 저장해야 할 때 사용됩니다. useRef를 사용하여 저장한 값은 current 속성으로 접근할 수 있습니다. 아래 예제에서 useRef(null)는 초기 값이 null인 ref 객체를 생성합니다. 이 ref 객체를 input 엘리먼트에 연결하면 inputEl.current를 통해 해당 엘리먼트에 접근할 수 있습니다.
3. useEffect
useEffect는 컴포넌트의 Side Effect(부수 효과)를 처리하기 위해 사용하는 리액트 훅입니다. useEffect는 컴포넌트가 렌더링된 후에 실행되며, 두 개의 인자를 받습니다. 첫 번째 인자는 Side Effect 함수이고, 두 번째 인자는 의존성 배열입니다. 의존성 배열에 특정 값을 넣으면 그 값이 변경될 때마다 Side Effect 함수가 실행됩니다. 만약 의존성 배열을 빈 배열로 넣으면, 컴포넌트가 마운트될 때만 Side Effect 함수가 실행되며, 의존성 배열을 생략하면 컴포넌트가 업데이트될 때마다 실행됩니다. 아래 예제에서는 useEffect에 의해 count1 값이 변경될 때마다 console.log가 찍히게 됩니다.
4. useMemo
useMemo는 계산량이 많은 함수의 반환 값을 메모이제이션(memoization)하여 불필요한 중복 계산을 방지하는 리액트 훅입니다. useMemo도 두 개의 인자를 받습니다. 첫 번째 인자는 메모이제이션 할 함수이고, 두 번째 인자는 의존성 배열입니다. useMemo는 의존성 배열에 있는 값이 변경되지 않는 한, 이전에 계산된 값을 재사용합니다.
앞선 useEffect처럼 useMemo도 의존성 배열에 빈 배열을 넣으면 컴포넌트가 마운트될 때만 함수가 실행되고, 의존성 배열을 생략하면 컴포넌트가 업데이트될 때마다 함수가 실행됩니다. 위 예제에서는 useMemo를 사용하여 count 값이 변경될 때만 expensiveResult 함수를 실행하고, 그렇지 않은 경우에는 이전에 계산된 값을 재사용합니다.
5. useReducer
useReducer는 useState와 같이 컴포넌트의 상태를 관리하기 위한 훅입니다. useState는 컴포넌트 내에 상태를 업데이트하는 로직을 두어야 하는 반면, useReducer는 상태 업데이트 로직을 컴포넌트 외부에 둘 수 있습니다. 이를 통해 중복되는 상태 업데이트 로직을 한 곳에 모아 관리할 수 있습니다. 특히 여러 개의 상태를 관리해야 하거나, 프로젝트 규모가 큰 경우에 유용하게 사용할 수 있는 훅입니다.
위 예시 코드는 Counter 컴포넌트의 상태를 업데이트하기 위한 useReducer 사용 예시입니다. useReducer를 사용하기 위해서는 Reducer 함수와 Dispatch 함수가 필요합니다. Reducer 함수는 state와 action 객체를 인자로 받아 새로운 상태를 반환하며, Dispatch 함수는 action 객체를 인자로 받아 Reducer 함수를 호출하게 됩니다.
커스텀 훅(Custom Hooks)은 개발자가 직접 만들어 사용하는 훅을 말합니다. 커스텀 훅을 사용하면 컴포넌트 간에 중복되는 로직을 제거하여 코드의 가독성을 높일 수 있습니다. 커스텀 훅을 작성할 때는 use로 시작하는 함수명을 써야 하며, 커스텀 훅 내부에 useState와 같은 다른 리액트 훅을 사용할 수 있습니다. 아래 예시는 입력 필드 값을 관리하는 useInput이라는 커스텀 훅 예시입니다.
useInput은 초기값(initialValue)을 인자로 받으며, 내부적으로 useState를 사용하여 입력값의 상태를 관리합니다. 또한 handleChange 함수를 제공하여 입력값의 변경을 처리합니다. 이러한 useInput 커스텀 훅은 일반적인 리액트 훅처럼 다른 컴포넌트에서 사용할 수 있습니다. 아래 예시 코드는 InputComponent에서 useInput을 사용한 예시입니다.
리액트 훅을 사용할 때는 컴포넌트나 커스텀 훅의 최상위 레벨에서만 호출해야 합니다. 즉, 일반 자바스크립트 함수나 반복문, 조건문 내에서는 훅을 호출할 수 없습니다. 이렇게 최상위 레벨에서만 리액트 훅을 호출해야 하는 이유는 리액트가 컴포넌트 렌더링 시, 훅이 동일한 순서로 호출될 것이라고 가정하기 때문입니다. 즉, 자바스크립트 함수나 반복문, 조건문 내에서 훅을 사용하면 호출 순서가 일관되지 않아, 리액트가 정상적으로 동작하지 않을 수 있습니다.
또한 훅을 과도하게 사용하면 오히려 코드의 복잡성을 증가시킬 수 있다는 점도 주의해야 합니다. 훅을 사용할 때는 상태와 로직을 적절히 추상화하여, 불필요한 코드를 추가하지 않도록 해야 합니다. 아울러 의존성 배열을 정확히 명시하여 불필요한 렌더링을 방지해야 합니다.
리액트 컴포넌트의 종류에는 클래스형 컴포넌트와 함수형 컴포넌트가 있습니다. 클래스형 컴포넌트는 ES6의 클래스 문법을 사용하며, 상태와 생명주기 메서드를 가집니다. 따라서 클래스형 컴포넌트는 생명주기와 관련된 복잡한 로직을 구현할 때 장점이 있지만, 자칫 코드가 길어져 가독성이 떨어지고 재사용성이 낮아질 수 있습니다.
반면, 함수형 컴포넌트는 간단한 함수로 정의됩니다. 기존의 자바스크립트 함수 표현식으로 쓸 수 있고, ES6 문법인 화살표 함수를 사용해서 정의할 수도 있습니다. 함수형 컴포넌트 자체로는 상태와 생명주기 메서드를 사용할 수 없지만, 리액트 훅을 통해 상태나 생명주기와 관련된 기능을 사용할 수 있습니다.
함수형 컴포넌트는 클래스형 컴포넌트에 비해 코드가 간결하고, 테스트와 디버깅이 용이하다는 장점이 있습니다. 이러한 이유로 현재 리액트 공식 문서에서는 클래스 컴포넌트 대신 함수형 컴포넌트를 사용할 것을 권장하고 있습니다.
기본적으로 리액트에서 컴포넌트 간 데이터를 전달하는 방법은 props를 사용하는 것입니다. 보통 props를 이용하여 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달합니다. 하지만, 실무에서는 자식에서 부모 컴포넌트로 데이터를 전달해야 하는 경우도 있습니다.
비록 리액트에서는 단방향 데이터 흐름(one-way data flow)을 권장하고 있지만, 이런 경우에는 부모 컴포넌트에서 콜백 함수를 props로 전달하고, 자식 컴포넌트에서 해당 함수를 호출하는 방식으로 처리하기도 합니다. 이때 useCallback 훅을 사용하여 부모 컴포넌트가 렌더링될 때, 불필요하게 자식 컴포넌트가 렌더링되지 않도록 하는 것이 중요합니다.
웹 애플리케이션의 규모가 커지면 props를 통한 데이터 전달이 복잡해집니다. 이런 경우에는 리액트에서 기본적으로 제공하는 Context API를 이용하기도 합니다. Context API는 컴포넌트 트리 상위에서 데이터를 제공하고 하위의 어떤 컴포넌트에서든 해당 데이터에 접근할 수 있게 해줍니다.
이를 통해 props 드릴링(drilling)이 없이도 데이터를 전달할 수 있습니다. 또한 리덕스(Redux), 몹엑스(MobX), 주스탠드(Zustand) 같은 상태 관리 라이브러리를 사용하면 전역 상태를 관리할 수 있어 컴포넌트 간 데이터 전달을 더욱 용이하게 할 수 있습니다.
리액트 개발자 도구(React Developer Tools)는 메타(Meta)에서 오픈소스로 개발한 도구로서, 크롬 웹 스토어와 파이어폭스 애드온에서 다운로드 받아 설치할 수 있는 브라우저 확장 도구입니다. 리액트 컴포넌트 계층 구조를 시각적으로 보여줘 컴포넌트 간의 관계를 쉽게 파악할 수 있으며, 각 컴포넌트의 Props와 State를 실시간으로 확인할 수 있어, 데이터의 흐름을 추적하고 디버깅하는 데 필수적인 도구입니다.
리액트로 앱이나 웹사이트를 개발할 때 대부분 리액트 기반의 프레임워크를 사용하게 됩니다. 리액트 자체만으로는 코드 분할이나 라우팅, 데이터 패칭 등을 구현하기 어렵기 때문에 추가적인 도구나 라이브러리를 사용해야 합니다. 하지만 리액트 기반의 프레임워크는 이러한 일반적인 문제들을 해결하기 위한 솔루션을 제공해 주며, 개발자가 빠르게 리액트 프로젝트를 시작할 수 있도록 해줍니다.
대표적인 리액트 프레임워크로는 Next.js, Remix, Gatsby, Astro 등이 있으며, 네이티브 앱 개발을 위해서는 Expo가 있습니다. 이러한 프레임워크를 사용하면 리액트로 개발할 때 프로젝트 구조 생성과 라우팅, 캐싱, 서버 사이드 렌더링과 같은 기능을 손쉽게 구현할 수 있습니다.
컴포넌트 렌더링은 애플리케이션 성능에 큰 영향을 미칩니다. 따라서 리액트 애플리케이션 성능을 최적화하기 위해서는 먼저 불필요한 렌더링을 방지하는 것이 중요합니다. 예를 들어, 함수형 컴포넌트에서 특정 props의 변화에만 컴포넌트가 렌더링되게 하려면 React.memo를 사용하는 방법이 있습니다.
참고로 React.memo는 고차 컴포넌트(Higher-Order Component)입니다. 고차 컴포넌트란 컴포넌트를 인자로 받아 새 컴포넌트를 반환하는 함수를 말합니다. React.memo는 함수형 컴포넌트를 인자로 받아 메모이제이션 된 컴포넌트를 반환합니다. 따라서 위 예시와 같이 부모 컴포넌트에서 자식 컴포넌트와 관련이 없는 count 상태 값이 변경되더라도, 자식 컴포넌트에 대한 불필요한 렌더링이 발생하지 않습니다.
이와 같이 React.memo는 특정 props의 변경에만 렌더링되도록 조건을 설정할 수 있습니다. 다만 React.memo는 얕은 비교(Shallow Equal)를 하기 때문에, props가 함수이거나 객체인 경우에는 자식 컴포넌트의 렌더링이 발생할 수 있습니다. 이를 해결하기 위해서 아래 코드처럼 useCallback이나 useMemo와 같은 훅을 사용하기도 합니다.
useCallback과 useMemo를 사용하면 콜백 함수나 객체를 메모이제이션하여 부모 컴포넌트에서 새로운 함수나 객체 주소가 할당되더라도, 자식 컴포넌트의 렌더링을 방지할 수 있습니다. 다만 useCallback이나 useMemo와 같은 메모이제이션 훅은 추가적인 메모리가 필요하므로, 무분별하게 사용하면 오히려 성능이 저하될 수도 있으니 주의해야 합니다.
코드 스플리팅(Code Splitting)은 번들링된 자바스크립트 코드를 여러 개의 작은 조각 단위(chunk)로 분할하는 것을 말합니다. 일반적으로 리액트 애플리케이션은 모든 코드를 하나의 큰 번들로 빌드하여 배포하는데, 이는 초기 로딩 시간을 길어지게 할 수 있습니다. 반면 코드 스플리팅을 하면 필요한 코드만 동적으로 로드하여 초기 번들 크기를 줄이고, 로딩 속도를 개선할 수 있습니다. 리액트에서 코드 스플리팅을 구현하기 위해서는 React.lazy() 함수와 Suspense 컴포넌트를 이용한 레이지 로딩(Lazy Loading)을 이용하기도 합니다.
위 예제 코드에서는 React.lazy()의 동적 임포트를 통해 컴포넌트 로딩을 지연하고, Suspense 컴포넌트로 로딩 중 폴백(fallback) UI를 보여주어 레이지 로딩을 구현하고 있습니다. 이처럼 레이지 로딩을 적용하면, 애플리케이션의 초기 로딩 속도와 성능을 최적화할 수 있습니다.
지금까지 리액트 개발 시 알아두어야 할 기본 개념과 성능 최적화 팁을 살펴보았습니다. 특히 리액트의 핵심 개념인 컴포넌트 기반 아키텍처, JSX 문법, 가상 DOM, Props와 State, 클래스형 컴포넌트와 함수형 컴포넌트의 차이점 등을 중점적으로 다뤘습니다.
더불어 리액트 훅, 리액트 개발 도구, 리액트 프레임워크에 관한 내용도 함께 정리했습니다. 실무에서 활용할 수 있는 성능 최적화 방법으로는 React.memo와 메모이제이션 훅, 코드 스플리팅을 위한 레이지 로딩 구현 방법을 살펴봤는데요. 이번 글에서 정리한 내용이 현재 리액트를 사용하고 있거나, 이제 막 리액트 공부를 시작하는 개발자분들에게 도움이 되길 바랍니다.
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.