반응형

리액트 훅 완전정복! 초보자를 위한 주요 훅 사용법 가이드

리액트 초보자라면 꼭 알아야 할 필수 훅들!
직접 써보고 '와, 이게 진짜 편하네' 했던
그 기능들만 모았습니다.
반응형

 

안녕하세요!

리액트를 공부하다 보면 꼭 만나게 되는 개념 중 하나가 바로 "훅(Hook)"이죠. 함수형 컴포넌트 안에서 상태를 관리하거나 생명주기 효과를 처리할 때 빠질 수 없는 친구인데요.

이번 글에서는 useState, useEffect, useRef, useMemo, useCallback, useContext 등 자주 사용되는 훅들을 초보자 눈높이에 맞춰 쉽게 설명해드릴게요.

직접 써보며 헷갈렸던 부분까지 모두 정리했으니 끝까지 함께 해주세요!

 

1. useState 🧠 상태를 다루는 가장 기본 훅

useState는 리액트 훅 중에서 가장 자주 사용되는 기능 중 하나예요.

컴포넌트 안에서 변하는 값, 예를 들면 버튼 클릭 횟수나 입력 필드의 내용 등을 저장하고 관리하는 데 사용됩니다.

기존의 클래스형 컴포넌트에서 this.statethis.setState()를 사용하던 것을 함수형 컴포넌트에서 더 간단하게 구현할 수 있게 해주는 거죠!

📌 기본 문법

const [상태변수, 상태변경함수] = useState(초기값);

🧪 실습 예제

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

💡 사용 시 팁

  • 여러 개의 상태가 필요한 경우, useState를 여러 번 선언해도 전혀 문제 없어요!
  • 상태가 바뀌면 해당 컴포넌트는 자동으로 다시 렌더링돼요. 이걸 잘 활용해야 렌더링 최적화에 도움이 돼요.

📋 요약 정리

항목 내용
사용 목적 컴포넌트 내 상태값 관리
초기값 숫자, 문자열, 객체 등 다양한 자료형 가능
업데이트 방식 전용 함수(set함수)를 통해 변경

처음 리액트를 접하면 상태라는 개념이 어렵게 느껴질 수도 있지만, useState만 잘 이해해도 리액트 앱의 절반은 완성한 셈이에요!

 

다음은 useEffect를 통해 컴포넌트의 생명주기를 어떻게 다룰 수 있는지 알아볼게요.

 

 

2. useEffect 🔁 생명주기 대체 훅

리액트 함수형 컴포넌트는 생명주기 메서드가 없죠.

그 대신 useEffect를 사용하면 componentDidMount, componentDidUpdate, componentWillUnmount처럼 동작하게 만들 수 있어요.

 

예를 들어,

컴포넌트가 처음 화면에 나타날 때 데이터를 불러온다거나, 특정 값이 바뀔 때마다 API 요청을 보낸다거나, 또는 컴포넌트가 사라질 때 정리 작업(clean-up)을 해야 할 때 아주 유용합니다.

📌 기본 문법

useEffect(() => {
  // 실행할 코드
  return () => {
    // 정리(clean-up) 코드
  };
}, [의존성 배열]);

🧪 실습 예제

import { useEffect, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <p>{seconds}초 지났습니다.</p>;
}

💡 동작 방식에 따른 분기

의존성 배열 실행 시점
[] 컴포넌트 최초 렌더링 시 한 번만
[count] count 값이 바뀔 때마다 실행
의존성 없음 모든 렌더링마다 실행

📣 주의할 점

  • 의존성 배열을 제대로 설정하지 않으면 무한 루프에 빠질 수 있어요!
  • 정리(clean-up) 함수는 컴포넌트가 사라질 때뿐만 아니라 다음 실행 전에 호출되므로 유의해야 해요.

이제 useEffect를 통해 비동기 작업이나 외부 이벤트도 자유롭게 처리할 수 있어요.

 

다음으로는, DOM을 직접 제어하거나 변경되지 않는 값을 저장할 수 있는 useRef를 알아보러 가볼까요?

 

 

3. useRef 🔍 DOM 제어와 값 기억에 강력

useRef는 리액트에서 아주 유용하지만, 처음엔 그 쓰임새가 헷갈릴 수 있는 훅이에요.

주로 DOM 요소를 직접 제어하거나 렌더링 사이에도 유지되어야 하는 값을 저장할 때 사용해요.

이 훅의 특징은 값을 변경해도 컴포넌트를 다시 렌더링하지 않는다는 거예요.

그래서 카운트 같은 상태는 useState를 쓰지만, 변경되더라도 화면에 영향을 주지 않아야 하는 값은 useRef를 쓰는 게 더 적절해요.

📌 기본 문법

const myRef = useRef(초기값);

// 접근: myRef.current

🧪 DOM 조작 예제

import { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef();

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>포커스 주기</button>
    </div>
  );
}

🧠 상태 저장 예제 (렌더링 방지)

import { useRef, useState, useEffect } from 'react';

function RenderCount() {
  const [value, setValue] = useState('');
  const count = useRef(1);

  useEffect(() => {
    count.current += 1;
  });

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>렌더링 횟수: {count.current}</p>
    </div>
  );
}

📋 요약 정리

기능 설명
DOM 접근 ref로 지정한 요소에 직접 접근 가능
렌더링 영향 없음 값 변경 시 컴포넌트 재렌더링 발생 안 함
유지되는 값 렌더링 사이에 값을 유지하고 싶을 때 사용

useRef는 보이지 않는 메모장 같아요.

화면에 영향을 주지 않으면서 내부 데이터를 보관할 수 있거든요.

 

이 다음엔 계산이 무거운 작업을 최적화하는 useMemo로 넘어가볼게요!

 

 

4. useMemo 📦 계산 결과를 메모이제이션

리액트에서 어떤 연산이 너무 자주 반복되면, 앱이 느려지는 원인이 될 수 있어요.

useMemo는 이런 상황에서 비용이 많이 드는 계산을 기억해두고, 필요할 때만 다시 계산하게 도와줘요.

쉽게 말해서,

어떤 계산된 값이 매번 바뀌지 않는데도 계속 다시 계산되는 게 비효율적이라면,

useMemo로 감싸서 “얘는 값 안 바뀌면 계산 다시 안 해!”라고 알려주는 거예요.

📌 기본 문법

const memoizedValue = useMemo(() => {
  return 복잡한계산(입력값);
}, [의존성값]);

🧪 실습 예제

import { useState, useMemo } from 'react';

function ExpensiveComponent() {
  const [number, setNumber] = useState(1);
  const [text, setText] = useState('');

  const factorial = (n) => {
    console.log('계산 중...');
    return n <= 1 ? 1 : n * factorial(n - 1);
  };

  const memoizedFactorial = useMemo(() => factorial(number), [number]);

  return (
    <div>
      <p>{number}! = {memoizedFactorial}</p>
      <button onClick={() => setNumber(prev => prev + 1)}>증가</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

💡 사용 시 주의할 점

  • 무조건 쓰면 성능이 좋아지진 않아요! 진짜 계산이 무거운 경우에만 쓰는 게 좋아요.
  • 의존성 배열을 잘못 설정하면 오래된 값을 계속 사용하게 될 수도 있어요.

📋 요약 정리

기능 설명
계산 결과 캐싱 의존값이 변하지 않으면 이전 계산 결과 재사용
최적화 대상 복잡하거나 반복되는 계산 작업
렌더링 영향 렌더링마다 계산 방지 가능

정말 계산이 무거운 로직에만 쓰는 게 핵심이에요.

불필요한 useMemo 남용은 오히려 독!

 

다음은 콜백 함수 자체를 메모이제이션해서 불필요한 재렌더링을 막는 useCallback 훅을 알아봅시다!

 

 

5. useCallback 🔄 콜백 함수를 캐싱

useCallbackuseMemo와 비슷하지만, 함수 자체를 기억하는 데 초점을 맞춘 훅이에요.

특히 하위 컴포넌트에 함수를 props로 넘길 때 불필요한 재렌더링을 막기 위해 많이 사용돼요.

리액트에서는 함수도 매번 새로 만들어지는 값이에요.

그래서 의도치 않게 하위 컴포넌트가 렌더링 될 필요가 없어도 재렌더링되곤 해요.

이때 useCallback을 사용하면 함수를 고정시켜서 그런 현상을 막을 수 있어요.

📌 기본 문법

const memoizedCallback = useCallback(() => {
  함수내용
}, [의존성값]);

🧪 실습 예제

import { useState, useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('버튼 클릭!');
  }, []);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>증가</button>
      <Child onClick={handleClick} />
    </div>
  );
}

function Child({ onClick }) {
  console.log('Child 렌더링');
  return <button onClick={onClick}>클릭</button>;
}

💡 언제 써야 할까?

  • 하위 컴포넌트가 React.memo를 사용 중일 때, props로 넘기는 함수가 매번 바뀌면 재렌더링이 발생해요.
  • useCallback을 쓰면 함수 참조가 고정돼서 렌더링 최적화가 가능해져요.

📋 요약 정리

기능 설명
함수 메모이제이션 렌더링 시 동일한 함수 참조 유지
최적화 대상 React.memo와 함께 하위 컴포넌트 렌더링 방지
주의사항 의존성 배열 정확히 지정해야 효과 있음

useCallback은 성능 최적화를 위한 작은 도구예요.

얘를 잘 쓰면 쓸수록 렌더링 횟수를 줄여 앱이 쾌적해집니다.

 

다음은 컴포넌트 간 전역 상태를 쉽게 공유할 수 있는 useContext를 살펴볼 차례예요!

 

 

6. useContext 🌐 전역 상태를 간단하게 공유

리액트 앱이 커지다 보면 여러 컴포넌트에서 공통된 데이터를 사용해야 하는 상황이 많아져요.

예를 들어 사용자 정보, 테마 설정, 로그인 상태 등은 전역 상태로 관리하는 게 편하죠.

이럴 때 사용하는 게 useContext입니다.

useContext는 컴포넌트 트리 전반에 걸쳐 값을 전달하는 React Context를 쉽게 읽을 수 있도록 도와주는 훅이에요.

복잡한 props 전달 없이도 데이터를 쏙쏙! 받아올 수 있죠.

📌 기본 구조

// Context 생성
const MyContext = createContext();

// 상위 컴포넌트에서 Provider로 감싸기
<MyContext.Provider value={공유할값}>
  <하위컴포넌트 />
</MyContext.Provider>

// 하위 컴포넌트에서 사용
const value = useContext(MyContext);

🧪 실습 예제

import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button>현재 테마: {theme}</button>;
}

💡 언제 쓰면 좋을까?

  • 테마 설정, 언어 설정 등 앱 전체에 영향을 주는 값이 있을 때
  • 로그인 정보처럼 여러 컴포넌트에서 접근이 필요한 상태가 있을 때

📋 요약 정리

기능 설명
전역 상태 공유 props 없이 하위 컴포넌트에서 상태 사용 가능
Context API createContext + useContext 조합으로 구성
성능 유의사항 변경 시 모든 하위 컴포넌트 재렌더링 가능성 있음

 

이제 useContext까지 익혔다면, 리액트의 주요 훅들은 거의 섭렵한 거예요! 💪

 

다음 단계는 지금까지 배운 내용을 정리하고, 각 훅을 실제 프로젝트에서 어떻게 조합해서 사용할 수 있는지 마무리하면서 이야기해볼게요.

지금까지 리액트의 주요 훅들useState, useEffect, useRef, useMemo, useCallback, useContext—을 하나씩 차근차근 살펴봤어요.

처음에는 각 훅이 언제, 왜, 어떻게 쓰는지 헷갈릴 수 있지만, 실제 프로젝트에서 직접 활용해보면 어느 순간 손에 익는 도구처럼 자연스럽게 쓰게 됩니다.

각 훅은 역할과 목적이 명확하기 때문에 상황에 맞춰 조합하는 연습을 많이 해보는 게 좋아요.

예를 들어 useContext로 전역 상태를 만들고, 그 안에서 useMemouseCallback으로 불필요한 렌더링을 줄이는 구조는 실전에서도 많이 사용돼요.

처음 훅을 공부할 땐 “이걸 왜 쓰지?” 싶은 느낌이 들 수 있지만, 리액트를 리액트답게 쓰는 핵심이 바로 이 훅들에 있답니다.

앞으로도 계속해서 다양한 상황에 훅을 적용해보면서 자신만의 개발 노하우를 만들어 보세요! 🚀

반응형
반응형

리액트 컴포넌트 설계 마스터:
Atomic Design, 합성, 상속 완전정복

리액트 컴포넌트를 더 유연하고 재사용 가능하게 만들고 싶다면,
Atomic Design과 합성, 상속
제대로 이해해야 해요! 🚀
반응형

 

안녕하세요, 개발자 여러분 😊
리액트를 처음 배울 땐 UI만 띄우는 것도 벅찼는데, 어느 순간부터는 코드의 재사용성구조의 일관성이 중요해지죠.
그래서 이번 글에서는 실무에서도 강력하게 통하는 Atomic Design 패턴부터, 컴포넌트 합성, 상속, 그리고 HOC, Render Props 패턴까지 한 번에 정리해드리려 합니다.

실전 예제와 함께 쉽게 설명해드릴 테니, 초보자 분들도 걱정 마세요!

 

1. Atomic Design 패턴이란? 🧬

처음 리액트를 배우다 보면, 어떤 컴포넌트를 어디까지 쪼개야 할지 고민되죠?

이럴 때 Atomic Design은 정말 유용한 설계 철학이자 구조화 전략이 되어줘요.

Atomic Design은 ‘작은 단위부터 큰 단위까지 계층적으로 UI를 구성하자’는 아이디어에서 출발했는데요, 이름처럼 ‘원자’ 단위로 UI를 분해하고 조립하는 접근입니다.

Atomic Design 구성요소 🔍

Atomic Design은 아래의 다섯 가지 구성요소로 나뉘어요:

  • Atoms: 버튼, 인풋 같은 더 이상 나눌 수 없는 UI 요소들
  • Molecules: Label + Input 같이 Atoms를 조합한 간단한 기능 단위
  • Organisms: Header, Footer처럼 여러 Molecule이 모인 복합 구조
  • Templates: 페이지의 레이아웃 구조 (Organisms 배치)
  • Pages: 실제 콘텐츠가 들어간 완성된 페이지

Atomic Design, 왜 쓰는 걸까? 🤔

Atomic Design의 가장 큰 장점은 재사용성과 일관성이에요.

프로젝트가 커질수록 컴포넌트가 복잡해지고 중복되는 코드가 생기기 쉬운데, 원자 단위로 분리해두면 언제든 조립해서 다른 곳에 사용할 수 있어요.

예를 들어,

어떤 버튼 하나를 수정했는데 그게 로그인 페이지, 회원가입 페이지, 마이페이지까지 다 바뀌어야 한다면? 🫠

이럴 땐 원자 단위로 관리하는 Atomic 구조가 유지보수의 신세계를 열어줍니다!

🧾 예제

// Atoms/Button.jsx
const Button = ({ children, onClick }) => (
  <button onClick={onClick} style={{ padding: '8px 16px', background: '#1b6ca8', color: '#fff' }}>
    {children}
  </button>
);

// Molecules/FormGroup.jsx
import Button from '../atoms/Button';
const FormGroup = () => (
  <div>
    <label>이메일</label>
    <input type="email" />
    <Button>제출</Button>
  </div>
);

 

이처럼 Atoms, Molecules, Organisms 순서로 쪼개서 설계하면 리팩토링도 쉽고, 다른 프로젝트에 복사해서 붙여넣기만 해도 호환이 척척 됩니다.

 

 

2. 컴포넌트 합성이란? 🔧

리액트에서 가장 강력한 개념 중 하나가 바로 컴포넌트 합성(Component Composition)이에요.

말 그대로, 컴포넌트를 다른 컴포넌트 안에 포함시켜서 더 큰 UI 구조를 만드는 거죠.

마치 레고처럼요!

합성은 코드 재사용성을 극대화하고, 공통 UI를 유연하게 확장할 수 있게 도와줘요.

이건 단순히 "다시 쓰기"의 개념이 아니라, 역할과 책임을 나눠서 컴포넌트를 더 똑똑하게 설계하는 방식이기도 하죠.

리액트의 기본 합성 방식: children 속성 🌱

리액트에서 컴포넌트를 합성하는 가장 기본적인 방법은 바로 props.children을 사용하는 거예요.

부모 컴포넌트가 자식 컴포넌트를 감싸고, 자식은 전달된 내용을 자신의 위치에 출력하죠.

// Wrapper.jsx
const Wrapper = ({ children }) => (
  <div style={{ border: '2px solid #1b6ca8', padding: '20px', borderRadius: '8px' }}>
    {children}
  </div>
);

// App.jsx
<Wrapper>
  <h2>안녕하세요!</h2>
  <p>이 부분은 Wrapper 컴포넌트 내부에 렌더링돼요.</p>
</Wrapper>

이렇게 하면 Wrapper 컴포넌트는 언제든지 다른 UI 요소를 감쌀 수 있는 공통 컴포넌트로 활용할 수 있죠.
정말 실용적인 방식이에요!

명시적 합성: 슬롯 패턴 🧩

때로는 children을 넘기는 것만으로는 부족할 수 있어요.

예를 들어 Header, Footer, Content를 각각 명확히 나누고 싶을 때는 슬롯 패턴을 쓰는 게 좋아요.

// Layout.jsx
const Layout = ({ header, content, footer }) => (
  <div>
    <header>{header}</header>
    <main>{content}</main>
    <footer>{footer}</footer>
  </div>
);

// App.jsx
<Layout
  header={<h1>타이틀입니다</h1>}
  content={<p>본문 내용입니다</p>}
  footer={<small>© 2025</small>}
/>
 

이 패턴을 사용하면 컴포넌트의 역할이 명확해지고, 구조도 훨씬 직관적이 돼요.

특히 복잡한 레이아웃에서 효과적입니다.

📋 합성 vs 상속

리액트 팀에서도 공식 문서에서 말하죠. "React는 상속보다 합성을 선호합니다."
이유는요? 합성은 컴포넌트 간 결합도를 낮추고, 더 명확한 데이터 흐름을 만들 수 있기 때문이에요.

 

그럼 다음 단계에서는 “왜 리액트는 상속보다 합성을 더 선호하는가?”를 실제로 비교해보며 알아볼게요!

 

 

3. 상속은 리액트에서 어떻게 다룰까? 🧩

리액트는 객체지향 프로그래밍에서 익숙한 상속을 기본 전략으로 삼지 않아요. 왜일까요? 🤔

컴포넌트 간의 관계가 복잡해지고 유지보수가 어려워지기 때문이에요.

리액트는 대신 합성(Composition)을 활용한 명확한 구성과 책임 분리를 더 선호하죠.

클래스 상속 vs 컴포넌트 합성 비교표 📊

구분 상속 (Inheritance) 합성 (Composition)
관계 구조 수직적 (부모-자식) 수평적 (독립적 조합)
유지보수 복잡하고 어렵다 간단하고 예측 가능
테스트 상위 클래스 영향 받음 각각 독립 테스트 가능
유연성 확장에 한계 있음 유연한 재사용 가능

왜 React는 상속보다 합성을 선택했을까? 💡

React의 설계 철학은 "컴포넌트는 독립적이고, 예측 가능하게"입니다.

상속 기반의 구조는 기능을 재사용하는 데는 편리할 수 있지만, 상위 컴포넌트의 변경이 하위 컴포넌트에 직접적인 영향을 준다는 치명적인 단점이 있어요.

반면, 합성은 이런 문제를 원천적으로 차단해 줍니다.

각 구성 요소는 독립적으로 존재하고, 합쳐질 때만 기능이 발생하니까요. 마치 퍼즐처럼요! 🧩

💡 실제 코드로 비교해보기

// 상속을 사용한 컴포넌트 설계 (지양)
class Button extends React.Component {
  render() {
    return <button style={{ background: 'blue' }}>{this.props.label};
  }
}

class DangerButton extends Button {
  render() {
    return <button style={{ background: 'red' }}>{this.props.label};
  }
}

// 합성을 사용한 컴포넌트 설계 (권장)
const Button = ({ children, style }) => (
  <button style={{ ...style, padding: '8px 16px' }}>{children}</button>
);

const DangerButton = ({ children }) => (
  <Button style={{ background: 'red', color: 'white' }}>{children}</Button>
);

 

보이시죠? 합성 방식은 훨씬 간단하고, 각 기능이 독립적이기 때문에 수정도 훨씬 쉬워요.

이게 바로 리액트가 합성을 선호하는 이유입니다.

 

 

4. HOC(Higher-Order Component) 패턴 🧠

리액트에서는 컴포넌트를 마치 함수처럼 다루는 패턴이 있는데요,

그게 바로 HOC(Higher-Order Component)입니다.

고차 컴포넌트라고도 불리죠.

이건 컴포넌트를 인수로 받아서, 새로운 기능을 덧붙인 새 컴포넌트를 반환하는 함수예요.

HOC의 기본 구조 🔁

고차 컴포넌트는 보통 이런 식으로 작성돼요:

// withLogger.js
const withLogger = (WrappedComponent) => {
  return (props) => {
    console.log("Props:", props);
    return <WrappedComponent {...props} />;
  };
};

// 사용 예시
const Hello = ({ name }) => <h1>안녕하세요, {name}님!</h1>;
const HelloWithLogger = withLogger(Hello);

<HelloWithLogger name="민지" />;

 

이렇게 HOC는 원래 컴포넌트를 변경하지 않고 확장할 수 있어서, 인증 처리, 로깅, 권한 제어 같은 부가 기능을 추가할 때 많이 사용돼요.

HOC 사용 시 주의할 점 ⚠️

  • Props 충돌 방지: HOC가 전달하는 props와 원본 컴포넌트의 props가 겹치지 않도록 조심해야 해요.
  • displayName 설정: 디버깅을 위해 HOC로 감싼 컴포넌트에 식별 가능한 이름을 설정하면 좋아요.
  • Wrapper 남용 금지: HOC를 너무 많이 중첩하면 컴포넌트 트리가 복잡해져서 디버깅이 어려워질 수 있어요.

🧾 HOC를 언제 쓰면 좋을까?

HOC는 반복적인 로직을 한 번에 추상화하고 싶을 때 유용해요.

예를 들어 로그인 여부 체크, API 통신 후 데이터 전달 등…

공통 기능을 재활용할 수 있는 곳이라면 HOC가 제격이죠.

하지만 너무 남발하면 컴포넌트의 흐름이 꼬이기 쉬우니, 적절한 선에서, 필요한 경우에만 사용하는 게 좋아요!

 

 

5. Render Props 패턴 이해하기 🎥

리액트에서 컴포넌트 재사용성을 높이는 또 다른 강력한 기법이 바로 Render Props 패턴입니다.

이건 말 그대로 props로 함수를 전달해서, 그 함수가 내부에서 렌더링할 내용을 결정하게 하는 방식이에요.

Render Props의 기본 구조 🔁

// MouseTracker.jsx
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

// App.jsx
<MouseTracker render={({ x, y }) => (
  <h1>마우스 위치: ({x}, {y})</h1>
)} />

 

위 예제를 보면, MouseTracker 컴포넌트는 마우스 좌표를 추적하지만 UI는 props로 받은 함수에 따라 다르게 바뀔 수 있어요.

로직과 표현을 분리해서 유연하게 설계할 수 있다는 게 핵심이죠.

Render Props vs HOC 비교 🤜🤛

구분 HOC Render Props
패턴 방식 함수로 컴포넌트를 감쌈 함수를 props로 전달
장점 코드 분리, 재사용 용이 로직과 UI 완벽 분리
단점 Wrapper 중첩 가능성 JSX 중첩, 가독성 저하

Render Props는 정말 강력하지만, this.props.render 같은 패턴이 익숙하지 않거나 JSX 트리 깊이가 커질 수 있다는 단점도 있어요.

그래서 React 훅이 등장한 이후로는 점점 쓰임이 줄어드는 추세이기도 하죠.

💡 언제 Render Props를 쓸까?

상태나 데이터를 추적하면서 렌더링 방식은 외부에서 결정되게 하고 싶을 때 Render Props가 정말 빛나요. 마우스 위치, 스크롤, resize 등 사용자 이벤트 처리에 딱이죠!

 

 

6. 합성과 상속의 실전 활용법 🔍

이제까지 컴포넌트 합성상속, 그리고 HOC, Render Props까지 배워봤죠.

이번에는 실무에서 어떻게 써먹는지 실전 사례와 함께 정리해볼게요. 🔧

📦 재사용 가능한 레이아웃 컴포넌트 만들기

대부분의 페이지는 공통된 구조를 갖고 있어요.

예를 들면 Header + Sidebar + Content 조합이죠.

이럴 땐 명시적 합성으로 구성 요소들을 slot처럼 받아보세요.

// Layout.jsx
const Layout = ({ header, sidebar, content }) => (
  <div className="layout">
    <header>{header}</header>
    <aside>{sidebar}</aside>
    <main>{content}</main>
  </div>
);

// 사용 예시
<Layout
  header={<Header />}
  sidebar={<Sidebar />}
  content={<PageContent />}
/>

🔐 인증 처리 공통 로직 추상화

로그인 여부에 따라 페이지 접근을 제어하는 기능, 여러 페이지에서 반복되죠?

이럴 땐 HOC 또는 Render Props로 공통 로직을 추상화하면 좋아요.

// withAuth.js (HOC 예시)
const withAuth = (Component) => {
  return (props) => {
    const isLoggedIn = useAuth();
    if (!isLoggedIn) return <LoginPage />;
    return <Component {...props} />;
  };
};

// 적용
const MyPage = () => <p>내 정보 페이지</p>;
const ProtectedMyPage = withAuth(MyPage);

🧩 합성과 상속을 혼용하면 안 되는가?

꼭 그런 건 아니에요. 아주 특수한 상황에서는 상속을 활용한 클래스 기반 확장이 유리할 수도 있어요.

예를 들어 Canvas나 WebGL 기반 렌더링 라이브러리와 통합할 때 말이죠.

다만, 리액트의 세계에선 합성이 거의 표준입니다.

✨ 마무리 팁

  • 공통 UI → 합성으로 모듈화 (children 또는 slot 방식)
  • 인증, 로깅, 로딩처리 → HOC나 Render Props로 추상화
  • UI 표현 분리 → Render Props 사용 (단점도 고려!)

실무에서는 이 패턴들을 적절히 조합해서 사용하는 것이 핵심이에요.

항상 "이 컴포넌트는 변경될 가능성이 있는가?", "재사용될 수 있는가?"를 고민하며 설계해보세요. 🙌 

여기까지 리액트의 컴포넌트 설계 패턴을 하나씩 살펴봤어요.

Atomic Design으로 UI를 체계적으로 쪼개고, 합성과 상속의 차이를 이해하면서, HOCRender Props로 기능을 추상화하는 방법까지!

사실 이 모든 걸 처음에 한 번에 다 이해하기는 쉽지 않아요.

너무 조급해하지 말고, 지금부터 하나씩 실습해보면서 감을 잡아보세요! 💪

앞으로 컴포넌트 구조를 설계할 때 오늘 배운 내용이 진짜 든든한 무기가 될 거예요.

 

다음 글에서는 리액트 훅(Hooks)을 활용해서 상태와 로직을 더 효율적으로 관리하는 방법에 대해 이야기해볼게요. 기대되시죠? 😊

반응형
반응형

리액트 컴포넌트의 다양한 구성 방법

컴포넌트 구성만 잘해도 리액트의 절반은 마스터한 거예요.
구조화된 코드로 유지보수성과 재사용성을 동시에 잡아보세요!
반응형

안녕하세요,  여러분! 😊

오늘은 리액트에서 가장 핵심이 되는 개념 중 하나인 "컴포넌트 구성 방식"에 대해 함께 알아보려 해요.

단순히 UI를 나누는 것을 넘어서, 유지보수성과 확장성을 고려한 설계 방식까지 꼼꼼히 다룰 예정입니다.

실무에서도 바로 적용할 수 있는 팁과 예제들을 준비했으니 끝까지 함께 해주세요 😊

 

1. 함수형 컴포넌트 기본 사용법 🧱

리액트를 처음 접하면 가장 먼저 마주하는 것이 바로 컴포넌트입니다.

특히 함수형 컴포넌트는 코드가 간결하고, React Hooks와 함께 사용하면 훨씬 강력한 기능을 구현할 수 있어요.

이 파트에서는 함수형 컴포넌트를 어떻게 정의하고 사용하는지, 그리고 어떤 구조로 작성하는 것이 좋은지를 알아봅니다.

함수형 컴포넌트란 무엇인가요?

함수형 컴포넌트는 단순히 JavaScript 함수로 정의된 컴포넌트입니다.

과거에는 클래스형 컴포넌트를 더 많이 사용했지만, 현재는 함수형 컴포넌트와 훅(Hooks)이 중심입니다.

 

가장 기본적인 함수형 컴포넌트 예시는 아래와 같습니다:

function Hello() {
  return <h1>안녕하세요!</h1>;
}

그리고 이것을 JSX 안에서 `<Hello />` 와 같이 호출하면 실제로 해당 컴포넌트가 렌더링됩니다.

Props로 데이터 전달하기

컴포넌트를 재사용 가능하게 만드는 핵심 요소가 바로 props입니다.

예를 들어 아래처럼 name을 전달해볼 수 있어요:

function Hello(props) {
  return <h1>안녕하세요, {props.name}!</h1>;
}

호출 시에는 이렇게 사용합니다: <Hello name="민수" />

정리 🧩

  • 함수형 컴포넌트는 간결하고 가독성이 좋다.
  • props를 이용해 유연한 데이터 전달이 가능하다.
  • 리액트 훅(Hooks)과 함께 쓰면 상태관리도 간단하게 가능하다.

이제 컴포넌트의 뼈대를 이해하셨다면, 다음으로 JSX 구조와 여러 컴포넌트 간의 관계를 좀 더 깊게 들여다보겠습니다.

 

 

2. JSX 구조와 다중 컴포넌트 구성 🏗️

JSX는 자바스크립트 안에서 HTML을 작성하는 것처럼 보이게 해주는 리액트의 문법입니다.

실제로는 자바스크립트의 문법적 확장(Syntax Extension)이며, React.createElement로 변환됩니다.

 

그런데 여러분, JSX 안에 컴포넌트를 여러 개 중첩해서 작성할 수 있다는 점 알고 계셨나요?

JSX로 컴포넌트 중첩하기

JSX에서는 divsection과 같은 태그로 여러 요소를 감싸거나, React.Fragment를 사용해 불필요한 DOM 생성 없이 컴포넌트를 중첩할 수 있어요.

function App() {
  return (
    <div>
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

이런 식으로 여러 컴포넌트를 한 곳에 모아 하나의 UI를 구성할 수 있습니다.

그리고 각각의 컴포넌트는 다시 자신만의 자식 컴포넌트를 가질 수 있어요.

Fragment를 활용한 DOM 최소화

JSX는 반드시 하나의 부모 요소로 감싸야 하는 특징이 있습니다.

그런데 불필요한 div를 추가하고 싶지 않을 땐 어떻게 하냐구요?

 

바로 <React.Fragment> 또는 <> </> 문법을 사용할 수 있어요:

function Wrapper() {
  return (
    <>
      <h2>제목입니다</h2>
      <p>본문 내용입니다</p>
    </>
  );
}

불필요한 DOM 태그를 줄이면서도 JSX 규칙은 지킬 수 있어, 요즘은 정말 자주 쓰이는 방식입니다.

한눈에 정리 🧾

기법 설명
중첩 컴포넌트 상위 컴포넌트에서 여러 하위 컴포넌트를 조합하여 UI 구성
Fragment 사용 불필요한 DOM 없이 여러 요소를 하나의 컴포넌트에서 반환

 

이제 컴포넌트를 여러 개로 나눠서 어떻게 재구성할 수 있는지 감이 오시죠?

다음은 실제로 이 컴포넌트들이 어떻게 조합되는지, 그 구조를 더 깊이 있게 다뤄볼게요.

 

 

3. 컴포넌트의 조합과 부모-자식 구조 🧩

리액트에서 진짜 재미있는 부분은 바로 컴포넌트 조합이에요.

단순히 UI를 나눈 것뿐만 아니라, 데이터를 상위에서 하위로 어떻게 흐르게 할지도 설계의 핵심입니다.

부모 → 자식 데이터 전달: Props 흐름

컴포넌트 간 조합은 데이터를 props로 넘기면서 구성됩니다.

예를 들어 App 컴포넌트에서 Profile 컴포넌트에 데이터를 전달해볼게요.

// 부모 컴포넌트
function App() {
  return <Profile name="유나" age={28} />;
}

// 자식 컴포넌트
function Profile(props) {
  return (
    <div>
      <p>이름: {props.name}</p>
      <p>나이: {props.age}</p>
    </div>
  );
}

이처럼 컴포넌트는 마치 블록처럼 쌓아올릴 수 있고, 필요한 정보를 위에서 아래로 흘려보낼 수 있습니다.

자식 컴포넌트 안에 또 자식? 계층 구조 만들기

리액트에서는 컴포넌트를 계층적으로 구성할 수 있어요.

예를 들어, App 안에 Header가 있고, Header 안에 Logo와 Menu가 있는 구조처럼요.

function Header() {
  return (
    <header>
      <Logo />
      <Menu />
    </header>
  );
}

이런 방식으로 코드를 구조화하면 협업이나 유지보수 시 훨씬 수월합니다.

👀 요약하자면...

  • 리액트 컴포넌트는 블록처럼 쌓고 조합하는 것이 가능하다.
  • 상위 컴포넌트는 하위 컴포넌트에게 props를 통해 정보를 전달한다.
  • 컴포넌트 계층을 명확히 구분하면 유지보수가 쉬워진다.

이제 컴포넌트가 서로 어떻게 연결되는지 감이 오셨죠?

다음 섹션에서는 개발자들이 즐겨 사용하는 패턴 중 하나인 '컨테이너-프리젠테이셔널 패턴'을 다뤄볼게요!

 

 

4. 컨테이너-프리젠테이셔널 패턴 🎭

컴포넌트를 구성할 때 가장 많이 사용되는 구조 중 하나가 바로 컨테이너(Container)프리젠테이셔널(Presentational) 패턴이에요.

이 방식은 로직과 UI를 명확히 분리할 수 있어서 협업과 유지보수에 탁월하답니다.

이 패턴이 왜 중요한가요?

리액트 앱이 커질수록 각 컴포넌트에 데이터 로직과 UI가 섞이기 쉬워요.

이러면 코드가 복잡해지고 재사용도 어려워지죠.

그래서 등장한 게 바로 이 패턴입니다.

 

쉽게 말해, '로직은 컨테이너가 처리하고, 화면은 프리젠테이셔널이 담당한다'는 거예요.

예제로 알아보는 분리 방법

// 프리젠테이셔널 컴포넌트
function UserProfile({ name, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}살</p>
    </div>
  );
}

// 컨테이너 컴포넌트
function UserProfileContainer() {
  const user = { name: "혜진", age: 25 };
  return <UserProfile name={user.name} age={user.age} />;
}

이런 방식으로 UI 로직은 단순하게, 데이터는 별도로 관리하면 확장성과 유지보수성이 극대화됩니다.

컨테이너 & 프리젠테이셔널 비교표 🔍

구분 컨테이너 프리젠테이셔널
역할 데이터 처리, 상태 관리 UI 렌더링 전담
관심사 비즈니스 로직 디자인 및 화면 구조

 

이제 프로젝트가 커져도, 로직은 로직대로, UI는 UI대로 명확하게 관리할 수 있겠죠?

다음은 프로젝트 구조를 깔끔하게 유지하는 폴더 구조 팁을 소개할게요!

 

 

5. 폴더 구조로 관리하는 방법 🗂️

리액트 프로젝트가 점점 커지면 한눈에 보기 어려운 '컴포넌트 지옥'에 빠질 수 있어요.

그래서 오늘은 폴더 구조를 체계적으로 구성해서 관리하는 법을 소개할게요!

기본적인 디렉터리 구조 예시

src/
│
├── components/          // 재사용 가능한 컴포넌트
│   └── Button.js
│
├── containers/          // 로직이 포함된 컴포넌트
│   └── UserContainer.js
│
├── pages/               // 페이지 단위 컴포넌트
│   └── Home.js
│
├── hooks/               // 커스텀 훅 저장소
│   └── useFetch.js
│
├── styles/              // 전역 또는 모듈 스타일
│   └── App.module.css
│
└── App.js               // 루트 컴포넌트

이렇게 구성하면 각 책임에 따라 폴더가 분리되므로 찾기도 쉽고 관리도 훨씬 깔끔해집니다.

상황별 폴더 분리 전략

  • 페이지 기반 프로젝트: pages 중심 구조 추천
  • 기능 기반 프로젝트: features 폴더 아래에 관련 모듈 묶기
  • 컴포넌트 수가 많을 경우: 공통 UI와 비즈니스 UI 구분

💡 팁 하나!

컴포넌트 파일이 많아지면 index.js를 활용해 모듈을 한 번에 export하는 방식도 매우 유용합니다.

이렇게 하면 import Button from '../components/Button' 대신

import { Button } from '../components'처럼 깔끔하게 사용할 수 있어요!

 

이제 폴더 구조까지 정리됐으니, 마지막으로 실무에서 바로 쓸 수 있는 컴포넌트 구성 팁들을 공유드릴게요 😉

 

 

6. 실무에서 유용한 컴포넌트 구성 팁 💡

이제 기본 개념과 구조는 충분히 익혔다면, 실무에서 바로 써먹을 수 있는 꿀팁들을 알아볼 시간이에요.

초보 개발자일수록 놓치기 쉬운 실수들을 방지하고, 더 효율적으로 개발하는 방법들을 알려드릴게요!

재사용 가능한 컴포넌트 만들기

비슷한 UI를 복붙하는 대신 props를 활용해 재사용 가능한 컴포넌트를 만들어보세요.

예를 들어 Button 컴포넌트 하나로 모든 버튼을 처리할 수 있습니다.

function Button({ label, onClick, type = "button" }) {
  return (
    <button type={type} onClick={onClick}>
      {label}
    </button>
  );
}

파일명과 컴포넌트명은 반드시 일치

이건 React의 암묵적 약속 같은 거예요.

컴포넌트 이름이 `ProfileCard`라면 파일명은 `ProfileCard.js`로 짓는 것이 좋아요. 찾기 쉽고, 협업 시에도 혼동을 줄일 수 있습니다.

상태관리는 필요한 컴포넌트에만

useStateuseEffect는 필요한 곳에만 쓰세요.

모든 컴포넌트에 상태를 두면 관리 지옥이 시작돼요.

최대한 상위 컴포넌트에서 처리하고, props로 전달하세요.

🚀 실무 팁 요약

  • 컴포넌트 이름과 파일명은 반드시 일치시킬 것
  • props를 통해 유연한 재사용 구조 만들기
  • 상태 관리는 꼭 필요한 경우에만, 상위에서 처리

이제 여러분도 실전에서 통하는 컴포넌트 설계를 할 수 있을 거예요! 🎉

 

 

마무리하며 🧩

지금까지 리액트 컴포넌트를 어떻게 구성하고 활용할 수 있는지에 대해 단계별로 살펴봤어요.

처음에는 컴포넌트 하나 만드는 것도 버거웠을 수 있지만, 이제는 구조화된 컴포넌트 설계재사용 가능한 패턴까지 이해하게 되셨을 거예요.

 

컴포넌트는 단순한 코드 블록이 아니라, 사용자와 상호작용하는 인터페이스의 조각입니다.

꾸준히 연습하고, 여러 프로젝트에서 실전 경험을 쌓다 보면 자연스럽게 '좋은 컴포넌트'가 무엇인지 눈에 들어오게 될 거예요.

 

다음 글에서는 이 컴포넌트들을 활용한 상태관리 기법컴포넌트 간 데이터 흐름에 대해 이야기해보려고 해요. 기대해주세요!

반응형

+ Recent posts