React
[React] 디자인 패턴 - Props Getters Pattern
presentKey
2023. 3. 6. 02:43
참고한 사이트
- https://javascript.plainenglish.io/5-advanced-react-patterns-a6b7624267a6
- https://simsimjae.medium.com/react-design-pattern-props-getter-pattern-5d3cf6f0b495
✨ Props Getters Pattern
장점: props 오버로딩을 통해서 부모 -> 자식, 자식 -> 부모 양방향 커뮤니케이션이 가능해집니다.
단점: props를 재정의 하기위해선, 노출된 props과 내부 로직을 제대로 이해하고 있어야 합니다.
🚩 구현
1. context API를 통해서, Provider와 useCounterContext 정의
src/patterns/props-getters/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, 자식 컴포넌트 구현
src/patterns/props-getters/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/props-getters/useCounter.jsx
import { useState } from 'react';
// Function which concat all functions togeter
const callFnsInSequence =
(...fns) =>
(...args) =>
fns.forEach((fn) => fn && fn(...args));
export default function useCounter({ initialNum, max }) {
const [num, setNum] = useState(initialNum);
const incrementNum = () => setNum((prev) => prev + 1);
const decrementNum = () => setNum((prev) => prev - 1);
// props getter for 'Counter'
const getCounterProps = ({ ...otherProps } = {}) => ({
value: num,
'aria-valuemax': max,
'aria-valuenow': num,
...otherProps,
});
// props getter for 'Increment'
const getIncrementProps = ({ onClick, ...otherProps } = {}) => ({
onClick: callFnsInSequence(incrementNum, onClick),
disabled: num === max,
...otherProps,
});
// props getter for 'Decrement'
const getDecrementProps = ({ onClick, ...otherProps } = {}) => ({
onClick: callFnsInSequence(decrementNum, onClick),
disabled: num === 0,
...otherProps,
});
return {
num,
incrementNum,
decrementNum,
getCounterProps,
getIncrementProps,
getDecrementProps,
};
}
🔔 [JavaScript 문법] { ... } = { }, 기본 값이 할당된 Destructured parameter
src/patterns/props-getters/components/Count.jsx
src/patterns/props-getters/components/Decrement.jsx
src/patterns/props-getters/components/Increment.jsx
src/patterns/props-getters/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, ...props }) {
return (
<button
type="button"
style={{ padding: '4px' }}
onClick={onClick}
{...props}
>
-
</button>
);
}
import React from 'react';
export default function Increment({ onClick, ...props }) {
return (
<button
type="button"
style={{ padding: '4px' }}
onClick={onClick}
{...props}
>
+
</button>
);
}
import React from 'react';
export default function Label({ children }) {
return <span style={{ padding: '4px' }}>{children}</span>;
}
3. Cart 컴포넌트에서 노출된 props 사용 및 props 재정의
- 노출된 props 이용
- {...getIncrementProps({ onClick: handleBtn1Click })}
- 1을 더하고 alert
- {...getIncrementProps({ disabled: num >= MAX_COUNT - 2 })}
- 8 까지만 1을 더하고, disabled
src/patterns/props-getters/Cart.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
const MAX_COUNT = 10;
export default function Cart() {
const { num, getCounterProps, getIncrementProps, getDecrementProps } =
useCounter({ initialNum: 0, max: MAX_COUNT });
const handleBtn1Clicked = () => alert('btn1 clicked');
return (
<>
<Counter {...getCounterProps()}>
<Counter.Decrement {...getDecrementProps()} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment {...getIncrementProps()} />
</Counter>
<br />
<button
type="button"
{...getIncrementProps({ onClick: handleBtn1Clicked })}
>
1을 더하고 alert
</button>
<br />
<button {...getIncrementProps({ disabled: num >= MAX_COUNT - 2 })}>
count: 8 까지만 1을 더하고, disabled
</button>
</>
);
}