참고한 사이트
- 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
✨ Custom Hook Pattern
장점: Custom Hook은 states, handlers를 노출하여 많은 제어 권한을 가질 수 있습니다.
단점: 로직과 UI가 분리되어 있어서, 올바르게 사용하기 위해선 컴포넌트가 어떻게 동작하는지 잘 이해해야합니다.
🚩 구현
1. context API를 통해서, Provider와 useCounterContext 정의
src/patterns/custom-hooks/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. Counter 컴포넌트와 useCounter(custom hook), 자식 컴포넌트 구현
src/patterns/custom-hooks/Counter.jsx
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: num }) {
return <CounterProvider value={{ num }}>{children}</CounterProvider>;
}
Counter.Decrement = Decrement;
Counter.Increment = Increment;
Counter.Label = Label;
Counter.Count = Count;
src/patterns/custom-hooks/useCounter.jsx
import { useState } from 'react';
export default function useCounter(initialNum) {
const [num, setNum] = useState(initialNum);
const incrementNum = () => setNum((prev) => prev + 1);
const decrementNum = () => setNum((prev) => prev - 1);
return { num, incrementNum, decrementNum };
}
src/patterns/custom-hooks/components/Count.jsx
src/patterns/custom-hooks/components/Decrement.jsx
src/patterns/custom-hooks/components/Increment.jsx
src/patterns/custom-hooks/components/Label.jsx
import React from 'react';
import { useCounterContext } from '../useCounterContext';
export default function Count() {
const { num } = useCounterContext();
return <strong style={{ padding: '4px' }}>{num}</strong>;
}
import React from 'react';
export default function Decrement({ onClick, disabled }) {
return (
<button
type="button"
style={{ padding: '4px' }}
onClick={onClick}
disabled={disabled}
>
-
</button>
);
}
import React from 'react';
export default function Increment({ onClick, disabled }) {
return (
<button
type="button"
style={{ padding: '4px' }}
onClick={onClick}
disabled={disabled}
>
+
</button>
);
}
import React from 'react';
export default function Label({ children }) {
return <span style={{ padding: '4px' }}>{children}</span>;
}
3. Cart 컴포넌트에서 custom logic 사용, Board 컴포넌트에서 일반 logic 사용
- Cart 컴포넌트
- count의 초기 num: 0
- 0 일때, 감소 버튼 disabled
- 10 일때, 증가 버튼 disabled
- Board 컴포넌트
- count의 초기 num: 5
- 증감범위 제한 없음
src/patterns/custom-hooks/Cart.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Cart() {
const { num, incrementNum, decrementNum } = useCounter(0);
const MAX_COUNT = 10;
const handleClickIncrement = () => {
// Put your custom logic
if (num < MAX_COUNT) {
incrementNum();
}
};
return (
<>
<h1>Cart Component</h1>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} disabled={num === 0} />
<Counter.Label>Custom increment Counter</Counter.Label>
<Counter.Count />
<Counter.Increment
onClick={handleClickIncrement}
disabled={num === MAX_COUNT}
/>
</Counter>
</>
);
}
src/patterns/custom-hooks/Board.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Board() {
const { num, incrementNum, decrementNum } = useCounter(5);
return (
<>
<h1>Board Component</h1>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} />
<Counter.Label>Basic Counter</Counter.Label>
<Counter.Count />
<Counter.Increment onClick={incrementNum} />
</Counter>
</>
);
}