React 기초 - 컴포넌트에 props 전달, 조건부 렌더링, 리스트 렌더링, 이벤트 핸들러

김명원·2024년 12월 11일
0

learnReact

목록 보기
1/26

컴포넌트에 Props 전달하기

React 컴포넌트는 props를 사용해 부모에서 자식으로 데이터를 전달합니다. props는 HTML의 속성과 유사하지만, 객체, 배열, 함수 등 모든 JavaScript 값을 전달할 수 있습니다. 이를 통해 컴포넌트 간 데이터를 효율적으로 공유할 수 있습니다.


Props란?

props는 JSX 태그에 정보를 전달하는 속성입니다. 아래는 기본적인 예시입니다:

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return <Avatar />;
}

위 코드에서 <img> 태그에 전달된 className, src, alt 등의 값은 HTML 표준 속성이며, React 컴포넌트에서도 그대로 사용할 수 있습니다.


Props 전달 방법

React에서는 두 단계를 통해 부모 컴포넌트에서 자식 컴포넌트로 props를 전달하고 사용할 수 있습니다.

1단계: 부모 컴포넌트에서 자식 컴포넌트에 Props 전달하기

부모 컴포넌트에서 props를 사용해 데이터를 전달합니다. 예를 들어, Avatar 컴포넌트에 person 객체와 size 값을 전달합니다.

export default function Profile() {
  return (
    <Avatar
      person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
      size={100}
    />
  );
}

💡 주의:
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}와 같이 이중 중괄호({{ }})는 JSX에서 객체를 표현하기 위한 문법입니다.


2단계: 자식 컴포넌트에서 Props 읽기

전달받은 props는 함수 매개변수로 받아 사용할 수 있습니다. 보통 구조 분해 할당을 사용해 간단히 작성합니다.

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={`https://i.imgur.com/${person.imageId}.jpg`}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

props를 사용하는 전체 예시는 아래와 같습니다:

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={`https://i.imgur.com/${person.imageId}.jpg`}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar person={{ name: 'Katsuko Saruhashi', imageId: 'YfeOqp2' }} size={100} />
      <Avatar person={{ name: 'Aklilu Lemma', imageId: 'OKS67lh' }} size={80} />
      <Avatar person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }} size={50} />
    </div>
  );
}

Props의 기본값 설정하기

컴포넌트에 props가 전달되지 않았을 경우, 기본값을 설정할 수 있습니다. 기본값은 구조 분해 할당을 활용해 설정합니다.

function Avatar({ person, size = 100 }) {
  return (
    <img
      className="avatar"
      src={`https://i.imgur.com/${person.imageId}.jpg`}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

위 코드에서 size가 전달되지 않을 경우, 기본값으로 100이 사용됩니다. 그러나 size={0} 또는 size={null}로 전달된다면 기본값은 사용되지 않습니다.


Props 활용 팁

  • 독립적인 컴포넌트 구성:
    부모 컴포넌트와 자식 컴포넌트를 독립적으로 관리할 수 있습니다.

    • 부모 컴포넌트는 자식 컴포넌트의 내부 동작을 신경 쓰지 않고 props를 통해 데이터만 전달합니다.
    • 자식 컴포넌트는 전달받은 props를 활용해 렌더링합니다.
  • 함수의 매개변수처럼 활용:
    props는 함수의 매개변수와 동일하게 동작합니다. 모든 컴포넌트는 하나의 props 객체를 매개변수로 받습니다.

function Avatar(props) {
  const { person, size } = props;
  // 또는
  let person = props.person;
  let size = props.size;
}

💡 주의:
props를 구조 분해 할 때, 반드시 중괄호({})로 감싸야 합니다:
function Avatar({ person, size }) { ... }


결론

React에서 props는 컴포넌트 간 데이터를 주고받는 중요한 역할을 합니다. 함수의 인수처럼 동작하며, 컴포넌트를 독립적이고 재사용 가능하게 만듭니다. 이를 통해 유지보수성과 확장성을 높일 수 있습니다.


조건부 렌더링

React 컴포넌트는 조건에 따라 다른 UI를 표시할 수 있습니다. 자주 사용되는 조건부 렌더링 방법은 다음과 같습니다:

  1. if
  2. 논리 AND (&&)
  3. 삼항 연산자 (? :)

1. 조건부로 return하기

if문 사용

function Item({ name, isPacked }) {
  if (isPacked) {
    return <li className="item">{name}</li>;
  }
  return <li className="item">{name}</li>;
}

null을 사용하여 아무것도 렌더링하지 않기

컴포넌트에서 아무것도 렌더링하지 않으려면 null을 반환할 수 있습니다:

function Item({ name, isPacked }) {
  if (isPacked) {
    return null;
  }
  return <li className="item">{name}</li>;
}

2. 조건부를 JSX에 포함하기

중복된 코드를 줄이고 가독성을 높이기 위해 조건부를 JSX에 포함할 수 있습니다.

삼항 연산자 (? :)

삼항 연산자를 사용하면 코드가 더 간결해집니다:

return (
  <li className="item">
    {isPacked ? name + ' ✔' : name}
  </li>
);

복잡한 조건부는 자식 컴포넌트로 추출하여 코드의 가독성을 높이세요.


논리 AND 연산자 (&&)

조건이 참일 때만 특정 JSX를 렌더링하려면 &&를 사용할 수 있습니다:

return (
  <li className="item">
    {name} {isPacked && '✔'}
  </li>
);

3. 변수에 조건부로 JSX 할당하기

조건부 로직이 복잡할 경우, JSX를 변수에 저장하여 사용하세요:

function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = <del>{name + ' ✔'}</del>;
  }
  return <li className="item">{itemContent}</li>;
}

💡 TIP: JavaScript 문법을 잘 활용하면 React 컴포넌트뿐만 아니라 모든 JS 코드의 가독성과 유지보수성을 높일 수 있습니다!


리스트 렌더링

리스트 렌더링 하기

리액트에서 유사한 컴포넌트를 여러 개 표시하고 싶을 때, map()filter()를 활용해 데이터를 필터링하고 컴포넌트 배열로 변환하여 렌더링할 수 있습니다.

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

위 코드는 아래와 같이 렌더링됩니다.

<ul>
  <li>Creola Katherine Johnson: mathematician</li>
  <li>Mario José Molina-Pasquel Henríquez: chemist</li>
  <li>Mohammad Abdus Salam: physicist</li>
  <li>Percy Lavon Julian: chemist</li>
  <li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>

map()filter()로 리스트 렌더링

map()을 사용하여 리스트를 렌더링할 때, 각 항목을 CourseItem 컴포넌트로 전달할 수 있습니다.

import Card from '../Card';
import CourseItem from './CourseItem'

function CourseListCard({ items }) {
  return (
    <Card title='강의 목록'>
      <div className='courses'>
        {items.map(item => <CourseItem {...item} />)}
      </div>
    </Card>
  );
}

export default CourseListCard;

filter()를 사용해 조건에 맞는 항목만 렌더링할 수도 있습니다.

import './App.css';
import CourseListCard from './components/course/CourseListCard';

function App() {
  const items = [
    { title: '입문자를 위한 HTML&CSS', isFavorite: false },
    { title: '자바스크립트 입문', isFavorite: true },
    { title: '포트폴리오 사이트 만들기', isFavorite: true },
  ];

  const favoriteItems = items.filter(item => item.isFavorite);

  return (
    <main style={{ flexDirection: 'column', gap: '1rem' }}>
      <CourseListCard title="강의 목록" items={items} />
      <CourseListCard title="관심 강의" items={favoriteItems} />
    </main>
  );
}

export default App;

key를 사용해 리스트 항목을 고유하게 만들기

리스트 항목에 key를 사용해야 경고 없이 효율적으로 렌더링할 수 있습니다.

<li key={item.id}>{item.title}</li>

key는 각 컴포넌트가 배열의 항목을 고유하게 식별할 수 있도록 도와줍니다.


여러 DOM 노드를 가진 리스트 항목

여러 개의 DOM 노드를 렌더링할 때는 Fragment를 사용하여 key를 전달할 수 있습니다.

import { Fragment } from 'react';

const listItems = people.map(person =>
  <Fragment key={person.id}>
    <h1>{person.name}</h1>
    <p>{person.bio}</p>
  </Fragment>
);

Fragment는 실제 DOM에 나타나지 않으며, 여러 엘리먼트를 묶어서 하나의 키를 전달할 수 있게 해줍니다.


이벤트 핸들링 하기

이벤트핸들러


이벤트 핸들러는 사용자가 마우스를 클릭하거나 입력 상자에서 키보드를 타이핑하는 등 사용자와의 상호작용에 따라 실행되는 사용자 정의 함수입니다. React에서는 JSX에 이벤트 핸들러를 추가할 수 있습니다.

이벤트 핸들러 추가하기


이벤트 핸들러를 추가하려면 먼저 함수를 정의하고 이를 JSX 태그에 props 형태로 전달해야 합니다. 아래 예시는 아무런 동작도 수행하지 않는 버튼입니다.

// App.jsx
export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

이제 다음 3단계 과정을 거쳐 사용자가 버튼을 클릭할 때 메시지를 보여주도록 만들어 보겠습니다.

  1. Button 컴포넌트 내부에 handleClick 함수를 선언합니다.
  2. 해당 함수 내부 로직을 구현합니다. 이번에는 메시지를 표시하기 위해 alert를 사용합니다.
  3. <button> JSX에 onClick={handleClick}을 추가합니다.
export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

handleClick 함수를 정의하고 이를 <button>prop 형태로 전달하였습니다. 여기서 handleClick이벤트 핸들러입니다. 이벤트 핸들러 함수는 다음과 같은 특징을 가집니다.

  • 주로 컴포넌트 내부에서 정의됩니다.
  • handle로 시작하고 그 뒤에 이벤트명을 붙인 함수명을 가집니다.

관습적으로 handle로 시작하여 이벤트명을 이어 붙인 이벤트 핸들러 명명법이 일반적입니다. onClick={handleClick}, onMouseEnter={handleMouseEnter}와 같은 경우를 자주 볼 수 있습니다.

다른 방법으로 이벤트 핸들러를 JSX 내에서 인라인으로 정의할 수도 있습니다.

<button onClick={function handleClick() {  alert('You clicked me!');}}>

또는 화살표 함수를 사용하여 보다 간결하게 정의할 수도 있습니다.

<button onClick={() => {  alert('You clicked me!');}}>

이러한 스타일은 모두 동일한 결과를 보여줍니다. 특히 인라인 이벤트 핸들러는 짧은 함수들을 정의할 때 편리합니다.

  • 주의하세요! 이벤트 핸들러로 전달한 함수들은 호출이 아닌 전달되어야 합니다. 아래는 예시입니다.
    함수를 전달하기 (올바른 예시)함수를 호출하기 (잘못된 예시)
    <button onClick={handleClick}><button onClick={handleClick()}>
    이 차이는 미묘합니다. 첫 번째 예시에서 handleClick 함수는 onClick 이벤트 핸들러에 전달되었습니다. 이후 React는 이 내용을 기억하고 오직 사용자가 버튼을 클릭했을 때만 함수를 호출하도록 합니다. 두 번째 예시에서는 handleClick() 끝의 ()렌더링 과정 중 클릭이 없었음에도 불구하고 즉시 함수를 실행하도록 만듭니다. 이는 JSX {} 내의 자바스크립트가 즉시 실행되기 때문입니다. 인라인으로 코드를 작성할 때에도 동일한 함정이 다른 형태로 나타납니다.
    함수를 전달하기 (올바른 예시)함수를 호출하기 (잘못된 예시)
    <button onClick={() => alert('...')}><button onClick={alert('...')}>
    다음과 같이 인라인 함수를 전달하면 버튼을 클릭할 때마다 실행되는 것이 아니라 컴포넌트가 렌더링될 때마다 실행될 것입니다.
    // 이 alert는 클릭 시 실행되지 않고 컴포넌트가 렌더링 된 시점에 실행됩니다!
    <button onClick={alert('You clicked me!')}>
    만약 이벤트 핸들러를 인라인으로 정의하고자 한다면, 아래와 같이 익명 함수로 감싸면 됩니다.
    <button onClick={() => alert('You clicked me!')}>
    이러한 방법으로 매 렌더링마다 내부 코드를 실행하지 않고 함수를 생성하여 추후 이벤트에 의해 호출되게 합니다. 두 가지 경우 모두, 전달하는 것은 함수입니다.

이벤트 핸들러 내에서 Prop 읽기

이벤트 핸들러는 컴포넌트 내부에서 선언되기에, 이들은 해당 컴포넌트의 prop에 접근할 수 있습니다. 아래에서 클릭 시 message prop의 내용을 포함한 alert를 표시하는 버튼을 볼 수 있습니다.

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

위와 같이 두 개의 버튼이 서로 다른 메시지를 표시할 수 있습니다. 전달되는 메시지를 변경해보세요.

이벤트 핸들러를 Prop으로 전달하기

종종 부모 컴포넌트로 자식의 이벤트 핸들러를 지정하기를 원할 수 있습니다. 버튼의 경우를 고려해 봅시다. Button 컴포넌트를 사용하는 위치에 따라 다른 기능을 수행하도록 만들고자 할 때가 있을 것입니다. 한 버튼은 영화를 재생하고 다른 버튼은 이미지를 업로드하도록 말이죠.

이러한 기능을 위해서 컴포넌트가 그 부모 컴포넌트로부터 받은 prop을 이벤트 핸들러로 다음과 같이 전달합니다.

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}

위 코드에서는 Toolbar 컴포넌트가 PlayButtonUploadButton을 렌더링합니다.

  • PlayButtonhandlePlayClickButtononClick prop으로 전달합니다.
  • UploadButton() => alert('Uploading!')ButtononClick prop으로 전달합니다.

최종적으로, Button 컴포넌트는 onClick prop을 받습니다. 이후 받은 prop을 브라우저 빌트인 <button>onClick={onClick}으로 직접 전달합니다. 이를 통해 React가 전달받은 함수를 클릭 시점에 호출함을 알 수 있습니다.

만약 디자인 시스템을 적용한다면 버튼과 같은 컴포넌트는 동작을 지정하지 않고 스타일만 지정하는 것이 일반적입니다. 그 대신, PlayButtonUploadButton 같은 컴포넌트가 이벤트 핸들러를 전달하도록 합니다.

이벤트 핸들러 Prop 명명하기

onClick 과 같은 브라우저 이벤트 이름에 맞게 사전세 이벤트 핸들러 prop이 등록되어 있는 걸 확인할 수 있고요.

우리가 작성한 사용자 정의 컴포넌트의 이벤트 핸들러 prop은 원하는 대로 이름을 지을 수 있습니다.

<button><div> 같은 빌트인 컴포넌트는 onClick과 같은 브라우저 이벤트 이름만을 지원합니다. 그러나 사용자 정의 컴포넌트에서는 이벤트 핸들러 prop의 이름을 원하는 대로 명명할 수 있습니다.

관습적으로 이벤트 핸들러 prop을 지을 때:

  • 클릭 이벤트의 경우 onClick, onMouseEnter처럼 브라우저 이벤트를 그대로 따릅니다.
  • 사용자 정의 이벤트 핸들러는 handleClick과 같은 형식으로 정의합니다.

profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글