React 컴포넌트 재사용 방법

곽태욱·2020년 10월 6일
0

고차 컴포넌트

고차 함수 (Higher-Order Function)

수학과 컴퓨터 과학에서 고차 함수는 하나 이상의 함수를 인수로 가지거나 함수를 결과로 반환하는 함수이다. 다른 모든 함수들은 일차 함수이다. (위키백과)

고차 컴포넌트 (Higher-Order Component)

컴포넌트를 인수로 가지면서 새로운 컴포넌트를 반환하는 컴포넌트이다. (React 공식 문서)

고차 함수의 '고차'와 비슷하게 고차 컴포넌트(HoC)도 컴포넌트를 인수로 받아 컴포넌트를 반환하는 컴포넌트이다. 즉, HoC는 매개변수로 받은 컴포넌트에 기능을 추가해서 새로운 컴포넌트를 반환한다. HoC는 아래 그림과 같이 기존 컴포넌트에 새로운 기능(초록색 부분)을 추가하고 싶을 때 사용한다.

어찌보면 공통되는 기능을 분리해서 재사용하는 점에서 추상 클래스와 비슷하다. 하지만 추상 클래스는 하위 클래스가 추상 메소드를 오버라이드해서 구체화할 수 있는 것과 다르게, HoC는 추가되는 기능을 각 컴포넌트 별로 커스터마이즈 할 수 없다. 그래서 HoC로 추가할 기능은 HoC를 사용하는 모든 컴포넌트에서 동일하게 동작할 수 있어야 한다. 물론 HoC는 컴포넌트와 함께 다른 값도 인수로 가질 수 있다.

그리고 HoC로 생성된 컴포넌트를 다시 HoC로 여러 번 감쌀 수 있다.

주의점

하지만 HoC로 컴포넌트를 감싸는 순서에 의존하면 안 된다. 즉, '매개변수로 전달된 컴포넌트에 이런 기능이 있겠지'라고 가정하면서 코딩하면 안 된다. HoC는 매개변수 컴포넌트의 기존 기능에 관심을 가지는 게 아니라 그 컴포넌트에 새로운 기능을 추가하는 것이 목적이다.

그리고 모든 모듈화 작업이 그렇듯이 HoC 내부에 외부 상태에 의존하는 코드가 있으면 안 된다. 즉, '외부 상태는 아마 ~ 이겠지'라고 가정하고 코드를 작성하면 안 된다. 이는 순수 함수의 장점을 버리는 것이고 나중에 디버그가 어려워진다. 추가되는 기능은 HoC 내부에서 모두 처리해야 하고 외부 입력을 받을 필요가 있으면 HoC 인수로 넘겨줘야 한다.

요약

  1. HoC를 사용하면 기존 컴포넌트에 새로운 기능을 추가할 수 있다.
  2. 컴포넌트에서 공통으로 사용되는 동일한 기능을 분리해서 재사용할 수 있다.

사용 방법

HoC를 적용한 컴포넌트 사용

// src/App.tsx
import React from "react";
import WrappedComponent from "./WrappedComponent";

function App() {
  return (
    <div className="App">
      <WrappedComponent title="title11" description="description22" />
    </div>
  );
}

export default App;

WrappedComponent에 HoC를 적용했는데 일반 컴포넌트를 사용하듯이 props를 넘겨주면 된다.

HoC 정의

// src/withHoC.tsx
import React, { FC } from "react";
import { WrappedComponentProps } from "./WrappedComponent";

const withHoC = (WrappedComponent: FC<WrappedComponentProps>) => {
  const EnhancedComponent: FC<WrappedComponentProps> = (props) => {
    console.log(props);
    return <WrappedComponent {...props} />;
  };

  return EnhancedComponent;
};

export default withHoC;

위와 같이 HoC를 정의할 수 있다. HoC의 이름은 일반적으로 with으로 시작한다.

EnhancedComponent의 props는 App.tsx에서 넘겨준 props가 넘어온다. 이는 콘솔 로그를 통해 확인할 수 있다. 이렇게 넘어온 props를 WrappedComponent 컴포넌트 props로 넘겨준다. 여기서 새로운 props를 넘겨줄 순 있지만 기존 props와 이름 충돌이 발생할 수 있기 때문에 웬만하면 props를 그대로 넘겨주는 것을 권장한다.

일반 컴포넌트에 HoC 적용

// src/WrappedComponent.tsx
import React, { FC } from "react";
import withHoC from "./withHoC";

export interface WrappedComponentProps {
  title: string;
  description: string;
}

const WrappedComponent: FC<WrappedComponentProps> = (props) => {
  console.log("WrappedComponent", props);
  return <div>Hello World</div>;
};

export default withHoC(WrappedComponent);

일반 컴포넌트에 HoC를 적용하려면 마지막 줄에서 컴포넌트를 export 할 때 HoC로 감싸주면 된다.

(번외) 다른 매개변수를 가지는 HoC

const withFetch = (
  WrappedComponent: FC<WrappedComponentProps>,
  url: string,
) => {
  (async () => {
    const response = await fetch(url)
    ...
  })();
  ...
}

export default withFetch;

HoC는 컴포넌트와 함께 다른 값을 매개변수로 가질 수 있다. 만약 데이터를 불러오는 withFetch HoC를 만들고 싶으면 아래와 같이 url을 매개변수로 전달할 수 있다. 데이터를 가져오는 부분은 IIFE 문법을 사용했다.

export default withFetch(WrappedComponent, "http://example.com")

그리고 위와 같이 일반 컴포넌트에 HoC를 적용하면 된다.

사용자 지정 Hook

반복적으로 사용되는, 기능보다 작은 단위(코드)?

UI가 아닌 기능을 여러 컴포넌트에서 재사용하기를 원한다면, 별도의 JavaScript 모듈로 분리하거나 사용자 지정 Hook을 만드는 것이 좋다.

컴포넌트 합성

어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올 지 미리 예상하기 어려울 수 있다. 이러한 컴포넌트에서는 children prop을 사용하여 자식 엘리먼트를 그대로 출력에 전달할 수 있다.

공통되는 컴포넌트 구조를 분리할 때

로직은 상위 컴포넌트에서 실행 후 그 결과(데이터)를 하위 컴포넌트로 내려준다.

profile
이유와 방법을 알려주는 메모장 겸 블로그. 블로그 내용에 대한 토의나 질문은 언제나 환영합니다.

0개의 댓글