Skip to content

๐ŸŽฏ Hyundux: ์ž์ฒด ์ƒํƒœ๊ด€๋ฆฌ

Dunk edited this page Aug 7, 2024 · 3 revisions

์†Œ๊ฐœ

hyundux๋Š” ๊ธฐ์กด์— redux๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋˜ ์ „ํ†ต์ ์ธ ํ‹€์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ state๋ฅผ ์†Œ์œ ํ•˜์ง€ ์•Š๊ณ  page ๋‹จ์œ„์—์„œ props๋กœ ์ปดํฌ๋„ŒํŠธ์— ๋‚ด๋ ค์ฃผ๊ณ  ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ component์— ์ž์ฒด state์˜ ๋ณ€ํ™”๋ฅผ ์ค„ ์ˆ˜ ์—†๋‹ค๋Š”๊ฑด ๋ฐ˜์ชฝ์งœ๋ฆฌ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์ฒด์ ์œผ๋กœ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ์ƒํƒœ๋ณ€ํ™”๋„ component๊ฐ€ ๊ฐ€์ง€๋„๋ก ํ•˜์˜€๋‹ค.

๊ทธ๋ฆผ ์˜ˆ์‹œ 4

์ฃผ์•ˆ์ 

  1. ์ข์€ ์ƒํƒœ๊ด€๋ฆฌ : ๊ธฐ์กด์— state๋Š” Page ๋‹จ์œ„๋กœ ๊ด€๋ฆฌํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋Œ€๋ถ€๋ถ„ Page๋ผ๋Š” ๋ถ€๋ชจ์—์„œ ์ƒํƒœ๋ฅผ ๋ชจ์•„ ๊ด€๋ฆฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ component๋ผ๋Š” ๋ฆฌ์•กํŠธ์˜ ์žฅ์ ์„ ๋ฐ˜๋งŒ ์‚ด๋ฆฐ๊ฒƒ ๊ฐ™๋‹ค๋Š” ๋Š๋‚Œ์ด ๋“ค์—ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ผ๋Š” ๊ฒƒ์€ ๊ฒฐ๊ตญ ์žฌ์‚ฌ์šฉ์ด ๋˜์•ผํ•˜๋Š”๋ฐ ๊ทธ๋Ÿด๋•Œ๋งˆ๋‹ค ๊ทธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ง„ ํŽ˜์ด์ง€์— ์ƒˆ๋กœ์šด ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š”๊ฒƒ์€ ์ข‹์•„๋ณด์ด์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„์˜ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.
  2. ๋‹จ๋ฐฉํ–ฅ ์ƒํƒœ๊ด€๋ฆฌ : ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ๋‹จ๋ฐฉํ–”์ด์—ฌ์•ผํ–ˆ๋‹ค. ์™ธ๋ถ€ fetch๋‚˜ local storage๊ฐ™์ด ํด๋ผ์ด์–ธํŠธ์˜ ์กฐ์ž‘๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„์•ผํ–ˆ์œผ๋ฉฐ ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋Ÿฌํ•œ ํด๋ผ์ด์–ธํŠธ ์™ธ๋ถ€์—์„œ state๋ฅผ ๋ฐ”๊พธ๊ฒŒ๋˜๋ฉด ์–ด๋–ค side effect๊ฐ€ ๋ฐœ์ƒํ• ์ง€ ๋ชจ๋ฅด๊ณ  ์–ด๋–ค ์‹œ์ ์— state ๊ฐฑ์‹  ๋ ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— hyundux์ž์ฒด๋Š” ๋™๊ธฐ์ ์œผ๋กœ ๋‹จ๋ฐฉํ–ฅ์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ์œ„ํ•ด ์‹ ๊ฒฝ์ผ๋‹ค.

hyundux store ๊ตฌ์กฐ

Store

interface Store {
	states: State<unknown>[];
	reducers: Reducer<unknown>[];
	subscribe: <PayLoad>(
		initState: State<PayLoad>,
		reducer: Reducer<PayLoad> | null,
		cb: (state: State<PayLoad>) => void
	) => void;
	dispatch: <T>(action: Action | DoAction<T>) => void;
	publish: <PayLoad>(oldState: State<PayLoad>, state: State<PayLoad>) => void;
	subscribeList: Map<string, <PayLoad>(state: State<PayLoad>) => void>;
}
  1. state: store์— ๋“ฑ๋ก๋œ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฐ์—ด
  2. reducer: action๋“ค์„ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ €์žฅํ•œ reducer
  3. subscribe: hook์—์„œ๋งŒ ๋ถ€๋ฅด๋Š” ํ•จ์ˆ˜๋กœ ์ดˆ๊ธฐ์— state์™€ reducer, publish์— ๋“ฑ๋ก๋  ๊ฒƒ๋“ค์„ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
  4. dispatch: ์ปดํฌ๋„ŒํŠธ๊ฐ€ action์„ ์ด์ฃผ๊ณ  ๋งตํ•‘๋œ ์ •๋ณด๋“ค์„ ํ†ตํ•ด state๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค.
  5. publish: state๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Œ์„ ์•Œ๋ฆฌ๋Š” ํ•จ์ˆ˜์ด๋‹ค.
  6. subscribeList: ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๋ฐฐ์—ด์ด๋‹ค.

ํ˜„์žฌ dispatch๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  function, property๋“ค์€ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•˜์ง€ ์•Š๋Š”๋‹ค.

dispatch๋„ ์™ธ๋ถ€์—์„œ ์ง์ ‘์ ์œผ๋กœ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ˆ˜์ •ํ•  ์˜ˆ์ •

State & Action & Reducer & DoAction interface

interface State<T> {
	type: string;
	payload: T;
}

interface Action {
	type: string;
	actionName: string;
	payload?: object;
}

interface Reducer<PayLoad> {
	type: string;
	reducer: (state: State<PayLoad>, action: Action) => State<PayLoad>;
}

interface DoAction<T> {
	type: string;
	doing: (state: State<T>) => State<T>;
}
  • State
    1. type: ํ•ด๋‹น state์— mapping์„ ์œ„ํ•œ key
    2. payload: state์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” payload
  • Action
    1. type: state์— mappingํ•˜๊ธฐ ์œ„ํ•œ key
    2. actionName: reducer์™€ mappingํ•˜๊ธฐ ์œ„ํ•œ key
    3. payload: ๊ฐ action์—์„œ ๋“ค๊ณ  ์žˆ์–ด์•ผํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” payload
  • Reducer
    1. type: state์— mappingํ•˜๊ธฐ ์œ„ํ•œ key
    2. reducer: state์™€ action์„ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜
  • DoAction
    1. type: state์— mappingํ•˜๊ธฐ ์œ„ํ•œ key
    2. reducer์˜ switch ๋ถ€๋ถ„์„ ๋•Œ์™€์„œ action์—์„œ ์ฒ˜๋ฆฌํ•จ

Reducer์— ๋ฆฌํ„ดํƒ€์ž…์ด Promise๊ฐ€ ์•„๋‹Œ์ด์œ 
๋ฆฌ๋“€์„œ๋Š” store๋ฅผ ๋ฐ˜๋“œ์‹œ ๋™๊ธฐ์ ์œผ๋กœ ๋ฐ”๊พธ๊ฒŒํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค. ๋น„๋™๊ธฐ๋ฅผ reducer์—์„œ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด ๋‹จ๋ฐฉํ–ฅ๋ณด๋‹จ ๋‹ค๋ฐฉํ–ฅ์ด ๋  ๊ฒƒ ๊ฐ™์•„์„œ reducer์—์„œ๋Š” ๋ชจ๋“  ์ฒ˜๋ฆฌ๋ฅผ ๋™๊ธฐ์ ์œผ๋กœํ•œ๋‹ค. (๐Ÿ“š Hyundux-saga: ์ž์ฒด ๋ฏธ๋“ค์›จ์–ด ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ ์—ฌ๊ธฐ์—์„œ ๋งŒ๋“  ๋ฏธ๋“ค์›จ์–ด๊ฐ€ action์„ ์žก์•„์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.)

DoAction์„ ๋งŒ๋“ค๊ฒŒ ๋œ ์ด์œ 
์‚ฌ์‹ค ์ƒํƒœ๊ด€๋ฆฌ๋Š” ๋ฌด๊ฒ๋‹ค. ๋‹ค์–‘ํ•œ state๊ฐ€ ์žˆ๋Š”๊ฒƒ๋„ ์•„๋‹ˆ๊ณ  ํ•˜๋‚˜์˜ state์ด๊ธด ํ•˜์ง€๋งŒ UI๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š”๊ฒŒ ์•„๋‹Œ ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง์ธ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ DoAction์€ ์ข€ action์— ์‹ค์ œ ์‹คํ–‰๋˜๋„๋กํ•˜๋Š” Do๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ฝ”๋“œ์‘์ง‘์„ฑ์€ ์ข‹์ง€ ์•Š์ง€๋งŒ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์˜ ์ˆ˜๋‚˜ ๋งŽ์€ ๊ฒƒ๋“ค์ด ์ค„์–ด๋“œ๋Š” Do๋ฅผ ๋งŒ๋“ค๊ฒŒ ๋˜์—ˆ๋‹ค. (flutter์— bloc๊ณผ cubit์„ ์ฐธ๊ณ )

custom hook

function useWork<PayLoad>(
	initialState: HState<PayLoad>,
	reducer: Reducer<PayLoad>
) {
	const [state, setState] = useState<HState<PayLoad>>(initialState);
	store.subscribe(state, reducer, (newState) => {
		setState(newState);
	});
	return state.payload
}

๊ฐ ์ƒํƒœ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถ€๋ฅด๋Š” ํ•ด๋‹น custom hook์„ ๋ถ€๋ฅด๋Š๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ subscribe๋ฅผ ํ•จ

  1. parameters
    • initState: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ดˆ๊ธฐ์— ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ƒํƒœ๊ฐ’๋“ค
    • reducer: ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ์ฒ˜๋ฆฌํ•˜๋Š” reducer

์‚ฌ์šฉ ์˜ˆ์‹œ

์˜ˆ์‹œ๋Š” ์ˆซ์ž๋ฅผ ์„ธ์–ด์ฃผ๋Š” counter์˜ ์˜ˆ์ œ์ด๋‹ค.

const WORKFLOW_NAME = "Count";
// state type
interface CountPayLoad {
	count: number;
	text: string;
}

const initCountState = createState<CountPayLoad>(WORKFLOW_NAME, {
	count: 0,
	text: "helloWorld",
});

// define reducer
const countReducer: Reducer<CountPayLoad> = {
	type: WORKFLOW_NAME,
	reducer: async function reducer(state, action) {
		const payLoad = state.payload;
		switch (action.actionName) {
			case "countUp":
				return makePayLoad(state, { count: payLoad.count + 1 });
			case "countDown":
				return makePayLoad(state, { count: payLoad.count - 1 });
			case "getText": {
				const actionPayLoad = (action.payload || {}) as { text: string };
				return makePayLoad(state, { text: actionPayLoad.text });
				}
			default:
				return state;
		}
	},
};

// actions
const action = {
	countUp: (): Action => {
		return {
			type: WORKFLOW_NAME,
			actionName: "countUp",
		};
	},
	countDown: (): Action => {
		return {
			type: WORKFLOW_NAME,
			actionName: "countDown",
		};
	},
	getText: (text: string): Action => {
		return {
			type: WORKFLOW_NAME,
			actionName: "getText",
			payload: {
				text: text,
			},
		};
	},
};

// do๋ฅผ ์‚ฌ์šฉํ•  ๋• Reducer๊ฐ€ ๋”ฐ๋กœ ํ•„์š”์—†๋‹ค.
const doAction = {
	countUp: (): DoAction<CountPayLoad> => {
		return {
			type: Do_NAME,
			doing: (state: State<CountPayLoad>): State<CountPayLoad> => {
				return makePayLoad(state, { count: state.payload.count + 1 });
			},
		};	
	},
};

export { action, initCountState, countReducer };

์œ„์ฒ˜๋Ÿผ state, reducer, action, doAction ๋“ฑ์„ ์„ ์–ธํ•˜๊ณ  ์‚ฌ์šฉํ•œ๋‹ค. ์œ„์™€ ๊ฐ™์€ ํ˜•ํƒœ๋Š” ์•„๋‹ˆ๋”๋ผ๋„ Action, Reducer, State์˜ ํƒ€์ž…์„ ํ†ต์ผ์‹œํ‚ค๊ณ  ํ‚ค๊ฐ€๋˜๋Š” WROKFLOW_NAME๋งŒ ๋งž์ถ”๊ฒŒ ๋˜๋ฉด ์ž˜ ์ž‘๋™ํ•œ๋‹ค.

์ถ”ํ›„ ์ˆ˜์ •์‚ฌํ•ญ

  1. ํ˜„์žฌ๋Š” dispatch๋ฅผ store๋ฅผ ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š”๋ฐ ์ด๋ฅผ custom hook์— ๋„ฃ์–ด์„œ ๋ณด๋‚ด๊ณ  ์ง์ ‘์ ์œผ๋กœ store์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์•„์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.
  2. ์ค„์ผ๋งŒํผ ์ค„์˜€์ง€๋งŒ ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋„ˆ๋ฌด ๊ธด๊ฑฐ ๊ฐ™๋‹ค..
  3. ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ state๋ฅผ ๊ด€๋ฆฌํ•˜๋‹ค๋ณด๋‹ˆ ํ•ด๋‹น state์— ๋Œ€ํ•œ ์˜์กด์„ฑ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค... ์ด ์ ์€ ๊ณ ์น˜๊ธฐ ์œ„ํ•ด ๋งŽ์€ ์‹œ๊ฐ„์ด ๋“ค๊ฒƒ๊ฐ™๋‹ค.
  4. store์— ๋ผ์ดํ”Œ์‚ฌ์ดํด์„ ์ข€ ๋” ์ž˜ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.