TIL033 | 리액트 디자인 패턴 5가지

김태규·2021년 12월 20일
0
post-thumbnail
post-custom-banner

Compound Components Pattern

  • 불필요한 props 전달을 줄일 수 있다.
  • 관심사를 더 확실하게 분리할 수 있다.
  • 유지/보수 하기 더 쉬워진다. 원하는 것을 더하고 빼는 것이 수월하다.
  • JSX가 너무 무거워질 수 있다. 이 패턴을 적용하게 되면 JSX의 열 개수를 너무 늘릴 수 있다.
import React from "react";
import { Counter } from "./Counter";

function Usage() {
  const handleChangeCounter = (count) => {
    console.log("count", count);
  };

  return (
    <Counter onChange={handleChangeCounter}>
      <Counter.Decrement icon="minus" />
      <Counter.Label>Counter</Counter.Label>
      <Counter.Count max={10} />
      <Counter.Increment icon="plus" />
    </Counter>
  );
}

export { Usage };

Control Props Pattern

  • state가 컴포넌트 바깥에 드러나있기 때문에 사용자는 직접적으로 그 컴포넌트를 컨트롤할 수 있게 된다.
  • JSX, useState, handleChange 세 곳 모두를 체크해야 한다.
import React, { useState } from "react";
import { Counter } from "./Counter";

function Usage() {
  const [count, setCount] = useState(0);

  const handleChangeCounter = (newCount) => {
    setCount(newCount);
  };
  return (
    <Counter value={count} onChange={handleChangeCounter}>
      <Counter.Decrement icon={"minus"} />
      <Counter.Label>Counter</Counter.Label>
      <Counter.Count max={10} />
      <Counter.Increment icon={"plus"} />
    </Counter>
  );
}

export { Usage };

Custom Hook Pattern

  • hook은 State, Handler와 같은 내부 로직들을 포함하며 사용자에게 더 많은 통제권을 준다.
  • 로직이 렌더링하는 부분과 분리되어 있으며, 사용자는 둘을 이어줘야 한다.
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

function Usage() {
  const { count, handleIncrement, handleDecrement } = useCounter(0);
  const MAX_COUNT = 10;

  const handleClickIncrement = () => {
    //Put your custom logic
    if (count < MAX_COUNT) {
      handleIncrement();
    }
  };

  return (
    <>
      <Counter value={count}>
        <Counter.Decrement
          icon={"minus"}
          onClick={handleDecrement}
          disabled={count === 0}
        />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment
          icon={"plus"}
          onClick={handleClickIncrement}
          disabled={count === MAX_COUNT}
        />
      </Counter>
      <button onClick={handleClickIncrement} disabled={count === MAX_COUNT}>
        Custom increment btn 1
      </button>
    </>
  );
}

export { Usage };

Props Getters Pattern

  • Custom Hook Pattern 에서 State, Handler와 같은 로직에 필요한 것을 하나하나 전달해줬다면, Props Getters Pattern 에서는 props getters 목록을 전달한다. 여기서 getter 는 많은 props 를 리턴해주는 함수이고, 사용자가 올바르게 사용할 수 있도록 의미있게 이름을 지어야한다.
  • 컴포넌트를 사용하는 쉬운 방법을 제공하면서 복잡한 부분은 가려져 있다. 사용자는 올바른 getter를 그에 맞는 JSX 요소에 사용하기만 하면 된다.
  • 복잡한 부분이 가려져있는 만큼 정확하게 오버라이드(상속 받은 메서드의 내용을 변경) 하기 어렵다.
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

const MAX_COUNT = 10;

function Usage() {
  const {
    count,
    getCounterProps,
    getIncrementProps,
    getDecrementProps
  } = useCounter({
    initial: 0,
    max: MAX_COUNT
  });

  const handleBtn1Clicked = () => {
    console.log("btn 1 clicked");
  };

  return (
    <>
      <Counter {...getCounterProps()}>
        <Counter.Decrement icon={"minus"} {...getDecrementProps()} />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment icon={"plus"} {...getIncrementProps()} />
      </Counter>
      <button {...getIncrementProps({ onClick: handleBtn1Clicked })}>
        Custom increment btn 1
      </button>
      <button {...getIncrementProps({ disabled: count > MAX_COUNT - 2 })}>
        Custom increment btn 2
      </button>
    </>
  );
}

export { Usage };

State Reducer Pattern

  • Custom Hook Pattern과 비슷한데 추가로 사용자가 Hook을 통해 전달된 reducer 를 정의할 수 있다. 이 reducer는 컴포넌트 내부 action들은 이제 외부에서 접근가능하며 오버로드(파라미터의 개수나 타입을 다르게 하는 것) 할 수 있다.
  • reducer의 액션이 바뀔 수 있기 때문에, 컴포넌트 내부 로직에 대한 깊은 이해가 필요하다.
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

const MAX_COUNT = 10;
function Usage() {
  const reducer = (state, action) => {
    switch (action.type) {
      case "decrement":
        return {
          count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1)
        };
      default:
        return useCounter.reducer(state, action);
    }
  };

  const { count, handleDecrement, handleIncrement } = useCounter(
    { initial: 0, max: 10 },
    reducer
  );

  return (
    <>
      <Counter value={count}>
        <Counter.Decrement icon={"minus"} onClick={handleDecrement} />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment icon={"plus"} onClick={handleIncrement} />
      </Counter>
      <button onClick={handleIncrement} disabled={count === MAX_COUNT}>
        Custom increment btn 1
      </button>
    </>
  );

references

https://javascript.plainenglish.io/5-advanced-react-patterns-a6b7624267a6

https://velog.io/@dnr6054/%EC%9C%A0%EC%9A%A9%ED%95%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%8C%A8%ED%84%B4-5%EA%B0%80%EC%A7%80#compound-components-pattern

post-custom-banner

0개의 댓글