반응형

리액트에서 리스트 데이터를 화면에 출력하는 모든 방법

리스트를 효과적으로 출력하는 방법을 몰라 고생하고 계신가요?
map 함수부터 조건부 렌더링까지,
초보자를 위한 실전 예제로 모두 설명해드립니다!
반응형

 

안녕하세요, 여러분! 🙋‍♂️

이번 시간에는 리액트(React)로 화면에 리스트 데이터를 출력하는 방법을 알아보려고 해요.

특히 초보 개발자 분들이 자주 실수하는 포인트와 자주 쓰이는 패턴들을 중심으로, 최대한 쉽게 설명드릴게요.

단순한 텍스트 배열부터 객체 배열까지 다양한 데이터 출력 방법을 예제로 직접 보여드릴 테니, 끝까지 읽어보시면 실력이 한층 업그레이드될 거예요!

 

1. map() 함수를 사용한 배열 출력 방법 🧩

리액트에서 리스트 데이터를 출력할 때 가장 많이 사용하는 방법 중 하나가 바로 map() 함수입니다.

배열의 각 요소를 순회하면서 JSX 요소를 리턴해 주는 방식인데요.

간단하지만 강력한 이 방법부터 함께 알아볼게요!

✅ 기본 배열을 출력하는 예제

예를 들어 ['사과', '바나나', '포도']라는 문자열 배열이 있다고 가정해봅시다.

이 데이터를 화면에 출력하려면 다음과 같이 작성할 수 있어요.

{['사과', '바나나', '포도'].map((item, index) => (
  <li key={index}>{item}</li>
))}

위 코드는 배열의 각 요소를 <li> 태그로 감싸서 출력해 주고 있어요.

key 속성은 리액트가 리스트를 효율적으로 렌더링하기 위해 꼭 필요한 부분이니, 잠시 후 자세히 설명드릴게요.

📋 리스트 출력 시 유용한 팁

  • JSX 안에서 직접 map()을 사용할 수 있어요.
  • 출력할 HTML 요소에 key 속성을 꼭 넣어주세요.
  • 너무 복잡한 로직은 별도의 함수로 분리하면 더 깔끔해져요.

📊 비교를 위한 표

구분 사용 여부 설명
map() 배열을 순회하며 JSX 반환
forEach() JSX를 반환하지 않아 화면에 출력 불가

자, 여기까지가 리액트에서 리스트 데이터를 가장 기본적으로 출력하는 방법이었습니다.

다음 섹션에서는 객체 배열을 출력할 때의 유용한 팁과 예제를 알아볼게요!

 

 

2. 객체 배열을 활용한 리스트 렌더링 📚

단순한 문자열 배열뿐 아니라 객체 배열을 화면에 출력하는 경우도 정말 많죠.

예를 들어

게시판의 글 목록, 사용자 리스트, 상품 정보 등 대부분의 실무 데이터는 객체 배열로 이루어져 있어요.

그럼 실제 예제로 살펴볼까요?

💡 사용자 목록 출력 예제

const users = [
  { id: 1, name: '홍길동', age: 28 },
  { id: 2, name: '김철수', age: 34 },
  { id: 3, name: '이영희', age: 22 },
];

{users.map(user => (
  <div key={user.id}>
    <p>이름: {user.name}</p>
    <p>나이: {user.age}</p>
  </div>
))}

여기서는 각 사용자 객체의 id를 key로 지정했어요.

이는 성능 최적화뿐 아니라 리액트 내부에서 효율적인 비교 연산을 가능하게 합니다.

고유한 값을 key로 사용해야 해요!

🔍 실제 사용 예시 (게시판)

게시판 데이터를 서버에서 받아온 후 화면에 출력할 때도 거의 동일한 방식이에요.

중요한 건 렌더링하는 컴포넌트 내에서 데이터가 어떤 구조로 되어 있는지, 각 항목을 어떤 방식으로 보여줄지 먼저 정의하는 거죠.

  • 서버 응답은 보통 JSON 형태의 객체 배열
  • map() 함수로 각 항목을 컴포넌트로 매핑
  • key는 id, slug 등 고유 식별자로 설정

실제 개발 프로젝트에서 자주 접하게 되는 패턴이니, 꼭 여러 번 실습하면서 손에 익혀두세요!

다음 섹션에서는 key 속성을 조금 더 자세히 파고들어 보겠습니다.

 

 

3. Key 속성의 중요성과 사용 팁 🔑

리스트를 렌더링할 때 빠지면 안 되는 요소 중 하나가 바로 key 속성입니다.

처음 리액트를 접한 분들이 가장 헷갈려하는 부분이기도 하죠.

하지만 이 key가 없으면 렌더링 성능이 크게 떨어지거나 예기치 않은 버그가 발생할 수 있어요.

🙋‍♂️ key란 도대체 뭐예요?

리액트는 리스트 안의 항목들을 업데이트할 때 DOM을 효율적으로 관리하기 위해 각 항목을 식별할 수 있는 고유한 key 값을 필요로 합니다.

이 key를 기준으로 어떤 항목이 추가되거나 제거됐는지를 판단하는 거죠.

{users.map(user => (
  <div key={user.id}>
    <p>{user.name}</p>
  </div>
))}

이처럼 각 항목에 고유한 id를 key로 사용하는 것이 가장 이상적이에요.

index를 key로 쓰는 경우도 있지만, 항목이 자주 변경되거나 순서가 바뀌는 리스트에서는 사용을 피하는 것이 좋아요.

🚫 index를 key로 사용하면 생기는 문제

  • 항목 순서가 바뀌면 불필요한 리렌더링 발생
  • 애니메이션/입력값 유지가 꼬이는 경우 발생
  • 디버깅이 어려워짐

🛠 key 사용 실전 팁 요약

상황 추천 key 설명
서버에서 가져온 데이터 고유 id primary key 또는 UUID
정적인 배열 index 변경되지 않는 배열이라면 무방

이제 key에 대한 감이 좀 오셨나요?

다음은 조건부 렌더링을 활용해서 리스트를 더 유동적으로 제어하는 방법을 알려드릴게요!

 

 

4. 조건부 렌더링으로 동적 리스트 제어 🎛

리스트를 출력할 때 모든 데이터를 한 번에 보여주는 건 오히려 사용자 경험을 해칠 수도 있어요.

그래서 자주 사용하는 테크닉 중 하나가 조건부 렌더링입니다.

즉, 특정 조건에 따라 리스트 항목을 보여줄지 말지를 결정하는 거죠.

🎯 조건을 활용한 출력 예제

예를 들어 나이가 30세 이상인 사용자만 출력하고 싶다면 어떻게 해야 할까요?

아래처럼 조건을 걸어주면 됩니다.

{users
  .filter(user => user.age >= 30)
  .map(user => (
    <p key={user.id}>{user.name} - {user.age}세</p>
))}

filter()를 먼저 사용해서 조건에 맞는 항목만 남긴 후 map()으로 렌더링하는 패턴이죠.

실무에서도 정말 많이 사용되니까 꼭 기억해두세요.

📦 조건부 렌더링 방식 비교

방법 장점 단점
filter + map 직관적이고 가독성 좋음 한 번 더 순회하므로 성능 손해 가능성
map 내부 조건문 1회 순회로 효율적 코드가 지저분해질 수 있음

🔑 실전 팁

  • 조건이 복잡할수록 filter를 먼저 사용
  • JSX 내부 조건문은 삼항연산자(? :)로 처리 가능
  • 조건을 외부 변수로 선언하면 코드가 훨씬 깔끔해짐

조건부 렌더링은 단순한 리스트 출력보다 훨씬 더 유연하게 화면을 제어할 수 있는 강력한 도구입니다.

다음 챕터에서는 리스트 아이템을 컴포넌트로 분리해서 더 구조적인 코드를 만드는 방법을 알아보겠습니다!

 

 

5. 리스트 아이템 컴포넌트로 분리하기 🧱

리스트가 길어지고 항목이 복잡해질수록, 코드도 금방 지저분해지기 시작합니다.

이런 경우에는 리스트 항목을 별도 컴포넌트로 분리하는 것이 가장 좋은 방법이에요.

유지보수도 쉬워지고 재사용성도 높아지거든요.

🛠 리스트 항목 컴포넌트 만들기

예를 들어 사용자 정보를 출력하는 UserItem 컴포넌트를 따로 만든다고 가정해 볼게요.

function UserItem({ user }) {
  return (
    <div>
      <p>이름: {user.name}</p>
      <p>나이: {user.age}</p>
    </div>
  );
}

그다음에 리스트를 출력할 때는 이렇게 작성하면 되죠.

{users.map(user => (
  <UserItem key={user.id} user={user} />
))}

📋 이렇게 분리하면 좋은 점

  • 복잡한 JSX 로직을 분리할 수 있어 가독성 향상
  • 각 항목의 스타일이나 로직을 독립적으로 관리 가능
  • 테스트나 리팩토링 시 훨씬 유리

컴포넌트를 잘게 나누는 습관은 리액트를 능숙하게 다루기 위한 첫걸음입니다.

다음 섹션에서는 실수 없이 리스트를 잘 출력하기 위한 베스트 프랙티스를 정리해볼게요!

6. 리스트 출력 시 피해야 할 실수와 베스트 프랙티스 📌

여기까지 따라오셨다면 이제 기본적인 리스트 출력은 문제없이 하실 수 있을 거예요.

하지만 실제로 개발을 하다 보면 사소한 실수 하나로 디버깅에 시간을 많이 쏟게 되죠.

그래서 자주 발생하는 실수들과 꼭 알아두어야 할 베스트 프랙티스를 정리해볼게요!

❗ 자주 하는 실수 TOP 3

  1. key 속성 없이 리스트 출력하기 → 렌더링 성능 저하, 경고 발생
  2. map 함수에서 return 누락하기 → 아무것도 안 그려짐
  3. 동일한 key 값을 여러 항목에 사용하기 → 렌더링 꼬임

✅ 안전한 리스트 렌더링을 위한 베스트 프랙티스

  • 항상 key 속성을 고유한 값으로 지정하세요.
  • 복잡한 항목은 별도 컴포넌트로 분리하세요.
  • 조건부 렌더링은 filter() 또는 삼항 연산자(?)로 처리하세요.
  • 리스트 안에서 setState를 호출하면 무한 루프가 발생할 수 있으니 주의하세요.

📎 추가 팁

항상 리스트를 작성할 때는 '만약에 항목이 추가되거나 삭제되면 어떻게 될까?'를 먼저 고민해보세요.

그리고 그 상황에서 리액트가 정상적으로 동작할 수 있도록 key, 조건, 구조화를 잘 챙기면 리스트 출력이 훨씬 안정적이고 효율적이게 됩니다.

지금까지 리액트에서 리스트 데이터를 출력하는 다양한 방법들을 함께 알아봤어요.

단순한 배열부터 복잡한 객체 리스트, 그리고 조건부 렌더링과 컴포넌트 분리까지 단계별로 정리해봤는데요.

하나씩 실습해보면서 익숙해지다 보면 어느새 자연스럽게 리스트 데이터를 다룰 수 있게 될 거예요 😊

핵심은 항상 “가독성과 유지보수성”입니다.

데이터를 잘 출력하는 것보다 중요한 건 나중에 누가 봐도 이해하기 쉬운 코드를 작성하는 거예요.

이번 글이 여러분의 리액트 실력을 한층 끌어올리는 계기가 되었길 바랍니다!

반응형
반응형

리액트 동적 화면 처리 완전 정복 🌟

사용자의 클릭 하나로 화면이 바뀌고,
조건에 따라 다른 컴포넌트가 등장하는
그 마법 같은 UI 구현!
리액트로 그 비밀을 풀어봅니다.
반응형

 

안녕하세요, 여러분! 😊

혹시 리액트로 개발하다 보면 화면을 동적으로 바꾸고 싶었던 적 있으신가요?

예를 들어 버튼을 누르면 화면이 전환되거나, 특정 조건일 때만 컴포넌트가 보이게 하고 싶었던 경험 있으시죠?

이번 포스트에서는 바로 그런 동적 UI 처리 방법을 리액트에서 어떻게 구현할 수 있는지 자세히 알아보려 해요.

상태(state) 관리, 조건부 렌더링, 컴포넌트 전환 등 실무에 바로 써먹을 수 있는 팁을 예제와 함께 하나하나 알려드릴게요.

초보자도 이해할 수 있도록 천천히, 차근차근 진행하니 끝까지 함께해 주세요!

 

1. useState로 조건부 렌더링하기 🎛️

리액트에서 동적인 UI를 처리하려면 가장 먼저 알아야 할 훅이 바로 useState입니다.

사용자의 행동에 따라 화면의 요소를 보이거나 숨기고 싶다면, 조건에 따라 렌더링되는 컴포넌트를 제어해야 하거든요.

💡 기본 사용 패턴

import { useState } from "react";

function ToggleExample() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "숨기기" : "보이기"}
      </button>
      {isVisible && <p>이 텍스트는 상태에 따라 보입니다!</p>}
    </div>
  );
}

 

이 예제에서는 버튼을 누르면 isVisible 상태가 true/false로 바뀌면서 텍스트가 조건부로 렌더링돼요.

이게 바로 조건부 렌더링의 핵심입니다.

🔍 다양한 조건부 렌더링 방식

  • 삼항 연산자 (ternary operator)를 사용한 렌더링
  • && 연산자를 활용한 짧은 조건 처리
  • if/else 또는 함수형 렌더링 방식

📋 예시: 로그인 상태에 따라 화면 전환

function LoginStatus() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      {isLoggedIn ? (
        <h2>환영합니다, 사용자님!</h2>
      ) : (
        <button onClick={() => setIsLoggedIn(true)}>로그인하기</button>
      )}
    </div>
  );
}

 

이처럼 useState를 기반으로 조건에 맞게 컴포넌트를 보여주면, 사용자의 액션에 따라 부드럽게 변화하는 UI를 만들 수 있어요.

✅ 정리해볼까요?

  1. 상태(state)는 동적 화면 처리를 위한 기본 도구입니다.
  2. 조건부 렌더링에는 다양한 방법이 있지만 &&삼항 연산자가 가장 흔하게 쓰입니다.
  3. 실제 화면에 바로 반영되기 때문에 즉각적인 피드백을 줄 수 있습니다.

앞으로 다룰 ‘컴포넌트 Show/Hide’와도 매우 밀접한 개념이니, 이 부분을 확실히 익혀두세요!

 

 

2. 화면 전환: 컴포넌트 Show/Hide 패턴 🔄

리액트 앱에서는 종종 "이 화면에서 저 화면으로 전환"하는 UI 흐름이 필요하죠.

탭 전환, 메뉴 접기/펼치기, FAQ 펼치기 등에서 말이에요.

이럴 때 자주 사용하는 기법이 바로 컴포넌트를 조건에 따라 보여주거나 숨기는 Show/Hide 패턴입니다.

🛠 간단한 Show/Hide 예제

function TabSwitcher() {
  const [tab, setTab] = useState("A");

  return (
    <div>
      <button onClick={() => setTab("A")}>Tab A</button>
      <button onClick={() => setTab("B")}>Tab B</button>

      {tab === "A" ? <ComponentA /> : <ComponentB />}
    </div>
  );
}

 

이 예제에서는 버튼 클릭 시 상태가 바뀌고, 그에 따라 A 컴포넌트 또는 B 컴포넌트가 화면에 표시됩니다.

완전한 페이지 전환은 아니지만 동적인 느낌을 주는 UI를 만들 수 있죠.

💡 팁: 조건이 많아질 땐 switch-case나 객체 매핑!

const components = {
  A: <ComponentA />,
  B: <ComponentB />,
  C: <ComponentC />
};

return (
  <div>
    <button onClick={() => setTab("A")}>Tab A</button>
    <button onClick={() => setTab("B")}>Tab B</button>
    <button onClick={() => setTab("C")}>Tab C</button>
    {components[tab]}
  </div>
);

 

이렇게 하면 코드가 훨씬 깔끔하고 유지보수도 쉬워집니다.

조건이 늘어날수록 이런 구조가 필요해요.

📦 FAQ 아코디언 구현 예시

function FAQItem() {
  const [open, setOpen] = useState(false);

  return (
    <div>
      <h4 onClick={() => setOpen(!open)}>Q. 이 기능은 어떻게 동작하나요?</h4>
      {open && <p>A. useState로 열고 닫을 수 있어요!</p>}
    </div>
  );
}

 

이런 UI는 실제 웹서비스에서 자주 쓰이죠.

사용자에게 필요한 정보를 보기 좋게 정리할 수 있으니까요.

✅ 핵심 요약

  • 상태(state)에 따라 컴포넌트를 조건적으로 렌더링
  • 여러 조건을 처리할 땐 객체 매핑으로 코드 간소화

다음은 리스트를 동적으로 렌더링하면서 key를 어떻게 다뤄야 할지에 대해 이야기해볼게요.

꼭 필요한 개념이니까 집중해주세요!

 

 

3. 리스트 동적 렌더링과 키(Key) 사용법 🧩

리액트에서 반복되는 UI,

예를 들어 댓글 목록이나 게시판 리스트 같은 걸 만들 때 자주 쓰는 게 동적 리스트 렌더링입니다.

그리고 이때 반드시 함께 써야 할 것이 바로 key 속성이에요.

이 key 하나로 성능과 버그 여부가 갈릴 수 있거든요.

🔁 기본 리스트 렌더링 예제

function FruitList() {
  const fruits = ["🍎 Apple", "🍌 Banana", "🍊 Orange"];

  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

 

여기서 key={index}처럼 index를 key로 사용하는 건 간단하긴 하지만,

항목의 순서가 바뀌거나 삽입/삭제가 많을 땐 비추천이에요.

컴포넌트 상태가 꼬일 수 있거든요.

📛 key 사용 시 주의사항

  • 리스트 항목이 바뀌거나 삽입/삭제되는 경우엔 index 대신 고유한 ID 사용 권장
  • key는 형제 요소 간의 구분자 역할이므로 절대 중복되면 안 됨

💼 실무 예시: 사용자 리스트 렌더링

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];

function UserList() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

 

이처럼 id 같은 고유 값을 key로 쓰면 리렌더링 이슈도 줄어들고, 성능 최적화에도 도움이 됩니다.

실무에서 무조건 쓰이는 방식이에요.

🧠 정리하면?

  1. 동적 리스트 렌더링에는 반드시 key를 사용한다
  2. index는 임시로만, 실제로는 고유한 식별자 사용
  3. 렌더링 효율성과 버그 예방 측면에서 매우 중요하다

이제 리스트뿐 아니라 입력창도 동적으로 제어할 시간이에요.

다음 파트에서는 동적 폼 처리에 대해 알아봅시다!

 

 

4. 동적 폼 처리와 입력값 관리 ✍️

리액트에서 사용자 입력을 처리할 때 가장 많이 사용하는 패턴이 바로 useStateonChange 이벤트의 조합입니다.

여기에 폼의 입력 필드를 동적으로 생성하거나 관리하려면 어떻게 해야 할까요?

이 섹션에서 그 해답을 찾아봅니다.

🧪 단일 입력 필드 제어

function SimpleForm() {
  const [name, setName] = useState("");

  return (
    <form>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>입력된 이름: {name}</p>
    </form>
  );
}

 

위 코드처럼 입력 필드를 valueonChange로 연결하면 제어 컴포넌트가 됩니다.

실시간으로 값이 반영되기 때문에 매우 직관적이죠.

📋 다중 필드 입력 처리

function MultiInputForm() {
  const [formData, setFormData] = useState({ name: "", email: "" });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  return (
    <form>
      <input name="name" value={formData.name} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <p>이름: {formData.name}, 이메일: {formData.email}</p>
    </form>
  );
}

 

이 방식은 입력 필드가 늘어나더라도 handleChange 하나로 다 제어할 수 있어서 굉장히 유용합니다.

⚠️ 입력값 초기화 팁

  • setFormData({ name: "", email: "" })을 사용해 초기화 가능
  • formRef.current.reset() 같은 DOM 방식도 상황에 따라 유용함

🔚 정리!

  1. 제어 컴포넌트 방식으로 입력값을 상태로 관리
  2. 다중 필드는 name 속성과 스프레드 연산자를 활용해 통합 관리
  3. 입력 초기화도 잊지 말고 꼭 처리할 것

이제 진짜 페이지 간 화면 전환이 궁금하시죠?

다음 섹션에서 React Router를 활용한 동적 화면 전환을 마스터해봐요!

 

 

5. React Router로 구현하는 동적 화면 전환 🚀

리액트 앱이 복잡해질수록 페이지 전환은 필수가 되죠.

React Router를 이용하면 SPA 구조를 유지하면서도 화면 전환처럼 보이는 멋진 UI를 만들 수 있어요.

사용자 경험을 부드럽게 만드는 핵심 기술이죠!

🔗 기본 설정: 라우터 구조 만들기

import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link
} from "react-router-dom";

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">홈</Link>
        <Link to="/about">소개</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

 

BrowserRouterRoutes, Route 컴포넌트를 조합해서 각 URL에 해당하는 컴포넌트를 연결하면 페이지처럼 보이는 화면 전환을 구현할 수 있습니다.

🧭 동적 파라미터와 URL 활용

function Profile() {
  const { username } = useParams();
  return <h2>{username}님의 프로필</h2>;
}

// 라우터 설정
<Route path="/user/:username" element={<Profile />} />

 

이렇게 하면 /user/jisu처럼 URL에 따라 내용을 동적으로 렌더링할 수 있어요.

유저 프로필, 상품 상세페이지 등에 딱이죠!

📦 React Router 실무 팁

  • 페이지 이동 후 스크롤 상단 이동은 useEffect + window.scrollTo(0, 0)
  • useNavigate()로 프로그래밍 방식 이동도 가능

🔍 요약 정리

  1. React Router는 SPA에서 가짜 페이지 이동을 구현하는 도구
  2. RouteLink 조합으로 전환 가능
  3. URL 파라미터로 동적 렌더링 처리도 가능

마지막 파트에서는 커스텀 훅을 만들어 동적 UI를 더 효율적으로 제어하는 방법을 알려드릴게요.

자동화와 재사용성까지 챙기는 시간입니다!

 

 

6. 커스텀 훅으로 만든 동적 UI 제어 🧠

동적 UI가 점점 복잡해지면, 공통 로직을 재사용할 일이 많아지죠.

예를 들어 토글 기능, 폼 초기화, 특정 키보드 입력 감지 등은 여러 곳에서 반복됩니다.

이럴 땐 커스텀 훅(Custom Hook)으로 추상화하면 훨씬 효율적이에요!

🔄 useToggle 훅 만들기

import { useState, useCallback } from "react";

function useToggle(initialValue = false) {
  const [state, setState] = useState(initialValue);
  const toggle = useCallback(() => setState((prev) => !prev), []);
  return [state, toggle];
}

 

이 훅은 true/false 상태를 간단히 토글해주는 역할을 합니다.

useCallback으로 성능 최적화까지 고려했어요.

✅ 사용 예시: FAQ 아코디언

function FAQItem() {
  const [open, toggleOpen] = useToggle();

  return (
    <div>
      <h4 onClick={toggleOpen}>Q. 커스텀 훅이 뭐예요?</h4>
      {open && <p>A. 반복되는 훅 로직을 추상화한 함수입니다!</p>}
    </div>
  );
}

 

이처럼 커스텀 훅을 사용하면 코드가 훨씬 깔끔해지고, 여러 곳에서 같은 방식으로 사용할 수 있어 유지보수도 쉬워집니다.

📦 커스텀 훅 팁

  • 이름은 반드시 use로 시작해야 함 (예: useForm, useInput 등)
  • 다른 훅을 내부에서 자유롭게 사용할 수 있음

🎯 요약!

  1. 동일한 훅 로직이 반복된다면 커스텀 훅으로 추상화
  2. 이름은 반드시 use로 시작
  3. 재사용성과 유지보수성 모두 향상됨

자, 이제 리액트 동적 화면 처리를 위한 모든 기초는 끝났어요.

마지막으로 전체 내용을 정리하며 마무리해볼게요. 😊

 

 

🧾 리액트 동적 화면 처리, 이제 어렵지 않죠?

지금까지 useState를 이용한 조건부 렌더링부터 React Router를 활용한 페이지 전환, 그리고 커스텀 훅으로 코드 리팩토링까지!

동적 UI를 만드는 핵심 개념을 하나씩 배워봤습니다.

초보자라면 처음엔 헷갈릴 수도 있지만, 직접 예제 코드를 따라 작성하다 보면 금세 감이 잡힐 거예요.

실제로 서비스를 개발하다 보면 사용자의 인터랙션에 반응해서 화면이 바뀌는 상황은 무수히 많습니다.

이번 포스트가 그런 상황을 어떻게 스마트하게 처리할지에 대한 든든한 실전 가이드가 되었기를 바라요.

 

여러분도 직접 연습하면서, 필요한 기능을 커스텀 훅으로 만들어보거나, React Router로 화면 전환을 구성해보세요. 실전에서 쓸 수 있는 진짜 스킬은 이렇게 쌓이는 거랍니다!

 

반응형
반응형

리액트 이벤트 처리 마스터하기 🎯:
초보자를 위한 실전 가이드

"onClick도 잘 모르겠는데, 이벤트 객체? this 바인딩?" 🤯
리액트 초보가 가장 헷갈리는 바로 그 주제!
이제는 명쾌하게 정리해드릴게요.
반응형

 

안녕하세요, 여러분! 😊

리액트를 처음 접하고 나서 가장 많이 헤매는 부분 중 하나가 바로 "이벤트 처리" 아닐까요?

JavaScript에서는 잘 되던 이벤트 등록이, 리액트에선 갑자기 JSX 문법이니 이벤트 객체니 하면서 헷갈리는 경우 많죠.

이번 글에서는 리액트의 이벤트 시스템을 차근차근 정리하고, 자주 실수하는 포인트도 함께 짚어드릴게요.

실습 위주로 따라오시면, 글 다 읽고 나면 자신 있게 "이벤트 처리? 그건 이제 껌이죠!"라고 말하실 수 있을 거예요.

그럼 시작해볼까요? 😊

 

1. 리액트의 기본 이벤트 바인딩 방식 🧷

리액트에서 이벤트를 다루는 방식은 일반 HTML과 다소 다릅니다.

JSX 문법을 따르기 때문에 이벤트 속성을 onclick이 아닌 onClick처럼 카멜케이스(camelCase)로 작성해야 하고, 실행할 함수는 문자열이 아닌 함수 참조로 전달해야 해요.

예제 코드 👇

function ClickButton() {
  function handleClick() {
    alert('버튼이 클릭되었어요!');
  }

  return (
    <button onClick={handleClick}>
      클릭해보세요!
    </button>
  );
}

 

위 코드에서 onClick={handleClick}처럼 함수 이름만 넘기고 괄호는 붙이지 않죠?

괄호를 붙이면 컴포넌트 렌더링 시점에 함수가 실행돼버리니까 꼭 조심해야 해요!

초보자 주의 포인트 📌

  • JSX에서는 이벤트 이름을 반드시 카멜케이스로 작성해야 합니다.
  • 이벤트 핸들러는 함수 참조로 전달해야 하며, 함수 호출문이 들어가면 안 돼요!

잘못된 예와 올바른 예 🆚

잘못된 방식 올바른 방식
onclick="handleClick()" onClick={handleClick}
onClick={handleClick()} onClick={() => handleClick()}

익명 함수로 감싸는 방식은 상황에 따라 사용하면 유용하지만, 함수 내부에서 props나 state를 다뤄야 할 때만 사용하는 게 좋아요!

 

 

2. SyntheticEvent란? 🤖 진짜 이벤트 객체와의 차이

리액트에서 이벤트를 처리할 때 SyntheticEvent라는 개념이 등장해요.

"이게 뭐야?" 싶으실 텐데, 쉽게 말해 브라우저의 native event를 감싼 리액트 전용 래퍼 객체라고 생각하시면 됩니다.

왜 SyntheticEvent를 쓸까?

  • 브라우저 간 이벤트 처리 방식이 달라서, 이를 통합하기 위해 리액트가 만든 표준화된 이벤트 객체예요.
  • stopPropagation()이나 preventDefault() 같은 메서드도 동일하게 사용할 수 있어요.

SyntheticEvent의 특징 🌐

기능 설명
통합 API 브라우저마다 다른 이벤트 처리 방식을 하나의 인터페이스로 제공
자동 메모리 최적화 이벤트 객체는 자동으로 풀(pool)에 반환되어 재사용됨
비동기 코드에서의 문제 setTimeout 등에서 접근하면 이벤트 객체가 null로 초기화되어 있음

Tip! 이벤트 객체를 비동기적으로 사용해야 할 경우 e.persist()를 꼭 호출하세요!

예시 코드 👇

function LogEvent() {
  function handleClick(e) {
    e.persist(); // SyntheticEvent 풀 반환 방지
    setTimeout(() => {
      console.log(e.type); // 'click'
    }, 1000);
  }

  return (
    <button onClick={handleClick}>이벤트 타입 출력</button>
  );
}

 

이처럼 SyntheticEvent는 리액트의 퍼포먼스 최적화를 위한 핵심 설계 중 하나이며, JS 이벤트와 매우 유사하지만 React 내부에서 관리된다는 점을 꼭 기억해 주세요!

 

 

3. 이벤트 객체 다루기 – e.preventDefault(), e.target 등 🧪

리액트 이벤트 핸들러에서 전달받는 e는 앞서 배운 SyntheticEvent 객체입니다.

이 객체를 활용하면 preventDefault()로 기본 동작을 막거나, e.target을 통해 이벤트가 발생한 요소에 접근할 수 있어요.

📌 주요 이벤트 객체 프로퍼티/메서드 요약

속성/메서드 설명
e.preventDefault() 기본 동작(예: 폼 제출, 링크 이동 등)을 막습니다.
e.stopPropagation() 이벤트 버블링(상위 요소로 전파)을 막습니다.
e.target 이벤트가 실제로 발생한 DOM 요소를 가리킵니다.
e.currentTarget 이벤트 핸들러가 바인딩된 요소를 가리킵니다.

🎯 예제 1 – 기본 동작 막기 (preventDefault)

function FormExample() {
  function handleSubmit(e) {
    e.preventDefault();
    alert('폼 제출이 막혔어요!');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="이름 입력" />
      <button type="submit">제출</button>
    </form>
  );
}

 

이 예제는 기본적으로 페이지 리로드를 막고, alert만 출력해요.

만약 e.preventDefault()를 생략하면 폼이 제출되면서 페이지가 새로고침됩니다.

🎯 예제 2 – target과 currentTarget 구분

function TargetExample() {
  function handleClick(e) {
    console.log('target:', e.target.tagName);
    console.log('currentTarget:', e.currentTarget.tagName);
  }

  return (
    <div onClick={handleClick} style={{ padding: '1em', border: '1px solid #ccc' }}>
      <button>버튼 클릭!</button>
    </div>
  );
}

 

이 코드에서 e.target실제 클릭된 버튼이고,

e.currentTargetonClick이 바인딩된 div를 가리킵니다.

DOM 이벤트 흐름을 이해할 때 아주 중요한 개념이에요!

 

💡 정리!

이벤트 객체를 잘 활용하면 복잡한 사용자 상호작용도 깔끔하게 구현할 수 있어요.

특히 targetcurrentTarget의 차이는 반드시 숙지해두세요!

 

 

4. this 바인딩 문제 해결법과 화살표 함수 사용법 🧭

리액트에서 이벤트 핸들러를 작성하다 보면 thisundefined가 되는 문제가 발생하곤 해요.

특히 클래스형 컴포넌트를 사용할 때 this.handleClick처럼 핸들러를 호출하려고 하면 에러가 발생할 수 있죠.

🎯 클래스형 컴포넌트에서의 this 바인딩

class EventExample extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this); // 바인딩 필수
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        클릭 횟수: {this.state.count}
      </button>
    );
  }
}

 

this.handleClick을 이벤트에 넘기기 전에 생성자에서 바인딩을 꼭 해줘야 해요.

그렇지 않으면 this가 undefined가 되어 this.setState가 작동하지 않아요!

💡 화살표 함수로 해결하기

class ArrowEventExample extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        클릭 횟수: {this.state.count}
      </button>
    );
  }
}

 

handleClick화살표 함수로 선언하면 생성자에서 바인딩하지 않아도 this가 자동으로 바인딩돼요.

요즘은 이 방식이 더 보편적으로 쓰이고 있어요!

🚀 함수형 컴포넌트에서는?

함수형 컴포넌트에서는 this를 쓰지 않기 때문에 바인딩 문제가 아예 없습니다!

대신 useState, useCallback 등 훅을 적절히 활용해서 상태나 함수를 관리하면 돼요.

function FunctionalEvent() {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    setCount(prev => prev + 1);
  };

  return (
    <button onClick={handleClick}>
      클릭 횟수: {count}
    </button>
  );
}

 

🔥 핵심 요약:

클래스형 컴포넌트는 this 바인딩이 필수!

하지만 함수형 컴포넌트에서는 바인딩 걱정 없이 깔끔하게 이벤트를 처리할 수 있어요.

이제 this 때문에 헤맬 일은 없겠죠?

 

 

5. 커스텀 이벤트 핸들러 구조 설계하기 🛠️

리액트에서 이벤트 처리는 단순히 onClick={() => ...}처럼 인라인으로 끝낼 수도 있지만, 규모가 커질수록 이벤트 핸들러의 구조화가 굉장히 중요해집니다.

컴포넌트가 많아지고 이벤트가 늘어나면 로직 재사용유지보수에 큰 차이를 만들어요.

💡 커스텀 핸들러를 분리하는 이유

  • 핸들러 로직이 복잡해질 경우 읽기 어려운 JSX가 되기 쉽습니다.
  • 여러 컴포넌트에서 같은 로직을 재사용할 때 중복을 피할 수 있습니다.

🧩 예제: 입력 값 검증 핸들러

function useInputValidator() {
  const validate = (value) => {
    if (value.length < 3) {
      return "3자 이상 입력해주세요!";
    }
    return "";
  };

  return validate;
}

function InputForm() {
  const [input, setInput] = React.useState("");
  const [error, setError] = React.useState("");
  const validate = useInputValidator();

  const handleChange = (e) => {
    const val = e.target.value;
    setInput(val);
    setError(validate(val));
  };

  return (
    <div>
      <input value={input} onChange={handleChange} />
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

 

위 코드에서는 useInputValidator라는 커스텀 훅으로 입력값 검증 로직을 분리했어요.

핸들러 함수는 깔끔하게 유지되고, 검증 로직은 재사용 가능합니다.

⚙️ 다양한 상황에 맞는 이벤트 구조 설계 팁

상황 설계 팁
반복적인 입력 검증 커스텀 훅으로 검증 함수 모듈화
복잡한 버튼 액션 핸들러에서 여러 유틸 함수를 조합
다양한 트리거로 같은 동작 공통 로직을 별도 핸들러로 추출

🚀 요약!

리액트 이벤트 처리를 고수처럼 만들고 싶다면, 핸들러를 짧고 간결하게 유지하고, 복잡한 로직은 커스텀 훅이나 함수로 분리해보세요.

유지보수가 10배 쉬워집니다!

 

 

6. 예제로 알아보는 이벤트 처리 베스트 프랙티스 🧠

리액트의 이벤트 시스템을 이론적으로 이해하는 것도 중요하지만, 예제를 통해 감을 잡는 것이 훨씬 효과적입니다.

이번에는 실무에서 자주 등장하는 이벤트 시나리오 3가지와 그에 맞는 처리 방법을 살펴보겠습니다.

🧪 예제 1: 인풋 실시간 반영 + 엔터키 제출

function ChatInput() {
  const [message, setMessage] = React.useState("");

  const handleChange = (e) => setMessage(e.target.value);

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      alert(`메시지 전송: ${message}`);
      setMessage("");
    }
  };

  return (
    <textarea
      value={message}
      onChange={handleChange}
      onKeyDown={handleKeyDown}
      placeholder="메시지를 입력하고 Enter를 눌러보세요"
    />
  );
}

 

이벤트를 중첩 조건으로 처리할 수 있고, preventDefault로 기본 동작을 제어할 수 있다는 것을 보여주는 예제예요.

🧪 예제 2: 동적으로 생성된 리스트 항목 클릭

function DynamicList() {
  const items = ["사과", "바나나", "오렌지"];

  const handleClick = (item) => {
    alert(`${item}을 선택하셨습니다!`);
  };

  return (
    <ul>
      {items.map((item, idx) => (
        <li key={idx} onClick={() => handleClick(item)}>
          {item}
        </li>
      ))}
    </ul>
  );
}

 

이벤트 핸들러에 익명 함수를 사용해서 인자를 넘기는 패턴이에요.

많이 쓰이는 패턴이지만 렌더링 성능에 영향을 줄 수 있으니 상황에 따라 useCallback도 고려해보세요!

🧪 예제 3: 부모에서 자식 이벤트 제어

function ParentComponent() {
  const handleChildClick = (msg) => {
    console.log("자식 클릭됨:", msg);
  };

  return <ChildComponent onCustomClick={handleChildClick} />;
}

function ChildComponent({ onCustomClick }) {
  return (
    <button onClick={() => onCustomClick("Hello from Child")}>
      자식 버튼
    </button>
  );
}

 

부모가 자식의 이벤트를 제어할 수 있도록 props로 핸들러를 전달하는 전형적인 패턴입니다.

컴포넌트 간 상호작용을 설계할 때 자주 사용되죠.

✅ 실전에서 꼭 기억할 베스트 프랙티스

  • 이벤트 핸들러는 짧고 명확하게 구성하자.
  • 중복되는 로직은 커스텀 훅이나 외부 함수로 분리하자.
  • props로 이벤트를 넘길 때는 이름을 명확하게 지정하자 (onClick보다는 onItemSelect 등).

🎉 결론!

이벤트 처리를 잘하면 리액트 앱의 UX, 유지보수, 성능까지 모두 향상됩니다.

여기까지 익히셨다면 이제 실전에서도 막힘 없이 이벤트를 자유자재로 다루실 수 있어요!

 

🔚 이벤트, 이제는 두렵지 않다!

여기까지 따라오시느라 정말 수고 많으셨어요!

처음엔 onClick 하나도 헷갈리던 리액트 이벤트 처리 방식, 이제는 꽤나 자신감이 붙지 않으셨나요?

이번 글을 통해 이벤트 객체의 구조부터, this 바인딩 문제, 커스텀 핸들러 설계, 실전 예제까지 모두 다뤄봤습니다.

앞으로는 이벤트 처리 코드를 짤 때,

"이걸 함수로 빼면 더 깔끔할까?",

"preventDefault 꼭 써야 할까?"

같은 고민을 스스로 던지고 답할 수 있게 되실 거예요.

 

다음엔 폼 처리, 상태 동기화 등 더 깊은 리액트 주제로 다시 찾아올게요.

놓치지 않도록 구독이나 북마크 해두시는 거 잊지 마시구요! 😊

반응형

+ Recent posts