본문 바로가기

React

HOC(Higher-Order Component)

 

 

  HOC(Higher-Order Component)는 컴포넌트 로직을 재사용할 수 있게 해주는 패턴이다. 간단히 말해서 고차 컴포넌트가 컴포넌트를 가져와서 기능이 추가된 새 컴포넌트를 반환하는 함수이다. HOC는 원래 컴포넌트를 래핑하고 일부 추가 props를 제공하거나 해당 동작을 수정한다.

  예를 들어 API에서 일부 데이터를 액세스해야 하는 컴포넌트가 있다고 한다면, 여러 컴포넌트에서 API 요청을 처리하는 코드르 복제하는 대신 컴포넌트의 props로 데이터를 가져와 데이터 가져오기 논리를 사용한 새로운 컴포넌트를 반환하는 HOC를 만들 수 있다. 

  HOC는 코드 재사용, 렌더링 제어, props 조작 등 다양한 용도로 사용할 수 있다. 재사용 가능한 구성 요소를 만들고 기존 구성 요소의 기능을 향상시키는 강력한 도구이다.

import React from 'react';

function withAuth(WrappedComponent) {
  retrun function(props) {
    const [isLoggedIn, setIsLoggedIn] = React.useState(false);
    
    React.useEffect(() => {
      setIsLoggedIn(true);
    }, []);
    
    if (!isLoggedIn) {
      return <div>Please log in to access this content.</div>
    }
    
    return <WrappedComponent {...props} />;
  }
}

function MyComponent(props) {
  return <div>{props.isLoggedIn ? 'Welcome!' : 'Please log in.'}</div>
}

const EnhancedMyComponent = withAuth(MyComponent);

export default EnhancedMyComponent;

isLoggedIn이라는 props를 기반으로 일부 컨텐츠를 렌더링하는 MyComponent라는 컴포넌트가 있다. 사용자가 로그인한 경우에만 렌더링이 되도록 MyComponent에 대한 액세스를 제한한다. 

  그리고 래핑된 컴포넌트 MyComponent를 가져오고 사용자가 인증되었는지 여부를 확인하는 새 컴포넌트를 반환하는 withAuth라는 HOC를 만든다. 사용자가 인증되면 모든 props가 전달된 래핑된 컴포넌트를 렌더링한다. 사용자가 인증되지 않은 경우 로그인을 요청하는 메시지를 렌더링한다. 

  마지막으로 withAuth HOC를 사용해 MyComponent를 HOC로 래핑하고 결과 컴포넌트를 EnhancedMyComponent로 내보낸다. EnhancedMyComponent가 렌더링되면 MyComponent를 렌더링하기 전에 먼저 사용자가 인증되었는지 여부를 확인한다.

 

  HOC를 사용할 때 래핑된 컴포넌트에 추가 props를 전달할 수도 있다.

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

function MyComponent(props) {
  return (
    <div>
      {props.isLoggedIn ? 'Welcome!' : 'Please log in.'}
      {props.isAdmin && <div>You are an admin!</div>}
    </div>
  )
}

const EnhancedMyComponent = withAuth(MyComponent);

export default EnhancedMyComponent;

 여기서 isAdmin이라는 MyComponent에 props를 추가했다. MyComponent를 withAuth로 래핑하면 다음과 같이 props를 전달하면 된다.

<EnhancedMyComponent isLoggedIn={true} isAdmin={true} />

이렇게 하면 EnhancedMyComponent가 렌더링되면 MyComponent를 렌더링하기 전에 사용자가 인증되었는지와 관리자인지 여부를 확인한다. 이렇게 코드를 수정하지 않고도 컴포넌트에 기능을 쉽게 추가할 수 있다.

 

  API에서 일부 데이터를 표시하는 컴포넌트를 HOC를 통해서도 구성할 수 있다.

import React from 'react';

function withLoadingIndicator(WrappedComponent) {
  return function(props) {
    const [isLoading, setIsLoading] = React.useState(true);
    const [data, setData] = React.useState(null);
    
    React.useEffect(() => {
      fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        setData(data)
        setLoading(false)
      }
    }, [])
    
    if (isLoading) {
      return <div>Loading...</div>
    }
    
    return <WrappedComponent data={data} {...props} />
  }
}

컴포넌트를 인수로 사용하고 데이터를 가져오는 동안 로드 표시기를 추가하는 새 컴포넌트를 반환하는 withLoadingIndicator라는 HOC를 정의했다. 새 컴포넌트는 가져온 데이터와 props를 사용하여 전달된 컴포넌트를 렌더링한다.

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

function MyComponent(props) {
  const { data } = props;
  return (
    <div>
      <h1>{data.title}</div>
      <p>{data.description}</p>
    </div>
  )
}

const EnhancedMyComponent = withLoadingIndicator(MyComponent);

export default EnhancedMyComponent;

 

  두 가지 HOC를 만들고 함께 연결하는 방법도 있다.

import React from 'react';

function withSorting(WrappedComponent) {
  return class extends React.Component {
    constructor(props)
      super(props);
      this.state = {
        sortBy: 'name'
      }
    }
    
    handleSort = (sortBy) => {
      this.setState({ sortBy });
    }
    
    render() {
      const { items, ...rest } = this.props;
      const { sortBy } = this.state;
      
      const sortedItems = items.sort((a, b) => {
        if (a[sortBy] < b[sortBy]) {
          return -1;
        }
        if (a[sortBy] > b[sortBy]) {
          return 1;
        }
        return 0;
      })
      return <WrappedComponent items={sortedItems} inSort={this.handleSort} sortBy={sortBy} {...rest} />;
    }
  }
}

function withFiltering(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        filterBy: ''
      }
    }
    
    handleFilter = (filterBy) => {
      this.setState({ filterBy });
    }
    
    render() {
      const { items, ...rest } = this.props;
      const { filterBy } = this.state;
      
      const filteredItems = items.filter((item) => {
        return item.name.toLowerCase().includes(filterBy.toLowerCase());
      });
      
      return <WrappedComponent items={filteredItems} onFilter={this.handleFilter} filterBy={filterBy} {...rest} />;
    }
  }
}

function ItemList(props) {
  const { items, onSort, sortBy, onFilter, filterBy } = props;
  
  return (
    <div>
      <div>
        <label>Sort by:</label>
        <select value={sortBy} onChange={(e) => onSort(e.target.value)}>
          <option value="name">Name</option>
          <option value="price">Price</option>
        </select>
      </div>
      <div>
        <label>Filter by name:</label>
        <input type='text' value={filterBy} onChange={(e) => onFilter(e.target.value)} />
      </div>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <div>{item.name}</div>
            <div>{item.price}</div>
          </li>
        ))}
      </ul>
    </div>
  )
}

const EnhancedItemList = withSorting(withFiltering(ItemList));

export default EnhancedItemList;

이렇게 withSorting과 withFiltering이라는 두 개의 HOC를 정의하고 ItemList 컴포넌트에 적용했다.

'React' 카테고리의 다른 글

useMemo  (0) 2023.03.29
React에서 html과 css 추출하기  (1) 2023.03.24
렌더링 된 후 특정 엘리먼트 접근하기 (후처리하기)  (0) 2023.03.23
Fragments  (0) 2023.03.23
Ref 전달하기  (0) 2023.03.22