ReactNextCentral

컴포넌트와 데이터 관리

Published on
컴포넌트로 UI 구축하기, Props로 데이터 표시하기, 상태(State)로 인터랙티비티 추가하기
Table of Contents

5장: 컴포넌트로 UI 구축하기

리액트 핵심 개념

리액트 애플리케이션을 구축하기 시작하려면 세 가지 핵심 개념을 알아야 합니다.

  • 컴포넌트
  • Props
  • 상태

다음 장에서는 이 개념들을 살펴보고 계속해서 학습할 수 있는 자료를 제공할 것입니다. 이 개념들에 익숙해지면 서버 및 클라이언트 컴포넌트와 같은 새로운 리액트 기능을 사용하여 Next.js를 설치하는 방법을 보여드릴 것입니다.

컴포넌트

사용자 인터페이스는 컴포넌트라고 불리는 더 작은 빌딩 블록으로 나눌 수 있습니다.

컴포넌트를 사용하면 독립적이고 재사용 가능한 코드 조각을 구축할 수 있습니다. 컴포넌트를 레고 블록으로 생각한다면 이 개별 블록을 함께 조합하여 더 큰 구조를 형성할 수 있습니다. UI의 한 부분을 업데이트해야 할 경우 특정 컴포넌트나 블록을 업데이트할 수 있습니다. 컴포넌트

예를 들어 이미지, 텍스트, 버튼 3개의 작은 컴포넌트로 이루어진 미디어 컴포넌트가 있습니다. 이 모듈성은 코드가 성장함에 따라 유지 관리가 더 쉬워지게 합니다. 나머지 애플리케이션에 영향을 주지 않고 컴포넌트를 추가, 업데이트 및 삭제할 수 있기 때문입니다.

리액트 컴포넌트의 좋은 점은 그것이 단순히 자바스크립트라는 것입니다. 자바스크립트 관점에서 리액트 컴포넌트를 어떻게 작성하는지 살펴보겠습니다.

컴포넌트 생성하기

리액트에서 컴포넌트는 함수입니다. 스크립트 태그 안에 header라는 새 함수를 생성하세요.

index.html
<script type="text/jsx">
  const app = document.getElementById("app")
 
  function header() {
  }
 
  const root = ReactDOM.createRoot(app);
  root.render(<h1>Develop. Preview. Ship.</h1>);
</script>

컴포넌트는 UI 요소를 반환하는 함수입니다. 함수의 반환문 안에서 JSX를 작성할 수 있습니다.

index.html
<script type="text/jsx">
  const app = document.getElementById("app")
 
  function header() {
     return (<h1>Develop. Preview. Ship.</h1>)
   }
 
  const root = ReactDOM.createRoot(app);
  root.render(<h1>Develop. Preview. Ship.</h1>);
</script>

이 컴포넌트를 DOM에 렌더링하려면 root.render() 메서드의 첫 번째 인자로 전달하세요.

index.html
<script type="text/jsx">
  const app = document.getElementById("app")
 
  function header() {
     return (<h1>Develop. Preview. Ship.</h1>)
   }
 
  const root = ReactDOM.createRoot(app);
  root.render(header);
</script>

하지만 잠깐, 이 코드를 브라우저에서 실행하려고 하면 오류가 발생합니다. 이를 작동시키려면 두 가지를 해야 합니다.

첫째, 리액트 컴포넌트는 일반 HTML과 자바스크립트와 구분하기 위해 대문자로 시작해야 합니다.

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
const root = ReactDOM.createRoot(app);
// 리액트 컴포넌트를 대문자로 시작
root.render(Header);

둘째, 리액트 컴포넌트를 일반 HTML 태그처럼 꺾쇠괄호 <>로 사용하세요:

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
const root = ReactDOM.createRoot(app);
root.render(<Header />);

브라우저에서 코드를 다시 실행해보면 변경 사항을 볼 수 있습니다.

컴포넌트 중첩하기

애플리케이션은 보통 단일 컴포넌트보다 더 많은 내용을 포함합니다. 리액트 컴포넌트를 일반 HTML 요소처럼 서로 안에 중첩할 수 있습니다.

예제에서 HomePage라는 새 컴포넌트를 만듭니다.

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
function HomePage() {
  return <div></div>;
}
 
const root = ReactDOM.createRoot(app);
root.render(<Header />);

그런 다음 <Header> 컴포넌트를 새로운 <HomePage> 컴포넌트 안에 중첩하세요.

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
function HomePage() {
  return (
    <div>
      {/* Header 컴포넌트를 중첩 */}
      <Header />
    </div>
  );
}
 
const root = ReactDOM.createRoot(app);
root.render(<Header />);

컴포넌트 트리

이런 방식으로 리액트 컴포넌트를 계속 중첩하여 컴포넌트 트리를 형성할 수 있습니다. 컴포넌트 트리

컴포넌트 트리는 컴포넌트가 서로 안에 중첩될 수 있음을 보여줍니다. 예를 들어 최상위 HomePage 컴포넌트는 Header, Article, Footer 컴포넌트를 포함할 수 있습니다. 그리고 그 컴포넌트들은 각각 자신의 자식 컴포넌트를 가질 수 있고, 이런 식으로 계속됩니다. 예를 들어 Header 컴포넌트는 Logo, Title 및 Navigation 컴포넌트를 포함할 수 있습니다.

이 모듈식 형태는 앱 내의 다양한 위치에서 컴포넌트를 재사용할 수 있게 해줍니다.

프로젝트에서 <HomePage>가 이제 최상위 컴포넌트이므로 root.render() 메서드에 전달할 수 있습니다.

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
function HomePage() {
  return (
    <div>
      <Header />
    </div>
  );
}
 
const root = ReactDOM.createRoot(app);
root.render(<HomePage />);

6장: Props로 데이터 표시하기

지금까지 <Header /> 컴포넌트를 재사용한다면 동일한 내용이 두 번 표시될 것입니다.

index.html
function Header() {
  return <h1>Develop. Preview. Ship.</h1>;
}
 
function HomePage() {
  return (
    <div>
      <Header />
      <Header />
    </div>
  );
}

하지만 다른 텍스트를 전달하고 싶거나 외부 소스에서 데이터를 가져오는 등 미리 정보를 알 수 없는 경우는 어떨까요?

일반 HTML 요소는 요소의 동작을 변경하는 정보 조각을 전달할 수 있는 속성을 가지고 있습니다. 예를 들어 <img> 요소의 src 속성을 변경하면 표시되는 이미지가 바뀝니다. <a> 태그의 href 속성을 변경하면 링크의 목적지가 바뀝니다.

같은 방식으로 리액트 컴포넌트에 정보 조각을 프로퍼티로 전달할 수 있습니다. 이를 Props라고 합니다. 예를 들어, 버튼의 다양한 변형을 생각해 볼 수 있습니다.

버튼 컴포넌트의 3가지 변형을 보여주는 다이어그램: 기본, 보조, 비활성화

자바스크립트 함수와 유사하게 컴포넌트가 렌더링될 때 컴포넌트의 동작이나 시각적으로 표시되는 내용을 변경하는 사용자 정의 인자(또는 Props)를 받도록 컴포넌트를 설계할 수 있습니다. 그런 다음 이 Props를 부모 컴포넌트에서 자식 컴포넌트로 전달할 수 있습니다.

참고: 리액트에서 데이터는 컴포넌트 트리 아래로 흐릅니다. 이를 단방향 데이터 흐름이라고 합니다. 다음 장에서 다룰 상태는 Props로 부모 컴포넌트에서 자식 컴포넌트로 전달될 수 있습니다.

Props 사용하기

HomePage 컴포넌트에서는 HTML 속성을 전달하듯 Header 컴포넌트에 사용자 정의 title Props를 전달할 수 있습니다.

index.html
function HomePage() {
  return (
    <div>
      <Header title="React" />
    </div>
  );
}

그리고 자식 컴포넌트인 Header는 첫 번째 함수 매개변수로 이 Props를 받을 수 있습니다.

index.html
function Header(props) {
  return <h1>Develop. Preview. Ship.</h1>;
}

console.log()로 props를 출력해보면 title 속성을 가진 객체임을 알 수 있습니다.

index.html
function Header(props) {
  console.log(props); // { title: "React" }
  return <h1>Develop. Preview. Ship.</h1>;
}

Props가 객체이므로 객체 구조 분해를 사용하여 함수 매개변수 내부에서 Props의 값에 명시적으로 이름을 지정할 수 있습니다.

index.html
function Header({ title }) {
  console.log(title); // "React"
  return <h1>Develop. Preview. Ship.</h1>;
}

그런 다음 <h1> 태그의 내용을 title 변수로 대체할 수 있습니다.

index.html
function Header({ title }) {
  console.log(title);
  return <h1>title</h1>;
}

파일을 브라우저에서 열어보면 "title"이라는 실제 단어가 표시되는 것을 볼 수 있습니다. 이는 리액트가 당신이 DOM에 일반 텍스트 문자열을 렌더링하려는 의도로 생각하기 때문입니다.

이것이 자바스크립트 변수임을 리액트에 알릴 방법이 필요합니다.

JSX에서 변수 사용하기

title Props를 사용하려면 중괄호 를 추가하세요. 이것은 JSX 마크업 내부에서 직접 일반 자바스크립트를 작성할 수 있게 해주는 특별한 JSX 문법입니다.

index.html
function Header({ title }) {
  console.log(title);
  return <h1>{title}</h1>;
}

중괄호를 "JSX 땅" 안에서 "자바스크립트 땅"으로 들어가는 방법으로 생각할 수 있습니다. 중괄호 안에는 어떤 자바스크립트 표현식(단일 값을 평가하는 것)도 추가할 수 있습니다. 예를 들어:

점 표기법을 사용한 객체 속성:

example.js
function Header(props) {
  return <h1>{props.title}</h1>;
}

템플릿 리터럴:

example.js
function Header({ title }) {
  return <h1>{`Cool ${title}`}</h1>;
}

함수의 반환 값:

example.js
function createTitle(title) {
  if (title) {
    return title;
  } else {
    return '기본 제목';
  }
}
 
function Header({ title }) {
  return <h1>{createTitle(title)}</h1>;
}

또는 삼항 연산자:

example.js
function Header({ title }) {
  return <h1>{title ? title : '기본 제목'}</h1>;
}

이제 title Props에 어떤 문자열을 전달할 수 있으며, 삼항 연산자를 사용한 경우에는 title Props를 전혀 전달하지 않아도 됩니다. 컴포넌트에서 기본 경우를 고려했기 때문입니다.

example.js
function Header({ title }) {
  return <h1>{title ? title : '기본 제목'}</h1>;
}
 
function HomePage() {
  return (
    <div>
      <Header />
    </div>
  );
}

이제 컴포넌트는 일반적인 title Props를 받아 애플리케이션의 다양한 부분에서 재사용할 수 있습니다. title 문자열을 변경하기만 하면 됩니다.

index.html
function HomePage() {
  return (
    <div>
      <Header title="리액트" />
      <Header title="새로운 제목" />
    </div>
  );
}

리스트 순회하기

데이터를 리스트로 표시해야 하는 경우가 자주 있습니다. 배열 메서드를 사용하여 데이터를 조작하고 스타일은 동일하지만 다른 정보를 담은 UI 요소를 생성할 수 있습니다.

HomePage 컴포넌트에 다음 이름 배열을 추가하세요.

index.html
function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li>{name}</li>
        ))}
      </ul>
    </div>
  );
}

그런 다음 array.map() 메서드를 사용하여 배열을 순회하고 화살표 함수를 사용하여 이름을 리스트 항목에 매핑할 수 있습니다.

index.html
function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li>{name}</li>
        ))}
      </ul>
    </div>
  );
}

"자바스크립트"와 "JSX" 땅 사이를 오가며 중괄호를 사용하는 방법에 주목하세요.

이 코드를 실행하면 리액트가 배열 내 항목에 고유 key 프롭이 없다는 경고를 줍니다. 이는 리액트가 배열의 요소를 고유하게 식별하여 DOM에서 어떤 요소를 업데이트해야 하는지 알기 위해 필요합니다.

현재 이름이 고유하기 때문에 일단 이름을 사용할 수 있지만, 항목 ID와 같이 고유한 것을 사용하는 것이 좋습니다.

index.html
function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
    </div>
  );
}

7장: 상태를 통한 상호작용 추가하기

리액트가 상태와 이벤트 핸들러를 통해 어떻게 상호작용을 추가하는지 알아봅시다.

예를 들어, HomePage 컴포넌트 내에 "좋아요" 버튼을 만들어 보겠습니다. 먼저 return() 문 내에 버튼 요소를 추가하세요.

index.html
function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <button>좋아요</button>
    </div>
  );
}

이벤트 리스닝하기

클릭할 때 버튼이 무언가를 하게 하려면 onClick 이벤트를 사용할 수 있습니다.

index.html
function HomePage() {
  // ...
  return (
    <div>
      {/* ... */}
      <button onClick={}>좋아요</button>
    </div>
  );
}

리액트에서 이벤트 이름은 카멜 케이스로 작성됩니다. onClick 이벤트는 사용자 상호작용에 반응하기 위해 사용할 수 있는 많은 가능한 이벤트 중 하나입니다. 예를 들어, 입력 필드에는 onChange를, 폼에는 onSubmit을 사용할 수 있습니다.

이벤트 처리하기

이벤트가 발생할 때마다 "처리"할 함수를 정의할 수 있습니다. handleClick()이라는 함수를 return 문 이전에 생성하세요.

index.html
function HomePage() {
  // ...
 
  function handleClick() {
    console.log("좋아요 횟수 증가")
  }
 
  return (
    <div>
      {/* ... */}
	  <button onClick={handleClick}>좋아요</button>
    </div>
     )
   }

그런 다음, onClick 이벤트가 발생할 때 handleClick 함수를 호출할 수 있습니다.

index.html
function HomePage() {
  // 	...
  function handleClick() {
    console.log('좋아요 횟수 증가');
  }
 
  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>좋아요</button>
    </div>
  );
}

이것을 브라우저에서 실행해보세요. 개발자 도구에서 로그 출력이 증가하는 것을 확인할 수 있습니다.### 이벤트 처리하기 이벤트가 발생할 때마다 "처리"할 함수를 정의할 수 있습니다. handleClick()이라는 함수를 return 문 이전에 생성하세요.

index.html
function HomePage() {
  // ...
 
  function handleClick() {
    console.log("좋아요 횟수 증가")
  }
 
  return (
    <div>
      {/* ... */}
	  <button onClick={handleClick}>좋아요</button>
    </div>
     )
   }

그런 다음, onClick 이벤트가 발생할 때 handleClick 함수를 호출할 수 있습니다.

index.html
function HomePage() {
  // 	...
  function handleClick() {
    console.log('좋아요 횟수 증가');
  }
 
  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>좋아요</button>
    </div>
  );
}

이것을 브라우저에서 실행해보세요. 개발자 도구에서 로그 출력이 증가하는 것을 확인할 수 있습니다.

상태와 훅

리액트는 훅이라고 불리는 함수 세트를 가지고 있습니다. 훅을 사용하면 컴포넌트에 상태와 같은 추가 로직을 추가할 수 있습니다. 상태는 시간이 지남에 따라, 주로 사용자 상호작용에 의해 변경되는 UI 내의 모든 정보로 생각할 수 있습니다. 상태와 훅

상태의 두 가지 다른 예로 첫째, 선택되거나 선택되지 않을 수 있는 토글 버튼이나 둘째, 여러 번 클릭될 수 있는 좋아요 버튼이 있습니다. 상태를 사용하여 사용자가 "좋아요" 버튼을 클릭한 횟수를 저장하고 증가시킬 수 있습니다. 사실, 상태를 관리하기 위해 사용되는 리액트 훅은 useState()입니다.

프로젝트에 useState()를 추가하세요. 이것은 배열을 반환하며, 배열 구조 분해를 사용하여 컴포넌트 내에서 이 배열 값을 접근하고 사용할 수 있습니다.

index.html
function HomePage() {
  // ...
  const [] = React.useState();
 
  // ...
}

배열의 첫 번째 항목은 상태 값으로, 원하는 대로 이름을 지을 수 있습니다. 설명적인 이름을 지정하는 것이 좋습니다.

index.html
function HomePage() {
  // ...
  const [likes] = React.useState();
 
  // ...
}

배열의 두 번째 항목은 값을 업데이트하는 함수입니다. 업데이트 함수에는 일반적으로 상태 변수의 이름 앞에 set을 붙여 이름을 지정합니다.

index.html
function HomePage() {
  // ...
  const [likes, setLikes] = React.useState();
 
  // ...
}

좋아요 상태의 초기 값을 0으로 설정할 기회도 가질 수 있습니다.

index.html
function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);
}

그런 다음 컴포넌트 내에서 상태 변수를 사용하여 초기 상태가 작동하는지 확인할 수 있습니다.

index.html
function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);
  // ...
 
  return (
    // ...
    <button onClick={handleClick}>좋아요({likes})</button>
  );
}

마지막으로, handleClick() 함수 내에 상태 업데이터 함수인 setLikes를 호출할 수 있습니다.

index.html
function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>좋아요 ({likes})</button>
    </div>
  );
}

버튼을 클릭하면 handleClick 함수가 호출되며, 이는 현재 좋아요 수 + 1의 단일 인자로 setLikes 상태 업데이터 함수를 호출합니다.

참고: 프롭스는 첫 번째 함수 매개변수로 컴포넌트에 전달되는 반면, 상태는 컴포넌트 내에서 초기화되고 저장됩니다. 상태 정보를 자식 컴포넌트로 프롭스로 전달할 수 있지만, 상태를 업데이트하는 로직은 상태가 처음 생성된 컴포넌트 내에 유지되어야 합니다.

상태 관리

이것은 상태에 대한 소개였으며, 리액트 애플리케이션에서 상태와 데이터 흐름을 관리하는 데 대해 더 많이 배울 수 있습니다. 더 배우고 싶다면 리액트 문서의 상호작용 추가 및 상태 관리 섹션을 통해 학습하는 것이 좋습니다.