참고 사이트
- 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
✨ State Reducer Pattern
장점: reducer는 모든 내부 action들에 접근하여 오버라이드할 수 있습니다.
단점: reducer의 액션이 바뀔 수 있기 때문에, 컴포넌트 내부 로직에 대한 깊은 이해가 필요합니다.

🚩 구현
1. context API를 통해서, Provider와 useCounterContext 정의
src/patterns/state-reducer/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/state-reducer/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/state-reducer/useCounter.jsx
import { useReducer } from 'react';
const internalReducer = (state, action) => {
switch (action.type) {
case 'increment':
return {
num: state.num + 1,
};
case 'decrement':
return {
num: state.num - 1,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
export default function useCounter({ initialNum }, reducer = internalReducer) {
const [{ num }, dispatch] = useReducer(reducer, {
num: initialNum,
});
const incrementNum = () => {
dispatch({ type: 'increment' });
};
const decrementNum = () => {
dispatch({ type: 'decrement' });
};
return {
num,
incrementNum,
decrementNum,
};
}
useCounter.reducer = internalReducer;
useCounter.types = {
increment: 'increment',
decrement: 'decrement',
};
src/patterns/state-reducer/components/Count.jsx
src/patterns/state-reducer/components/Decrement.jsx
src/patterns/state-reducer/components/Increment.jsx
src/patterns/state-reducer/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 }) {
return (
<button type="button" style={{ padding: '4px' }} onClick={onClick}>
-
</button>
);
}
import React from 'react';
export default function Increment({ onClick }) {
return (
<button type="button" style={{ padding: '4px' }} onClick={onClick}>
+
</button>
);
}
import React from 'react';
export default function Label({ children }) {
return <span style={{ padding: '4px' }}>{children}</span>;
}
3. Cart 컴포넌트에서 action 오버라이드, Board 컴포넌트는 일반 action 사용

- Cart 컴포넌트
- 감소 버튼 클릭 시, -2 감소
- Board 컴포넌트
- 증가 버튼 +1, 감소 버튼 -1
src/patterns/state-reducer/Cart.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Cart() {
const reducer = (state, action) => {
switch (action.type) {
case 'decrement':
return {
num: state.num - 2, // The decrement delta was changed for 2 (Default is 1)
};
default:
return useCounter.reducer(state, action);
}
};
const { num, incrementNum, decrementNum } = useCounter(
{ initialNum: 0 },
reducer
);
return (
<>
<h2> Cart Component (-2 +1)</h2>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment onClick={incrementNum} />
</Counter>
</>
);
}
src/patterns/state-reducer/Board.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Board() {
const { num, incrementNum, decrementNum } = useCounter({ initialNum: 0 });
return (
<>
<h2> Board Component (+- 1)</h2>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment onClick={incrementNum} />
</Counter>
</>
);
}
참고 사이트
- 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
✨ State Reducer Pattern
장점: reducer는 모든 내부 action들에 접근하여 오버라이드할 수 있습니다.
단점: reducer의 액션이 바뀔 수 있기 때문에, 컴포넌트 내부 로직에 대한 깊은 이해가 필요합니다.

🚩 구현
1. context API를 통해서, Provider와 useCounterContext 정의
src/patterns/state-reducer/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/state-reducer/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/state-reducer/useCounter.jsx
import { useReducer } from 'react';
const internalReducer = (state, action) => {
switch (action.type) {
case 'increment':
return {
num: state.num + 1,
};
case 'decrement':
return {
num: state.num - 1,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
export default function useCounter({ initialNum }, reducer = internalReducer) {
const [{ num }, dispatch] = useReducer(reducer, {
num: initialNum,
});
const incrementNum = () => {
dispatch({ type: 'increment' });
};
const decrementNum = () => {
dispatch({ type: 'decrement' });
};
return {
num,
incrementNum,
decrementNum,
};
}
useCounter.reducer = internalReducer;
useCounter.types = {
increment: 'increment',
decrement: 'decrement',
};
src/patterns/state-reducer/components/Count.jsx
src/patterns/state-reducer/components/Decrement.jsx
src/patterns/state-reducer/components/Increment.jsx
src/patterns/state-reducer/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 }) {
return (
<button type="button" style={{ padding: '4px' }} onClick={onClick}>
-
</button>
);
}
import React from 'react';
export default function Increment({ onClick }) {
return (
<button type="button" style={{ padding: '4px' }} onClick={onClick}>
+
</button>
);
}
import React from 'react';
export default function Label({ children }) {
return <span style={{ padding: '4px' }}>{children}</span>;
}
3. Cart 컴포넌트에서 action 오버라이드, Board 컴포넌트는 일반 action 사용

- Cart 컴포넌트
- 감소 버튼 클릭 시, -2 감소
- Board 컴포넌트
- 증가 버튼 +1, 감소 버튼 -1
src/patterns/state-reducer/Cart.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Cart() {
const reducer = (state, action) => {
switch (action.type) {
case 'decrement':
return {
num: state.num - 2, // The decrement delta was changed for 2 (Default is 1)
};
default:
return useCounter.reducer(state, action);
}
};
const { num, incrementNum, decrementNum } = useCounter(
{ initialNum: 0 },
reducer
);
return (
<>
<h2> Cart Component (-2 +1)</h2>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment onClick={incrementNum} />
</Counter>
</>
);
}
src/patterns/state-reducer/Board.jsx
import React from 'react';
import Counter from './Counter';
import useCounter from './useCounter';
export default function Board() {
const { num, incrementNum, decrementNum } = useCounter({ initialNum: 0 });
return (
<>
<h2> Board Component (+- 1)</h2>
<Counter value={num}>
<Counter.Decrement onClick={decrementNum} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment onClick={incrementNum} />
</Counter>
</>
);
}