IT

[영어로 배우는 React] 4. 컴포넌트와 Props

생각파워 2022. 4. 7. 22:10

공식 페이지 : https://reactjs.org/docs/components-and-props.html

 

컴포넌트는 UI를 독립적이고, 재사용 가능한 부분으로 분리할 수 있게 해 주고, 각 부분을 독립적으로 생각할 수 있게 해 줍니다. 이 페이지에서는 컴포넌트 개념에 대해 설명합니다. 개념상 컴포넌트는 자바스크립트 함수입니다. 그 함수는 임의의 입력값을 받아들이고('props'라고 부르는), 스크린에 어떻게 나타날지를 설명하는 리액트 앨리먼트를 리턴합니다. 

 

1. 함수와 클래스 컴포넌트

컴포넌트를 선언하는 가장 쉬운 방법은 자바스크립트 함수를 쓰는 것입니다. 

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

위 함수는 단일 props를 함수 인자로 받아들이고, 리액트 앨리먼트를 리턴합니다. 보통 이런 컴포넌트를 "함수 컴포넌트"라고 부릅니다. 문자 그대로 컴포넌트 자체가 자바스크립트 함수이기 때문입니다. 

컴포넌트를 ES6 기준에 따라 선언할 수도 있습니다. ES6방식은 class를 이용하여 선언하기 때문에 "클래스 컴포넌트"라 부릅니다. 

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

위의 두 컴포넌트 선언 방식은 동일한 결과를 보여줍니다. 

 

2. 컴포넌트 랜더링하기

앞서 우리는 DOM 태그만을 표현하는 리액트 앨리먼트를 봤습니다. 

const element = <div />;

이렇게 생긴 거죠. 그러나 앨리먼트는 사용자가 선언한 컴포넌트를  표현할 수도 있습니다. 

const element = <Welcome name="Sara" />;

위의 Welcome 컴포넌트는 사용자가 만든 컴포넌트입니다. 리액트가 사용자 정의 컴포넌트를 표현하는 앨리먼트는 보게 되면, 리액트는 JSX 속성과 자식을 컴포넌트에게 전달합니다. 전달받을 때 싱글 오브젝트로 전달을 받는데, "props"라는 이름으로 부릅니다. 예를 살펴보겠습니다. 

function Welcome(props) {  
	return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

위 예제는 root.render(element)에서 element를 받았는데, element의 내용을 보니 <Welcome name="Sara" />과 같이 사용자 정의 컴포넌트로 돼 있습니다. Welcome 컴포넌트를 보니 props라는 파라미터가 쓰였네요. 앞서 props는 JSX의 속성과 자식들을 전달한다고 했었습니다. 현재 Welcome 컴포넌트에는 name="Sara"라는 속성이 있고, 이것이 Welcome으로 전달되겠죠. Welcome 내부에서 <h1>Hello, {props.name}</h1>으로 속성을 출력하고 있는 게 보이실 겁니다. 

 

다시 한번 복습

1. <Welcome name="Sara"> 앨리먼트를 인자로 root.render()를 호출

2. 리액트가 Welcome 컴포넌트를 {name : 'Sara'}값이 든 props를 이용해 호출

3. Welcome 컴포넌트가 <h1>Hello, Sara</h1>을 리턴

4. React DOM이 <h1>Hello, Sara</h1>로 DOM을 업데이트. 효율적으로 업데이트한답니다. 

 

참고. 컴포넌트 이름은 항상 대문자로 시작!!
리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 취급합니다. 예를 들어 <div /> 태그는 html div 태그로 인식하고, <Welcome /> 태그는 컴포넌트로 인식합니다. 그래서 Welcome컴포넌트가 현재 scope안에 존재하는지 체크합니다. 이런 관행의 비하인드를 알고 싶으면 JSX In Depth 여기를 한번 보세요.

 

3. 컴포넌트 구성하기

컴포넌트는 다른 컴포넌트를 참조할 수 있습니다. 이를 통해 모든 세부 수준에 대해 동일한 구성요소 추상화를 사용할 수 있습니다. 버튼, 폼, 다이얼로그, 스크린과 같은 리액트 앱의 요소들은 모두 컴포넌트로서 표현될 수 있습니다. 

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

위 예제는 Welcome 컴포넌트를 여러 번 사용해서 App 컴포넌트를 만든 예입니다.

보통, 새로운 리액트 앱은 최상위에 App 컴포넌트를 가지고 있습니다. 그러나 리액트를 기존 앱에 통합시키려고 한다면, Button과 같은 작은 컴포넌트를 가지고 bottom-up 방식으로 시작하고, 점차 상위 수준으로 넓혀나갈 수 있습니다. 

 

 

4. 컴포넌트 추출하기

컴포넌트를 작은 컴포넌트들로 나누는 것을 두려워하지 마세요. 

이 예제를 한번 보시죠.

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

일부러 그런지 모르겠지만 내용이 복잡하게 돼 있습니다. 

이 예제에서는 author, text, date를 props로 받아들여서, 표현해주고 있습니다. 그런데 표현이 너무 복잡하니까, 작은 컴포넌트를 추출해서 간단하게 표현해 보도록 하겠습니다. 먼저 Avatar를 추출합니다. 

function Avatar(props) {
  return (
    <img className="Avatar"
    src={props.user.avatarUrl}
    alt={props.user.name}    
    />
  );
}

Avatar 컴포넌트는 자기가 Comment내에 존재한다는 걸 알 필요가 없습니다. 그래서 prop의 이름도 author에서 user와 같이 좀 더 일반적인 명을 사용했습니다. 우리는 컴포넌트가 사용되는 곳보다 컴포넌트의 관점에 따라 props를 네이밍 할 필요가 있습니다. 아래는 약간 정리된 Comment입니다. 

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

다음으로 Avatar 컴포넌트를 랜더링 하는 UserInfo 컴포넌트를 추출해 보겠습니다. 

function UserInfo(props) {
  return (
    <div className="UserInfo">
        <Avatar user={props.user} />
        <div className="UserInfo-name">
            {props.user.name}
        </div>    
    </div>  );
}

이건 Comment를 아래와 같이 좀 더 심플하게 만듭니다. 

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

<UserInfo /> 컴포넌트 하나로 많은 내용이 캡슐화되었습니다. 깔끔해지기도 했고요.

 

컴포넌트 추출이 처음에는 힘든 작업처럼 보일 수 있습니다. 하지만 재사용 가능한 컴포넌트의 팔레트를 사용하면 더 큰 앱에서 효과를 볼 수 있습니다. Button, Panel, Avatar와 같이 여러 번 사용되거나, App, FeedStory, Comment와 컴포넌트가 복잡할 때, 컴포넌트 추출이 좋은 대안이 될 수 있습니다.   

 

 

5. Props는 읽기 전용

컴포넌트가 어떤 방식으로 선언됐든, 컴포넌트의 props는 변경할 수 없습니다. 잠시 예를 보겠습니다. 

function sum(a, b) {
  return a + b;
}

위와 같은 함수를 "순수한 함수"라고 부릅니다. 같은 입력에 대해 같은 결과가 리턴되기 때문입니다. 반대로, 아래 예는 "순수하지 않은 함수"입니다. 매개변수의 내용을 바꾸려고 하기 때문이죠.

function withdraw(account, amount) {
  account.total -= amount;
}

리액트는 아주 유연하지만, 꼭 지켜야 하는 한 가지 룰이 있습니다. 

All React components must act like pure functions with respect to their props.

 

모든 React 컴포넌트는 props에 대하여 순수 함수처럼 작동해야 한다는 뜻입니다.

물론 긴 시간에 걸쳐 애플리케이션 UI는 변경됩니다. 다음장에서 새로운 콘셉트인 state에 대해 알아볼 겁니다. state는 컴포넌트 출력을 변경하는 것을 허용합니다. 위 규칙을 위반하지 않고서 말이죠. 

반응형