Design Pattern

Higher Order Component Pattern (with React)

eddie0329 2022. 1. 21. 15:57
๋ฐ˜์‘ํ˜•

๐Ÿ“Œ ๋ชฉ๋ก

  • HOC ๋ž€?
  • ๊ฐ„๋‹จํ•œ HOC ๊ตฌํ˜„
  • JSON Placeholder ๋ฐ์ดํ„ฐ๋กœ withLoader ๋งŒ๋“ค๊ธฐ
  • HOC์˜ ์žฅ์ ๊ณผ ๋‹จ์ 

๐Ÿ“Œ HOC ๋ž€?

High Order Component๋Š” ๊ณตํ†ต ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋” ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๋งˆ์น˜ mixin๊ณผ ๊ฐ™์ด์š”. ํ•˜์ง€๋งŒ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ๋ฒˆ ๊ฐ์‹ธ์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ํ•œ๋ฒˆ ์–ด๋–ค ๋…€์„์ธ์ง€ ๋ณผ๊นŒ์š”?

๐Ÿ“Œ ๊ฐ„๋‹จํ•œ HOC ๊ตฌํ˜„

๋จผ์ € ์ด๋Ÿฐ withStyle ํ•จ์ˆ˜๋ฅผ ํ•œ๋ฒˆ ์ •์˜ํ•ด๋ณผ๊ฒŒ์š”.

const withStyle = (Component) => {
  return props => {
    return <Component style={{ color: 'red', border: 'solid 1px blue', }} {...props} />
  }
}

๊ทธ๋ฆฌ๊ณ  ๋ฒ„ํŠผ๊ณผ Text๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import withStyle from "./hoc/withStyles";

const Button = ({ style, children }) => <button style={style}>{ children }</button>;

const StyledButton = withStyle(Button);

export default StyledButton;
import withStyle from "./hoc/withStyles";

const Text = ({ style, children }) => <p style={style}>{ children }</p>

const StyledText = withStyle(Text);

export default StyledText;

์ด์ฒ˜๋Ÿผ ํ•œ๋ฒˆ ๊ฐ์‹ธ์ฃผ๋Š” ํ˜•ํƒœ๋กœ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ JSON Placehodler ๋ฐ์ดํ„ฐ๋กœ withLoader ๋งŒ๋“ค๊ธฐ

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋ฒˆ์—” ํ•œ๋ฒˆ data fetch์— ๊ด€๋ จํ•œ ์•„์ด๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊นŒ์š”?
์—ฌ๊ธฐ์„œ๋Š” JSONPlaceholder๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋จผ์ € ApiService ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ์ค„๊ฒŒ์š”.

import axios from 'axios';

const ApiService = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com/',
  timeout: 1000,
});

ApiService.interceptors.response.use(
  (response) => response.data, 
  (error) => Promise.reject(error)
);

export default ApiService;

๊ทธ๋ฆฌ๊ณ  ์ด๊ฑธ withLoader์— ์ ์šฉ์‹œ์ผœ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { useState, useEffect, useCallback } from 'react';
import ApiService from '../../services';

const withLoader = (Component, url) => {
  return (props) => {
    const [data, setData] = useState();
    const [loading, setLoading] = useState(false);

    const fetchData = useCallback(async () => {
      setLoading(true);
      setData(await ApiService.get(url));
      setLoading(false);
    }, []);

    useEffect(() => {
      fetchData();
    } , [fetchData]);

    if (loading) return <div style={{ fontSize: '200px' }}>Loading...</div>
    else return <Component  {...props} data={data} fetchData={fetchData} />
  }
}

export default withLoader;

์ด์ œ ์ด๊ฑธ Todo์— ํ•œ๋ฒˆ ์—ฐ๊ฒฐ์„ ํ•ด๋ณผ๊นŒ์š”?

import { useMemo } from "react";
import withLoader from "./hoc/withLoader";

const Todo = ({ todo }) => {
  const title = useMemo(() => todo?.title ?? "", [todo]);
  const completed = useMemo(() => todo?.completed ?? false, [todo]);

  return (
    <li>
      <span style={{ textDecoration: "bold", marginRight: "4px" }}>
        {title}
      </span>
      <input type="checkbox" checked={completed} onChange={() => {}} />
    </li>
  );
};

const Todos = ({ data, fetchData }) => {
  return (
    <section style={{ marginTop: '20px' }}>
      <button onClick={fetchData}>Refecth Data</button>
      <ul>
        {data?.map((todo) => (
          <Todo key={todo.id} todo={todo} />
        ))}
      </ul>
    </section>
  );
};

export default withLoader(Todos, "todos");

๐Ÿ“Œ HOC์˜ ์žฅ์ ๊ณผ ๋‹จ์ 

์žฅ์ 

  • ๋กœ์ง ์žฌ์‚ฌ์šฉ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • HOC๋Š” ์ค‘์ฒฉ์ด ๊ฐ€๋Šฅํ•œ๋ฐ depth๊ฐ€ ๊นŠ์–ด์ง€๋ฉด ์ฐพ์•„๋‹ค๋‹ˆ๊ธฐ๊ฐ€ ํž˜๋“ค๋‹ค.
๋ฐ˜์‘ํ˜•