Design & Rendering Patterns/React) Design Patterns

[React Design] HOC Pattern 고차 컴포넌트 패턴

머지?는 병합입니다 2024. 11. 25. 15:03

 

1. 고차 컴포넌트 정의

 

자바스크립트는 함수형 프로그래밍이 특징인 언어입니다.

그 중에서 함수를 값처럼 인자를 받아 함수를 리턴하는 함수를 고차 함수라고 합니다.

 

고차 컴포넌트( HOC )도 이와 비슷한 개념 입니다.

단지 컴포넌트를 인자로 받아 새로운 컴포넌트를 리턴하는 차이가 있다고 보시면 됩니다.

 

그리고 이 리턴하는 새로운 컴포넌트는

고차 컴포넌트의 기능을 래핑하여 기능 + 인자 컴포넌트를 반환하는 차이가 있을 뿐입니다.

 

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)

 

위 코드에서는 기존의 Button 과 Text  컴포넌트에 withStyle 고차 컴포넌트의 css 기능을 입혀 

StyledButton 과 StyledText 라는 새로운 컴포넌트를 만들었습니다

 

 

만약 API 데이터르 가졍는 동안 Loading... 이라는 글자를 추가하려면 아래의 고차함수를 

만든 후 래핑해주면 됩니다.

import React, { useEffect, useState } from "react";

export default function withLoader(Element, url) {
  return (props) => {
    const [data, setData] = useState(null);

    useEffect(() => {
      async function getData() {
        const res = await fetch(url);
        const data = await res.json();
        setData(data);
      }

      getData();
    }, []);

    if (!data) {
      return <div>Loading...</div>;
    }

    return <Element {...props} data={data} />;
  };
}

 

데이터를 가져오는 동안에는 로딩 중 텍스트를 반환하며, 데이터를 받아오는 게 끝나면 data 는 더 이상 null 이 아니므로

props 로 받아온 컴포넌트를 표시할 수 있습니다.

 

mport React from "react";
import withLoader from "./withLoader";

function DogImages(props) {
  return props.data.message.map((dog, index) => (
    <img src={dog} alt="Dog" key={index} />
  ));
}

export default withLoader(
  DogImages,
  "https://dog.ceo/api/breed/labrador/images/random/6"
);

 

DogImages 목록 위로 마우스 커서를 가져가면 텍스트 박스를 표시하는 기능을 추가한다고 가정해 봅시다.

 

 

2. 고차 컴포넌트 조합

 

우선 전달받은 컴포넌트에 마우스 호버링 prop을 제공하는 고차 컴포넌트를 만들어야 합니다.

이 prop 을 기반으로, DogImages 목록 위에 마우스 호버링 여부에 따라 텍스트 박스를 조건부로 랜더링할 수 있습니다.

 

import React, { useState } from "react";

export default function withHover(Element) {
  return props => {
    const [hovering, setHover] = useState(false);

    return (
      <Element
        {...props}
        hovering={hovering}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
      />
    );
  };
}

 

이제 withHover 고차 컴포넌트 를 withLoader 고차 컴포넌트에 래핑할 수 있습니다

import React from "react";
import withLoader from "./withLoader";
import withHover from "./withHover";

function DogImages(props) {
  return (
    <div {...props}>
      {props.hovering && <div id="hover">Hovering!</div>}
      <div id="list">
        {props.data.message.map((dog, index) => (
          <img src={dog} alt="Dog" key={index} />
        ))}
      </div>
    </div>
  );
}

export default withHover(
  withLoader(DogImages, "https://dog.ceo/api/breed/labrador/images/random/6")
);

 

이제 DogImages Element 에는 withHover와 withLoader에서 전달한 모든 props가 포함됩니다.

이제 호버링 props의 값이 참인지 거짓인지에 따라 조건부로 Hovering! 텍스트 상자를 렌더링할 수 있습니다.

 

 

3. HOC Pattern 의 장점

 

재사용하고자 하는 로직을 한 곳에 모아 관리할 수 있습니다. 

이렇게 하면 여기저기서 코드르 복사하면서 실수르 버그를 퍼트릴 위험을 줄일 수 있습니다.

 


4. HOC Pattern 의 단점 ( wrapper hell, props 충돌 )

 

 

고차 컴포넌트가 대상 컴포넌트에 전달하는 prop의 이름은 충돌을 일으킬 수 있습니다.

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)

 

이 경우, withStyles 고차 컴포넌트는 전달받은 컴포넌트에 style이라는 이름의 prop을 추가합니다.

하지만 Button 컴포넌트는 이미 style prop을 가지고 있어, 자칫하면 덮어씌워질 수 있습니다.

이러한 이름 충돌을 처리할 수 있도록 prop의 이름을 변경하거나 병합하는 방식을 사용해야 합니다.

 

function withStyles(Component) {
  return props => {
    const style = {
      padding: '0.2rem',
      margin: '1rem',
      ...props.style
    }

    return <Component style={style} {...props} />
  }
}

const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)


여러 고차 컴포넌트를 조합하여 사용하게 되다보면 어떤 고차 컴포넌트가 어떤 prop을 제공하는지 

파악하기 어려울 수 있습니다. 이렇게 되면 디버깅과 애플리케이션 확장에 어려움이 생길 수 있습니다.

이러한 현상을 wrapper hell 이라고 합니다.

 

 

참고:

https://www.patterns.dev/react/hoc-pattern

 

HOC Pattern

Pass reusable logic down as props to components throughout your application

www.patterns.dev