Components와 Props

민돌·2023년 9월 18일
0

React

목록 보기
7/9
post-custom-banner

개념적으로 컴포넌트는 JavaScript 함수와 유사하다.
props라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환한다.

컴포넌트는 어떤 문맥이든 가장 작은 단위로서 언급되며, 웹 앱을 구성 하는 데 있어 가장 작은 단위가 된다.


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

컴포넌트를 정의하는 가장 간단한 방법은 JavaScript 함수를 작성하는 것이다.

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

props는 속성을 나타내는 데이터인데, 이 객체 인자를 받은 후 React 엘리먼트를 반환하므로 이러한 컴포넌트를 "함수 컴포넌트"라고 한다.

또한 ES6 class를 사용하여 컴포넌트를 정의할 수 있다.

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

이를 "클래스 컴포넌트"라고 한다.


컴포넌트 렌더링

DOM 태그로 React 엘리먼트를 나타냈는데

const element = <div />;

React 엘리먼트는 사용자 정의 컴포넌트로도 나타낼 수 있다.

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

React가 사용자 정의 컴포넌트로 작성한 엘리먼트를 발견하면 JSX 어트리뷰트와 자식을 해당 컴포넌트에 단일 객체로 전달하는데, 이 객체를 "props"라고 한다.

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

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

위 예시에서의 동작 과정을 살펴보자.
1. <Welcome name="Sara" /> 엘리먼트로 root.render()를 호출한다.
2. React는 {name: 'Sara'}를 props로 하여 Welcome 컴포넌트를 호출한다.
3. Welcome 컴포넌트는 결과적으로 <h1>Hello, Sara</h1> 엘리먼트를 반환한다.
4. React DOM은 <h1>Hello, Sara</h1> 엘리먼트와 일치하도록 DOM을 효율적으로 업데이트 한다.

🚨주의: 컴포넌트의 이름은 항상 대문자로 시작한다.
React는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리한다.
예를 들어 <div />는 HTML div 태그를 나타내지만,
<Welcome />은 컴포넌트를 나타내며 범위 안에 Welcome이 있어야 한다.


컴포넌트 합성

컴포넌트는 자신의 출력에 다른 컴포넌트를 참조할 수 있다.
이는 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있다는걸 의미한다.
React 앱에서는 버튼, 폼, 다이얼로그, 화면 등의 모든 것들이 흔히 컴포넌트로 표현된다.

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

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

일반적으로 새 React 앱은 최상위에 단일 App 컴포넌트를 가지고 있습니다.
하지만 기존 앱에 React를 통합하는 경우에는 Button과 같은 작은 컴포넌트부터 시작해서 뷰 계층의 상단으로 올라가면서 점진적으로 작업해야 할 수 있다.


컴포넌트 추출

다음 Comment 컴포넌트를 여러 개의 작은 컴포넌트로 나눠보자.

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>
  );
}
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>
  );
}

컴포넌트를 여러 개의 작은 컴포넌트로 언제&나누는 이유는 뭘까?

그 이유는 재사용성과 복잡성때문이다.

코드가 재사용 될 가능성이 있다면 새로운 컴포넌트로 만드는 건 좋은 방법이고,
코드가 복잡하다면 가독성을 개선하고 유지보수 할 수 있게 만들기 위해 컴포넌트를 분리할 수 있다.

즉, 컴포넌트는 UI를 독립적이고 재사용 가능한 조각으로 나눌 수 있다.

A. 재사용 가능한 컴포넌트

A-1. 재사용이 가능하다는 건 그만큼 일반적이라는 것.

컴포넌트의 재사용성을 고려할 때 일반적이라는 두 가지 측면 중 '속성이 보편적'인지 고민한다. 즉, '다른 컴포넌트가 가져가서 사용할 수 있도록 보편적인 속성을 갖고 있는가?'를 고려한다.

재사용성을 높이려면 더 많은 컴포넌트를 만족시켜야 하고 더욱 일반적이어야 한다.


A-2. HTML 요소 측면에서의 재사용성

function ListComponent(...) {
  return (
    <ul>
      <li>
        <h3>...</h3>
        <p>...</p>
      </li>
      <li>
        <h3>...</h3>
        <p>...</p>
      </li>
    </ul>
  );
}

위 컴포넌트를 분리해보자.

function SomethingComponent(...) {
  return (
    <>
      <h3>...</h3>
      <p>...</p>
    </>
  );
}

A-3. 중복을 고려한 재사용성
컴포넌트의 재사용성에 대해 말할 때 보통 중복을 떠올리는데, 두 곳 이상에서 중복된 무언가 존재한다면 '추출'하는 과정을 거치게되고, 추출한 것을 재사용한다.

function Page1() {
  return (
    <ul>
      <li>
        <section>
          <h3>...</h3>
          <p>가격...</p>
          <p>요약...</p>
        </section>
      </li>
    </ul>
  );
}

function Page2() {
  return (
    <ul>
      <li>
        <section>
          <h3>...</h3>
          <p>가격...</p>
        </section>
      </li>
    </ul>
  );
}

위 컴포넌트를 추출해보자.

function Page1() {
  return (
    <ul>
      <li>
        <Card ... />
      </li>
    </ul>
  );
}

function Page2() {
  return (
    <ul>
      <li>
        <Card ... />
      </li>
    </ul>
  );
}

추출한 Card 컴포넌트는 아래와 같이 만들어진다.

function Card(props) {
  return (
    <section>
      <h3>...</h3>
      <p>가격...</p>
      {props.showSummary && <p>요약...</p>}
    </section>
  );
}

이때 둘 이상의 컴포넌트에서 사용할 재사용 가능한 컴포넌트를 만들 때 가장 큰 특징 중 하나는 조건문이다.
완전히 같은 컴포넌트를 사용하면 문제가 되지 않지만 서로 다른 부분이 있다면 조건문이 들어간다.

B. 복잡한 컴포넌트

컴포넌트를 나누는 중요한 또 다른 이유 중 하나는 컴포넌트의 복잡성이다.

B-1. 컴포넌트가 여러 책임을 갖는 경우
하나의 컴포넌트에 여러 책임을 갖는 컴포넌트는 거대한 페이지 컴포넌트와 비슷하다.

function Page(props) {
  // 선택한 탭을 변경하면 보여주는 내용을 변경합니다.
  // 페이징을 다룹니다.
  // 단어를 검색을 합니다.
  // 검색 조건 토글을 다룹니다.
  // 등등
}

B-2. 컴포넌트에 비지니스 로직이 있는 경우

C. 렌더링 퍼포먼스

재사용과 복잡성 이외에 컴포넌트를 분리하면 좋은 기중 중 하나이다.
하나의 컴포넌트 안에서 서로 영향을 주지 않는 상태가 여럿 있으면 발생하는 문제


props는 읽기 전용이다.

props는 컴포넌트 외부에서 전달되는 속성이나 데이터를 저장하는 객체이다.
이 props 객체는 읽기 전용이어야 한다.
즉, 함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안된다.

function MyComponent(props) {
  // 잘못된 방식: props를 직접 수정하려고 함
  props.someValue = 'new value';

  return (
    <div>{props.someValue}</div>
  );
}

그럼, 모든 React 컴포넌트는 어떻게 자신의 props를 다뤄야 할까?
👉 순수 함수처럼 동작하면된다.

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

위와 같은 함수들을 순수 함수라고 호칭하는데, 입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하기 때문이다.






📚Reference

Components와 Props
프론트엔드 아키텍처: 컴포넌트를 분리하는 기준과 방법

post-custom-banner

0개의 댓글