(리액트) 디자인 패턴 – 컨트롤 소품 패턴

참조 사이트

소품 모드 제어

이점: 모든 상태를 제어하고 소유하는 하나의 특정 구성 요소가 있으므로 사용자 지정 논리를 연결할 수 있습니다.

피해: 구현은 복잡할 수 있습니다.

시행

하나. context API를 통해 Provider 정의 및 useCounterContext

src/patterns/control-props/useCounterContext.jsx

import { createContext, useContext } from 'react';

const CounterContext = createContext(null);

export function CounterProvider({ children, value }) {
  return (
    <CounterContext.Provider value={value}>{children}</CounterContext.Provider>
  );
}

export function useCounterContext() {
  return useContext(CounterContext);
}

2. 컨트롤 컴포넌트(카운터)에 외부 상태를 사용할지 내부 상태를 사용할지 제어하는 ​​로직을 작성합니다.

src/patterns/control-props/Counter.jsx

import { useState } from 'react';
import Count from './components/Count';
import Decrement from './components/Decrement';
import Increment from './components/Increment';
import Label from './components/Label';
import { CounterProvider } from './useCounterContext';

export default function Counter({ children, value = null, onChangeCounter }) {
  const (num, setNum) = useState(0);

  // value와 onChangeCounter가 전달되면, inConstrolled = true (외부 state)
  const isControlled = value !
== null && !
!
onChangeCounter; // 전달받은 value를 사용할 지, 내부 num을 사용할 지 결정 const getCount = () => (isControlled ? value : num); const handleCountChange = (newValue) => isControlled ? onChangeCounter(newValue) : setNum(newValue); const incrementNum = () => handleCountChange(getCount() + 1); const decrementNum = () => handleCountChange(getCount() - 1); return ( <CounterProvider value={{ num: getCount(), incrementNum, decrementNum }}> {children} </CounterProvider> ); } Counter.Decrement = Decrement; Counter.Increment = Increment; Counter.Label = Label; Counter.Count = Count;

3. 커스텀 로직 삽입 및 카운터 컴포넌트 사용

  • 외부 상태 – 카운터
    • 초기 수: 500
    • 증감의 범위는 500~505
  • 내부 상태 – 카운터
    • 초기 카운트: 0
    • 증가 또는 감소 범위 제한 없음

src/patterns/control-props/Cart.jsx

import React, { useState } from 'react';
import Counter from './Counter';

export default function Cart() {
  const (num, setNum) = useState(500);
  const handleChangeCounter = (newNum) => {
    if (newNum >= 500 && newNum <= 505) {
      setNum(newNum);
    }
  };

  return (
    <>
      <Counter value={num} onChangeCounter={handleChangeCounter}>
        <Counter.Decrement />
        <Counter.Label>외부 state - Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment />
      </Counter>
      <hr />
      <Counter>
        <Counter.Decrement />
        <Counter.Increment />
        <Counter.Label>내부 state - Counter</Counter.Label>
        <Counter.Count />
      </Counter>
    </>
  );
}


결과