본문 바로가기
JavaScript/React

[ReactJS] 재사용할 수 있는 컴포넌트 제작

by 혀나Lee 2019. 9. 17.
이 페이지는 재사용할 수 있는 컴포넌트 제작에 대해 설명하며 리액트 & 리액트 네이티브 통합 교과서를 기반으로 작성했습니다.

일체형 컴포넌트의 어려움

특정 기능에 대해 하나의 컴포넌트만 구현한다면 작업이 간소화된다. 최소한 유지해야 할 컴포넌트가 많지 않고 모든 것이 컴포넌트의 내부에 있기 때문에 데이터의 통신 경로가 많지 않을 것이다. 그러나 이 개념은 팀 개발의 협업을 어렵게 하고 컴포넌트가 커질 수록 추후 리팩토링하는 것이 더 어려워진다.

(나의 경우는 퍼블리셔가 있는 회사에서 HTML을 주면 일체형 컴포넌트를 만들게 되는 점이 있었다...이제 이걸 잘 나누자)

 

JSX 마크업

구현하고자 하는 일체형 컴포넌트를 작성하자. 너무 길어서 깃헙 주소로 대체

https://github.com/PacktPublishing/React-and-React-Native/blob/master/Chapter05/monolithic-components/MyFeature.js

 

PacktPublishing/React-and-React-Native

Code files for React and React Native uploaded by Packt - PacktPublishing/React-and-React-Native

github.com

 

컴포넌트 구조 리팩토링

JSX로 시작

모든 일체형 컴포넌트의 JSX는 리팩토링하는 방법을 찾는 최고의 시작점이다.

article 리스트 컴포넌트 구현

import React, { Component } from 'react';

export default class ArticleList extends Component {
  render() {
    // The properties include things that are passed in
    // from the feature component. This includes the list
    // of articles to render, and the two event handlers
    // that change state of the feature component.
    const {
      articles,
      onClickToggle,
      onClickRemove,
    } = this.props;

    return (
      <ul>
        {articles.map(i => (
          <li key={i.id}>
            { /* The "onClickToggle()" callback changes
                 the state of the "MyFeature" component. */ }
            <a
              href="#"
              title="Toggle Summary"
              onClick={onClickToggle.bind(null, i.id)}
            >
              {i.title}
            </a>
            &nbsp;

            { /* The "onClickRemove()" callback changes
                 the state of the "MyFeature" component. */ }
            <a
              href="#"
              title="Remove"
              onClick={onClickRemove.bind(null, i.id)}
            >
              &#10007;
            </a>
            <p style={{ display: i.display }}>
              {i.summary}
            </p>
          </li>
        ))}
      </ul>
    );
  }
}

여기서 <li> 태그 부분을 컴포넌트로 빼낸다.

article 리스트, 아이템 컴포넌트 구현

import React, { Component } from 'react';
import ArticleItem from './ArticleItem';

export default class ArticleList extends Component {
  render() {
    // The properties include things that are passed in
    // from the feature component. This includes the list
    // of articles to render, and the two event handlers
    // that change state of the feature component. These,
    // in turn, are passed to the "ArticleItem" component.
    const {
      articles,
      onClickToggle,
      onClickRemove,
    } = this.props;

    // Now this component maps to an "<ArticleItem>" collection.
    return (
      <ul>
        {articles.map(i => (
          <ArticleItem
            key={i.id}
            article={i}
            onClickToggle={onClickToggle}
            onClickRemove={onClickRemove}
          />
        ))}
      </ul>
    );
  }
}
import React, { Component } from 'react';

export default class ArticleItem extends Component {
  render() {
    // The "article" is mapped from the "ArticleList"
    // component. The "onClickToggle()" and
    // "onClickRemove()" event handlers are passed
    // all the way down from the "MyFeature" component.
    const {
      article,
      onClickToggle,
      onClickRemove,
    } = this.props;

    return (
      <li>
        { /* The "onClickToggle()" callback changes
             the state of the "MyFeature" component. */ }
        <a
          href="#"
          title="Toggle Summary"
          onClick={onClickToggle.bind(null, article.id)}
        >
          {article.title}
        </a>
        &nbsp;

        { /* The "onClickRemove()" callback changes
             the state of the "MyFeature" component. */ }
        <a
          href="#"
          title="Remove"
          onClick={onClickRemove.bind(null, article.id)}
        >
          &#10007;
        </a>
        <p style={{ display: article.display }}>
          {article.summary}
        </p>
      </li>
    );
  }
}

엘리먼트들의 이벤트 프로퍼티의 핸들러들을 컴포너트 내부에서 구현해도 되지만, 코드의 재사용성을 위해 부모 컴포넌트로 부터 프로퍼티를 통해 받도록 구현한다.

article 컴포넌트 추가 기능 구현

import React, { Component } from 'react';

export default class AddArticle extends Component{
  render() {
    const {
      name,
      title,
      summary,
      onChangeTitle,
      onChangeSummary,
      onClickAdd
    } = this.props;

    return (
      <section>
        <h1>{name}</h1>
        <input
          placeholder="Title"
          value={title}
          onChange={onChangeTitle}
        />
        <input
          placeholder="Summary"
          value={summary}
          onChange={onChangeSummary}
        />
        <button onClick={onClickAdd}>Add</button>
      </section>
    );
  }
}

최상위 컴포넌트

코드를 보기 좋게 render만 표현함 - 자세히 보기

    return (
      <section>
        { /* Now the add article form is rendered by the
             "AddArticle" component. This component can
             now be used in several other components. */ }
        <AddArticle
          name="Articles"
          title={title}
          summary={summary}
          onChangeTitle={this.onChangeTitle}
          onChangeSummary={this.onChangeSummary}
          onClickAdd={this.onClickAdd}
        />

        { /* Now the list of articles is rendered by the
             "ArticleList" component. This component can
             now be used in several other components. */ }
        <ArticleList
          articles={articles}
          onClickToggle={this.onClickToggle}
          onClickRemove={this.onClickRemove}
        />
      </section>
    );

함수형 컴포넌트 만들기

위의 리스트, 아이템, 폼 컴포넌트와 같이 화면에 보여주기위한 render 기능만 있는 컴포넌트의 경우는 함수형으로 구현하는 것이 좋다. 함수형으로 구현할 경우, 컴포넌트가 상태 또는 생명주기 메서드에 의존하지 않는다는 것을 명시해준다. React는 컴포넌트가 함수라는 것을 알았을 때 많은 작업을 수행하지 않기 때문에 훨씬 효율적이다.

article 리스트

import React from 'react';
import ArticleItem from './ArticleItem';

export default ({
  articles,
  onClickToggle,
  onClickRemove,
}) => (
  <ul>
    {articles.map(i => (
      <ArticleItem
        key={i.id}
        article={i}
        onClickToggle={onClickToggle}
        onClickRemove={onClickRemove}
      />
    ))}
  </ul>
);

article 리스트 아이템

import React from 'react';

export default ({
  article,
  onClickToggle,
  onClickRemove,
}) => (
  <li>
    { /* The "onClickToggle()" callback changes
         the state of the "MyFeature" component. */ }
    <a
      href="#"
      title="Toggle Summary"
      onClick={onClickToggle.bind(null, article.id)}
    >
      {article.title}
    </a>
    &nbsp;

    { /* The "onClickRemove()" callback changes
         the state of the "MyFeature" component. */ }
    <a
      href="#"
      title="Remove"
      onClick={onClickRemove.bind(null, article.id)}
    >
      &#10007;
    </a>
    <p style={{ display: article.display }}>
      {article.summary}
    </p>
  </li>
);

article 추가 컴포넌트

import React from 'react';

export default ({
  name,
  title,
  summary,
  onChangeTitle,
  onChangeSummary,
  onClickAdd,
}) => (
  <section>
    <h1>{name}</h1>
    <input
      placeholder="Title"
      value={title}
      onChange={onChangeTitle}
    />
    <input
      placeholder="Summary"
      value={summary}
      onChange={onChangeSummary}
    />
    <button onClick={onClickAdd}>Add</button>
  </section>
);

 

렌더 프롭 활용

렌더 프롭은 리액트 16의 기능이 아니다. 이는 리액트 16의 출시와 동시에 인기를 얻고 있는 기술이자, 리액트 측에서 공식적으로 인증된 의존성과 대체성 문제를 다루는 기술이다.

렌더 프롭 설명은 React 공식 사이트에서 자세히 설명하고 있어서 대체합니다.

https://ko.reactjs.org/docs/render-props.html#use-render-props-for-cross-cutting-concerns

 

Render Props – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

댓글