ReactNextCentral

리액트 빠르게 시작해 보기

Published on
이 페이지에서는 매일 사용할 80%의 리액트 개념에 대해 소개해 드립니다.
Table of Contents

주요 내용 소개

이 페이지는 리액트 시작을 위한 깊이 있는 안내서를 찾을 수 있으며, 일상적으로 마주치게 되는 모든 기본 개념을 다룹니다. 몇 가지 주요한 포인트를 요약해 보겠습니다.

컴포넌트 생성 및 중첩 방법

리액트 앱은 크거나 작은 컴포넌트로 구성되며, 이 컴포넌트는 JSX 문법을 사용해 마크업을 반환합니다.

View

마크업 및 스타일 추가하기

리액트는 JSX 마크업 구문을 사용합니다. JSX는 모든 태그를 닫아야 하며, className으로 CSS 클래스를 지정합니다.

View

데이터 표시하기

리액트에서는 JSX 내에 중괄호를 사용해 JavaScript 표현식을 삽입하며, 이를 통해 동적 데이터를 표시합니다.

View

조건 렌더링하기

리액트에는 조건부 렌더링을 위한 별도의 구문이 없습니다. 대신, JavaScript의 if-else나 삼항 연산자로 컴포넌트나 요소의 렌더링을 결정합니다.

View

목록 렌더링하기

리액트는 데이터 배열을 매핑해 JSX 요소 목록을 생성하며, key 속성으로 각 요소를 고유하게 식별합니다.

View

이벤트 응답하기

리액트는 사용자 이벤트에 응답하기 위해 컴포넌트에서 이벤트 핸들러 함수를 정의합니다.

View

화면 업데이트하기

리액트의 useState 훅으로 컴포넌트의 로컬 상태를 관리하며, 상태 변경 시 컴포넌트가 다시 렌더링됩니다.

View

컴포넌트 간에 데이터 공유하기

공통 데이터를 여러 컴포넌트에서 공유하려면, 상태를 최근 공통 조상으로 올리고, 이를 자식에 props로 전달합니다.

View

이것은 시작에 불과하며, 리액트에는 Context, Ref, 라이프사이클 메서드, 고차 컴포넌트, 렌더 Props 등의 고급 기능과 개념이 더 있으며, 이러한 내용을 공식 문서에서 자세히 알아볼 수 있습니다.

컴포넌트 생성 및 중첩

리액트 앱은 컴포넌트로 구성됩니다. 컴포넌트는 컴포넌트라는 개체로 구성됩니다. 컴포넌트는 JavaScript 클래스나 함수로 이루어져 있으며, 자체적인 로직과 외관을 가진 UI(사용자 인터페이스)의 한 부분입니다. 컴포넌트는 버튼과 같이 작을 수도 있고 전체 페이지와 같이 클 수도 있습니다.

리액트 컴포넌트는 마크업을 반환하는 JavaScript 함수입니다.

function MyButton() {
  return (
    <button>I'm a button</button>
  );
}

이제 MyButton을 다른 컴포넌트에 중첩할 수 있습니다.

export default function MyApp() {
  return (
    <div>
      <h1>내 앱에 오신 것을 환영합니다</h1>
      <MyButton />
    </div>
  );
}

<MyButton />대문자로 시작함에 유의하세요. 이렇게 하면 리액트 컴포넌트라는 것을 알 수 있습니다. 리액트 컴포넌트 이름은 항상 대문자로 시작해야 하며, HTML 태그는 소문자여야 합니다.

결과를 확인해보세요. Edit upbeat-violet-fxbh5x

export default 키워드는 파일에서 주요 컴포넌트를 지정합니다. JavaScript 구문에 익숙하지 않은 경우, MDN과 javascript.info에서 훌륭한 참조 자료를 제공합니다.

JSX를 사용한 마크업 작성

위에서 보았던 마크업 구문을 JSX라고 합니다. 이는 선택적이지만, 대부분의 리액트 프로젝트는 편의성을 위해 JSX를 사용합니다. 로컬 개발을 위해 권장하는 모든 도구는 JSX를 기본적으로 지원합니다.

JSX는 HTML보다 엄격합니다. <br />과 같이 태그를 닫아야 합니다. 컴포넌트는 또한 여러 개의 JSX 태그를 반환할 수 없습니다. 이를 공유 부모(<div>...</div> 또는 빈 < >...< / > 래퍼)로 묶어야 합니다.

function AboutPage() {
  return (
    <>
      <h1>About</h1>
      <p>Hello there.<br />How do you do?</p>
    </>
  );
}

JSX로 변환할 HTML이 많은 경우, 온라인 변환 도구(HTML to JSX 변환 도구)를 사용할 수 있습니다.

스타일 추가

리액트에서는 className으로 CSS 클래스를 지정합니다. 이는 HTML의 class 속성과 동일한 방식으로 작동합니다.

<img className="avatar" />

그런 다음 별도의 CSS 파일에서 해당 클래스에 대한 CSS 규칙을 작성합니다.

/* CSS 파일에 작성 */
.avatar {
  border-radius: 50%;
}

리액트는 CSS 파일을 추가하는 방법을 명시하지 않습니다. 가장 간단한 경우, HTML에 <link> 태그를 추가합니다. 빌드 도구나 프레임워크를 사용하는 경우, 해당 문서를 참조하여 프로젝트에 CSS 파일을 추가하는 방법을 알아보세요.

리액트 프로젝트에 CSS 파일을 추가하는 방법

리액트 프로젝트에 CSS 파일을 추가하는 방법은 몇 가지 단계로 수행할 수 있습니다.

  1. 먼저, 스타일을 적용하려는 리액트 컴포넌트와 동일한 디렉토리에 CSS 파일을 생성합니다. 컴포넌트 파일의 이름이 MyComponent.js라면, CSS 파일의 이름을 MyComponent.css로 지정할 수 있습니다.

  2. 다음으로, 리액트 컴포넌트 파일(MyComponent.js)에서 파일 상단에 CSS 파일을 import합니다. 이는 import 키워드와 CSS 파일의 경로를 사용하여 수행됩니다.

다음은 예시입니다.

다음과 같은 프로젝트 구조를 가정해 봅시다.

/my-app
  /src
    /components
      MyComponent.js
      MyComponent.css

그리고 MyComponent.css 파일에 몇 가지 스타일이 포함되어 있다고 가정해 봅시다.

.container {
  margin: auto;
  width: 50%;
  border: 3px solid green;
  padding: 10px;
}

그럼, MyComponent.js 파일에서 이 CSS 파일을 다음과 같이 import합니다.

import React from 'react';
import './MyComponent.css';  // CSS를 import합니다.

function MyComponent() {
  return (
    <div className="container">
      <h1>Hello, World!</h1>
    </div>
  );
}

export default MyComponent;

이렇게 하면 MyComponent 컴포넌트의 div에 있는 .container 클래스 스타일이 MyComponent.css에서 적용됩니다.

참고로, 이 방법은 Create-React-App, Next.js 등과 같은 프레임워크에서 기본적으로 작동합니다. 이러한 프레임워크는 CSS를 처리하는 webpack 로더가 이미 구성되어 있습니다. 하지만 프로젝트를 처음부터 설정하거나 다른 환경에서 작업하는 경우에는 css-loaderstyle-loader와 같은 로더를 구성해야 할 수도 있습니다. 또는 styled-components와 같은 CSS-in-JS 라이브러리를 사용할 수도 있습니다.

데이터 표시

JSX를 사용하면 JavaScript 내에 마크업을 넣을 수 있습니다. 중괄호를 사용하여 코드의 변수를 포함하여 사용자에게 표시할 수 있습니다. 예를 들어, 다음은 user.name을 표시합니다.

return (
  <h1>
    {user.name}
  </h1>
);

JSX 속성에서도 중괄호를 사용하여 JavaScript로 "이스케이프"할 수 있지만, 따옴표 대신 중괄호를 사용해야 합니다. 예를 들어, className="avatar""avatar" 문자열을 CSS 클래스로 전달하지만, src={user.imageUrl}은 JavaScript의 user.imageUrl 변수 값을 읽고 해당 값을 src 속성으로 전달합니다.

return (
  <img
    className="avatar"
    src={user.imageUrl}
  />
);

JSX 중괄호 안에는 문자열 연결과 같은 복잡한 표현식도 넣을 수 있습니다.

Edit dazzling-kalam-6qm5em

const user = {
  name: 'Hedy Lamarr',
  imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
  imageSize: 90,
};

export default function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}

위의 예제에서 style={{}}는 특별한 구문이 아니라, style={ } JSX 중괄호 내부의 일반 {} 객체입니다. 스타일이 JavaScript 변수에 따라 달라지는 경우 style 속성을 사용할 수 있습니다.

조건부 렌더링

리액트에서 조건을 작성하는 특별한 구문은 없습니다. 대신 일반적인 JavaScript 코드를 작성할 때 사용하는 기술을 사용합니다. 예를 들어, JSX를 조건부로 포함시키려면 if을 사용할 수 있습니다.

let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return (
  <div>
    {content}
  </div>
);

더 간결한 코드를 선호하는 경우, 조건부 ? 연산자를 사용할 수 있습니다. if와 달리 JSX 내부에서 작동합니다.

<div>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</div>

else 분기가 필요하지 않은 경우, 더 짧은 논리 && 구문을 사용할 수도 있습니다.

<div>
  {isLoggedIn && <AdminPanel />}
</div>

이러한 접근 방식은 속성을 조건부로 지정하는 데에도 동일하게 적용됩니다. JavaScript 구문에 익숙하지 않은 경우, 항상 if...else를 사용하는 것으로 시작할 수 있습니다.

목록 렌더링

구성 요소의 목록을 렌더링하기 위해 for 루프와 배열의 map() 함수와 같은 JavaScript 기능을 활용할 것입니다.

예를 들어, 제품의 배열이 있다고 가정해봅시다.

const products = [
  { title: '배추', id: 1 },
  { title: '마늘', id: 2 },
  { title: '사과', id: 3 },
];

컴포넌트 내부에서 map() 함수를 사용하여 제품 배열을 <li> 항목의 배열로 변환합니다.

const listItems = products.map(product =>
  <li key={product.id}>
    {product.title}
  </li>
);

return (
  <ul>{listItems}</ul>
);

<li>에는 key 속성이 있음에 유의하세요. 목록의 각 항목마다 형제들 중에서 해당 항목을 고유하게 식별하는 문자열 또는 숫자를 전달해야 합니다. 일반적으로 키는 데이터로부터 가져온 데이터베이스 ID와 같은 값이어야 합니다. 리액트는 키를 사용하여 항목을 나중에 삽입, 삭제 또는 재정렬할 때 어떤 일이 발생했는지 알 수 있습니다.

이벤트에 응답하기

컴포넌트 내부에서 이벤트 핸들러 함수를 선언함으로써 이벤트에 응답할 수 있습니다.

function MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

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

onClick={handleClick}에서 마지막에 괄호가 없음에 주목하세요! 이벤트 핸들러 함수를 호출하지 마세요. 함수를 전달하기만 하면 됩니다. 리액트는 사용자가 버튼을 클릭할 때 이벤트 핸들러를 호출합니다.

화면 업데이트

컴포넌트에서 정보를 "기억"하고 표시하려는 경우가 자주 있습니다. 예를 들어, 버튼이 클릭된 횟수를 세고 싶을 수 있습니다. 이를 위해 컴포넌트에 상태(state)를 추가하세요.

먼저, 리액트에서 useState를 import하세요.

import { useState } from 'react';

이제 컴포넌트 내부에 상태 변수를 선언할 수 있습니다.

function MyButton() {
  const [count, setCount] = useState(0);
  // ...

useState에서 두 가지를 얻게 됩니다. 현재 상태(count)와 상태를 업데이트할 수 있는 함수(setCount). 이들에게 아무 이름이나 지정할 수 있지만, [something, setSomething] 형식으로 작성하는 것이 관례입니다.

버튼이 처음 표시될 때 count는 0이 됩니다. 왜냐하면 useState()에 0을 전달했기 때문입니다. 상태를 변경하려면 setCount()를 호출하고 새 값을 전달하면 됩니다. 이 버튼을 클릭하면 카운터가 증가합니다.

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

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

리액트는 컴포넌트 함수를 다시 호출합니다. 이번에는 count가 1이 될 것입니다. 그 다음에는 2가 될 것입니다. 그리고 이런 식으로 계속됩니다.

동일한 컴포넌트를 여러 번 렌더링하면 각각 자체 상태를 가지게 됩니다. 각 버튼을 개별적으로 클릭해보세요.

Edit confident-frog-v56qeb

각 버튼이 자체적인 count 상태를 "기억"하고 다른 버튼에 영향을 주지 않는 것을 알아차리세요.

Hooks 사용

use로 시작하는 함수를 Hooks라고 합니다. useState는 리액트에서 제공하는 내장 Hook입니다. API 참조에서 다른 내장 Hooks도 찾을 수 있습니다. 또한 기존 Hooks를 조합하여 직접 Hooks를 작성할 수도 있습니다.

Hooks는 다른 함수보다 더 제한적입니다. 컴포넌트의 상단(또는 다른 Hooks)에서만 Hooks를 호출할 수 있습니다. 조건문이나 반복문에서 useState를 사용하려면 새로운 컴포넌트를 추출하고 그곳에 두세요.

컴포넌트 간 데이터 공유

이전 예제에서 각 MyButton은 독립적인 count를 가지고 있으며 각 버튼이 클릭될 때 클릭한 버튼의 count만 변경됩니다.

React Sharing Data

처음에는 각 MyButton의 count 상태가 0입니다.

React Sharing Data

첫 번째 MyButton은 count를 1로 업데이트합니다.

그러나 종종 컴포넌트들이 데이터를 공유하고 항상 함께 업데이트해야 할 필요가 있습니다.

두 개의 MyButton 컴포넌트가 동일한 count를 표시하고 함께 업데이트하려면, 개별 버튼에서 상태를 "상위로" 이동하여 모두를 포함하는 가장 가까운 컴포넌트인 MyApp으로 이동해야 합니다.

이 예시에서는 MyApp입니다.

React Sharing Data

처음에는 MyAppcount 상태는 0이며, 이는 두 개의 자식 컴포넌트에 전달됩니다.

React Sharing Data

클릭 이벤트 발생 시, MyApp은 자체적인 count 상태를 1로 업데이트하고 이를 두 개의 자식 컴포넌트에 전달합니다.

이제 버튼 중 하나를 클릭하면 MyAppcount가 변경되고, 이는 MyButton의 두 개의 count도 변경시킵니다. 아래는 이를 코드로 표현한 방법입니다.

먼저, MyButton에서 상태를 MyApp으로 이동합니다.

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>개별적으로 업데이트되는 카운터</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
  // ... 여기서 코드를 이동합니다 ...
}

그런 다음, MyApp에서 상태를 각 MyButton으로 전달하고 공유된 클릭 핸들러와 함께 전달합니다. JSX 중괄호를 사용하여 MyButton으로 정보를 전달할 수 있습니다. 이전에 <img>와 같은 내장 태그에 사용한 것과 같은 방식입니다.

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>함께 업데이트되는 카운터</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

이렇게 전달하는 정보를 props라고 합니다. 이제 MyApp 컴포넌트에는 count 상태와 handleClick 이벤트 핸들러가 포함되어 있으며, 두 가지 모두를 각 버튼으로 props로 전달합니다.

마지막으로, MyButton을 수정하여 부모 컴포넌트에서 전달된 props를 읽도록 변경합니다.

function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      {count}번 클릭됨
    </button>
  );
}

버튼을 클릭하면 onClick 핸들러가 실행됩니다. 각 버튼의 onClick propMyApp 내부에서 handleClick 함수로 설정되었으므로 해당 함수 내의 코드가 실행됩니다. 해당 코드는 setCount(count + 1)을 호출하여 count 상태 변수를 증가시킵니다. 새로운 count 값은 각 버튼에 prop로 전달되므로 새로운 값이 모두 표시됩니다.

이를 "상태를 위로 끌어올리기(lifting state up)"라고 합니다. 상태를 위로 이동함으로써 여러 컴포넌트 간에 상태를 공유할 수 있게 되었습니다.

Edit determined-wave-us42os

Footnotes

  1. "바인딩(binding)"이라는 용어는, 프로그래밍 컨텍스트에서 특정 객체나 값과 함수나 변수를 연결하는 과정을 의미합니다. JavaScript의 객체 지향 프로그래밍에서, 특히 클래스와 관련된 메서드에서 this 키워드의 값은 해당 메서드가 호출되는 방식에 따라 다를 수 있습니다. JavaScript 클래스의 메서드에 대해 이야기할 때, "바인딩되지 않았다"는 것은, 해당 메서드가 호출될 때 this의 값이 그 클래스의 인스턴스를 자동으로 가리키지 않는다는 의미입니다.