FrontEnd/React 심화

[React] HOC(고차 컴포넌트) 함수형 예제

기은P 2022. 11. 15. 17:18
반응형

1. 개요

기술면접을 보았을때 HOC란 무엇인가?라고 물었을때 잘 모른다고 대답을 했었는데요, 살짝 찾아보니까 메인 다큐먼트에서는 클래스형 컴포넌트에서 사용되는 것이 있었습니다.

기술면접에서 물어볼 정도면 뭔가 중요한 내용이 있는게 아닐까 싶어서 HOC를 함수형으로 사용할 수 있는 방법을 찾아보았고, 어째서 HOC에서 물어보셨는지에 대해 깨닳게 되었습니다!

 

예제로 사용한 깃허브 주소입니다.

https://github.com/kimdongjang/hoc_test

 

GitHub - kimdongjang/hoc_test

Contribute to kimdongjang/hoc_test development by creating an account on GitHub.

github.com

 

 

2. HOC(Higher Order Component)

고차 컴포넌트는 컴포넌트를 매개 변수로 받아 새로운 컴포넌트로 반환하는 함수를 의미합니다.

React에서 저희는 함수형 컴포넌트를 작성합니다.

HOC는 이렇게 작성한 컴포넌트를 인자로 받아 Wrapping 해서 새로운 컴포넌트를 반환해 컴포넌트의 재사용성을 늘리는 역할을 해줍니다.

 

 

3. 사용방법

HOC는 with로 시작하는 컨벤션으로 파일을 작성합니다.

먼저 예제로 작성할 코드를 보면서 설명을 적겠습니다.

 

HOC를 사용해서 여러 개의 이미지마다 공통적으로 마우스를 hover 할 때 반응하는 컴포넌트를 만들으려고 합니다.

1차적으로 작성된 코드가 Image만 있는 컴포넌트이고, 2차적으로 Image마다 event를 추가한다고 했을 때, HOC를 사용해 이 Image들에게 Wrapping을 하면 Image마다 event를 작성할 필요없이 Wrap하는 것으로 코드 작성이 쉬워집니다.

HOC가 재사용성을 늘리기 위해 사용하는 것이기 때문에 이러한 관점에서 코드를 보는 것이 중요합니다.

 

exam1/ImageBox.jsx

import withHover from "./withHover";

function ImageBox({
  imageUrl,
  imageTitle,
  isHovered,
  handleMouseEnter,
  handleMouseLeave
}) {
  return (
    <div>
      {isHovered && <div id="hover">{imageTitle}</div>}
      <img
        src={imageUrl}
        alt={imageTitle}
        width="400px"
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      />
      <h5>이미지에 마우스를 올리면 이미지 제목이 표시됩니다.</h5>
    </div>
  );
}

export default withHover(ImageBox);

 

exam1/withHover.jsx

import { useState } from "react";

export default function withHover(InnerComponent) {
  // props는 App.jsx 부모에서 prop로 전달한 데이터
  return (props) => {
    const [isHovered, setIsHovered] = useState(false);

    function handleMouseEnter() {
      setIsHovered(true);
    }

    function handleMouseLeave() {
      setIsHovered(false);
    }

    return (
      <InnerComponent
        {...{
          ...props,
          handleMouseEnter,
          handleMouseLeave,
          isHovered
        }}
      />
    );
  };
}

위와 같은 코드가 HOC 컴포넌트이고, 인자로 Wrap할 컴포넌트를 받아서 return값으로 컴포넌트를 반환합니다!

 

유의할 점은 함수형으로 작성할 경우에는 위처럼

function withHOC() 내부에

return () => {

   return (<innerComponent/>)

}

와 같이 return을 2개를 작성해서 사용해야 한다는 점입니다.

 

 

App.js

import logo from './logo.svg';
import './App.css';
import ImageBox from './exam1/ImageBox';
import { useEffect, useState } from 'react';
import axios from 'axios';
import TodoList from './exam2/TodoList';
import UserList from './exam2/UserList';

function App() {
  const url = "https://dog.ceo/api/breeds/image/random";
  const [title, setTitle] = useState('')
  const [image, setImage] = useState('');
  useEffect(() => {
    axios.get(url).then(response => { setTitle(response.data.message); setImage(response.data.message) })
  }, [])
  return (
    <div className="App">
      <ImageBox imageUrl={image} imageTitle={title} />
      <UserList />
      <TodoList />
    </div>
  );
}

export default App;

App.js에서는 랜덤 이미지를 호출해와 ImageBox에 이미지 주소와 title을 전달해줍니다.

*userList와 todoList는 주석을 달아서 테스트하면 됩니다.

 

 

 

4. 다른 코드

exam2/TodoList.jsx

import React, { useEffect, useState } from 'react';
import withHOC from './withHoc';

function TodoLIst({ data }) {
  let renderTodos = data.map((todo) => {
    return (
      <div key={todo.id}>
        <p>
          <strong>{todo.title}</strong>
        </p>
      </div>
    );
  });

  return (
    <div>
      <div>{renderTodos}</div>
    </div>
  );
}

export default withHOC(TodoLIst, 'todos');

 

exam2/UserList.jsx

import React, { useEffect, useState } from 'react';
import withHOC from './withHoc';

function UserList({ data }) {
  let renderUsers = data.map((user) => {
    return (
      <div key={user.id}>
        <p>
          <strong>{user.name}</strong>
        </p>
      </div>
    );
  });

  return (
    <div>
      <div>{renderUsers}</div>
    </div>
  );
}

export default withHOC(UserList, 'users');

 

exam2/withHoc.jsx

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

function withHOC(WrappedComponent, entityTitle) {
  return function () {
    const [data, setData] = useState([]);
    const [term, setTerm] = useState('');

    useEffect(() => {
      const timer = setTimeout(() => {
        let fetchData =
          entityTitle === 'users'
            ? [
              { name: 'yeji', id: 1 },
              { name: 'yoyo', id: 2 },
              { name: 'jojo', id: 3 },
              { name: 'aoao', id: 4 },
            ]
            : [
              { id: 1, userId: 1, title: 'hello', completed: true },
              { id: 2, userId: 2, title: 'bye', completed: true },
              { id: 3, userId: 3, title: 'mornign', completed: false },
              { id: 4, userId: 4, title: 'night', completed: false },
            ];

        setData([...fetchData]);

        clearTimeout(timer);
      }, 2000);
    }, []);

    let filterData = data.filter((d) => {
      if (entityTitle === 'users') {
        const { name } = d;
        return name.indexOf(term) >= 0;
      }

      if (entityTitle === 'todos') {
        const { title } = d;
        return title.indexOf(term) >= 0;
      }
    });

    return (
      <div>
        <h2>{entityTitle}</h2>
        <input
          type="text"
          value={term}
          onChange={(e) => setTerm(e.target.value)}
        />
        <WrappedComponent data={filterData}></WrappedComponent>
      </div>
    );
  };
}

export default withHOC;

 

App.js

위에 있는 App.js 그대로 사용하면 됩니다.

 

 

 

참고

https://ostarblog.tistory.com/12

https://velog.io/@ellie12/React-HOC-%EA%B3%A0%EC%B0%A8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8

반응형