본문 바로가기

JSX 심화

1. JSX 심화

이번 장에서는 JSX의 고급 기능들과 실제 개발에서 자주 사용되는 패턴들을 학습해보겠습니다.

1.1 리스트 렌더링

JavaScript 배열을 사용해서 여러 개의 요소를 렌더링할 수 있습니다.

1.1.1 기본 리스트 렌더링

function App() {
  const fruits = ['사과', '바나나', '오렌지', '포도'];

  return (
    <div>
      <h1>과일 목록</h1>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

1.1.2 객체 배열 렌더링

function App() {
  const users = [
    { id: 1, name: '김개발', age: 25, job: '프론트엔드 개발자' },
    { id: 2, name: '이디자인', age: 28, job: 'UI/UX 디자이너' },
    { id: 3, name: '박백엔드', age: 30, job: '백엔드 개발자' }
  ];

  return (
    <div>
      <h1>팀 구성원</h1>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap' }}>
        {users.map(user => (
          <div key={user.id} style={{
            border: '1px solid #ddd',
            borderRadius: '8px',
            padding: '15px',
            minWidth: '200px'
          }}>
            <h3>{user.name}</h3>
            <p>나이: {user.age}세</p>
            <p>직업: {user.job}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

:::warning Key의 중요성 리스트를 렌더링할 때 각 항목에 고유한 key 속성을 제공해야 합니다. React가 변경사항을 효율적으로 추적할 수 있게 도와줍니다. 가능하면 배열 인덱스보다는 고유한 ID를 사용하는 것이 좋습니다. :::

1.2 조건부 렌더링 심화

1.2.1 if-else 패턴

function App() {
  const user = {
    name: '김개발',
    isVip: true,
    points: 1500
  };

  // 조건에 따라 다른 컴포넌트 반환
  const getUserBadge = () => {
    if (user.points > 1000) {
      return <span style={{color: 'gold'}}>⭐ 골드 회원</span>;
    } else if (user.points > 500) {
      return <span style={{color: 'silver'}}>🥈 실버 회원</span>;
    } else {
      return <span style={{color: 'bronze'}}>🥉 브론즈 회원</span>;
    }
  };

  return (
    <div>
      <h1>안녕하세요, {user.name}님!</h1>
      <p>{getUserBadge()}</p>
      {user.isVip && (
        <div style={{
          background: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
          color: 'white',
          padding: '10px',
          borderRadius: '5px',
          margin: '10px 0'
        }}>
          🎉 VIP 회원 혜택을 받으실 수 있습니다!
        </div>
      )}
    </div>
  );
}

1.2.2 논리 연산자 활용

function App() {
  const todos = [
    { id: 1, text: 'React 공부하기', completed: false },
    { id: 2, text: 'JSX 이해하기', completed: true },
    { id: 3, text: '프로젝트 만들기', completed: false }
  ];

  const completedCount = todos.filter(todo => todo.completed).length;

  return (
    <div>
      <h1>할 일 목록</h1>
      <p>완료된 할 일: {completedCount}개</p>
      
      {todos.length === 0 ? (
        <p>할 일이 없습니다.</p>
      ) : (
        <ul>
          {todos.map(todo => (
            <li key={todo.id} style={{
              textDecoration: todo.completed ? 'line-through' : 'none',
              color: todo.completed ? '#888' : '#000'
            }}>
              {todo.text}
              {todo.completed && ' ✅'}
            </li>
          ))}
        </ul>
      )}
      
      {completedCount === todos.length && todos.length > 0 && (
        <p style={{color: 'green', fontWeight: 'bold'}}>
          🎉 모든 할 일을 완료했습니다!
        </p>
      )}
    </div>
  );
}

1.3 이벤트 처리 기초

JSX에서 이벤트를 처리하는 방법을 알아봅시다.

1.3.1 기본 이벤트 처리

function App() {
  const handleButtonClick = () => {
    alert('버튼이 클릭되었습니다!');
  };

  const handleInputChange = (event) => {
    console.log('입력값:', event.target.value);
  };

  const handleFormSubmit = (event) => {
    event.preventDefault(); // 페이지 새로고침 방지
    alert('폼이 제출되었습니다!');
  };

  return (
    <div>
      <h1>이벤트 처리 예제</h1>
      
      <button onClick={handleButtonClick}>
        클릭하세요
      </button>
      
      <br /><br />
      
      <input 
        type="text" 
        placeholder="입력해보세요"
        onChange={handleInputChange}
      />
      
      <br /><br />
      
      <form onSubmit={handleFormSubmit}>
        <input type="text" placeholder="이름을 입력하세요" />
        <button type="submit">제출</button>
      </form>
    </div>
  );
}

1.3.2 이벤트 객체와 매개변수 전달

function App() {
  const colors = ['red', 'blue', 'green', 'yellow', 'purple'];
  
  const handleColorClick = (color, event) => {
    console.log(`선택된 색상: ${color}`);
    console.log('클릭된 요소:', event.target);
  };

  return (
    <div>
      <h1>색상 선택</h1>
      <div style={{ display: 'flex', gap: '10px' }}>
        {colors.map(color => (
          <button
            key={color}
            style={{
              backgroundColor: color,
              color: 'white',
              border: 'none',
              padding: '10px 20px',
              borderRadius: '5px',
              cursor: 'pointer'
            }}
            onClick={(event) => handleColorClick(color, event)}
          >
            {color}
          </button>
        ))}
      </div>
    </div>
  );
}

1.4 동적 스타일링

조건에 따라 스타일을 동적으로 변경하는 방법입니다.

function App() {
  const cards = [
    { id: 1, title: '긴급', priority: 'high', content: '버그 수정 필요' },
    { id: 2, title: '보통', priority: 'medium', content: '기능 개선' },
    { id: 3, title: '낮음', priority: 'low', content: '문서 정리' }
  ];

  const getPriorityStyle = (priority) => {
    const baseStyle = {
      padding: '15px',
      margin: '10px',
      borderRadius: '8px',
      border: '2px solid'
    };

    switch (priority) {
      case 'high':
        return { ...baseStyle, borderColor: '#ff4757', backgroundColor: '#fff0f0' };
      case 'medium':
        return { ...baseStyle, borderColor: '#ffa502', backgroundColor: '#fffaf0' };
      case 'low':
        return { ...baseStyle, borderColor: '#2ed573', backgroundColor: '#f0fff0' };
      default:
        return baseStyle;
    }
  };

  const getPriorityEmoji = (priority) => {
    switch (priority) {
      case 'high': return '🔴';
      case 'medium': return '🟡';
      case 'low': return '🟢';
      default: return '⚪';
    }
  };

  return (
    <div>
      <h1>작업 카드</h1>
      {cards.map(card => (
        <div key={card.id} style={getPriorityStyle(card.priority)}>
          <h3>{getPriorityEmoji(card.priority)} {card.title}</h3>
          <p>{card.content}</p>
          <small>우선순위: {card.priority}</small>
        </div>
      ))}
    </div>
  );
}

1.5 실습: 간단한 계산기 UI

지금까지 배운 내용을 활용해서 계산기 UI를 만들어봅시다:

function App() {
  const buttons = [
    ['C', '±', '%', '÷'],
    ['7', '8', '9', '×'],
    ['4', '5', '6', '−'],
    ['1', '2', '3', '+'],
    ['0', '.', '=']
  ];

  const handleButtonClick = (value) => {
    console.log('버튼 클릭:', value);
  };

  const getButtonStyle = (value) => {
    const baseStyle = {
      border: 'none',
      borderRadius: '8px',
      fontSize: '20px',
      cursor: 'pointer',
      transition: 'all 0.2s'
    };

    // 숫자 버튼
    if (!isNaN(value) || value === '.') {
      return {
        ...baseStyle,
        backgroundColor: '#f0f0f0',
        color: '#000',
        padding: '20px'
      };
    }

    // 연산자 버튼
    if (['+', '−', '×', '÷', '='].includes(value)) {
      return {
        ...baseStyle,
        backgroundColor: '#ff9500',
        color: '#fff',
        padding: '20px'
      };
    }

    // 기능 버튼
    return {
      ...baseStyle,
      backgroundColor: '#a6a6a6',
      color: '#000',
      padding: '20px'
    };
  };

  return (
    <div style={{
      maxWidth: '300px',
      margin: '20px auto',
      padding: '20px',
      backgroundColor: '#000',
      borderRadius: '20px'
    }}>
      {/* 디스플레이 */}
      <div style={{
        backgroundColor: '#000',
        color: '#fff',
        fontSize: '48px',
        textAlign: 'right',
        padding: '20px',
        marginBottom: '20px',
        borderRadius: '10px',
        minHeight: '60px'
      }}>
        0
      </div>

      {/* 버튼 그리드 */}
      {buttons.map((row, rowIndex) => (
        <div key={rowIndex} style={{
          display: 'grid',
          gridTemplateColumns: rowIndex === 4 ? '2fr 1fr 1fr' : 'repeat(4, 1fr)',
          gap: '10px',
          marginBottom: '10px'
        }}>
          {row.map((button, buttonIndex) => (
            <button
              key={button}
              style={getButtonStyle(button)}
              onClick={() => handleButtonClick(button)}
            >
              {button}
            </button>
          ))}
        </div>
      ))}
    </div>
  );
}

1.6 JSX 베스트 프랙티스

1.6.1 가독성을 위한 구조화

function App() {
  // 복잡한 JSX는 변수로 분리
  const header = (
    <header>
      <h1>제목</h1>
      <nav>네비게이션</nav>
    </header>
  );

  const footer = (
    <footer>
      <p>© 2025 My App</p>
    </footer>
  );

  return (
    <div>
      {header}
      <main>메인 콘텐츠</main>
      {footer}
    </div>
  );
}

1.6.2 조건부 렌더링 간소화

// ❌ 복잡한 조건부 렌더링
function App() {
  return (
    <div>
      {user.isLoggedIn ? (
        user.isVip ? (
          <VipWelcome user={user} />
        ) : (
          <RegularWelcome user={user} />
        )
      ) : (
        <LoginPrompt />
      )}
    </div>
  );
}

// ✅ 함수로 분리
function App() {
  const renderWelcome = () => {
    if (!user.isLoggedIn) return <LoginPrompt />;
    return user.isVip ? <VipWelcome user={user} /> : <RegularWelcome user={user} />;
  };

  return <div>{renderWelcome()}</div>;
}

1.7 정리

JSX의 고급 기능들을 학습했습니다:

  1. 리스트 렌더링과 key 속성의 중요성
  2. 조건부 렌더링의 다양한 패턴
  3. 이벤트 처리 기초
  4. 동적 스타일링 방법
  5. JSX 베스트 프랙티스

다음 장에서는 React의 핵심 개념인 컴포넌트에 대해 더 자세히 알아보겠습니다.