Skip to content

๐Ÿ“š Hyunduxโ€saga: ์ž์ฒด ๋ฏธ๋“ค์›จ์–ด ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ

Dunk edited this page Aug 25, 2024 · 2 revisions

์†Œ๊ฐœ

hyundux๋Š” ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์— ์ „์ฒด์ ์ธ ํ๋ฆ„์ด ๋ฐฉํ•ด๋˜์ง€ ์•Š๋„๋ก ๋งŒ๋“  client๋ฅผ ์œ„ํ•œ ์ƒํƒœ๊ด€๋ฆฌ์ด๋‹ค. ํ•˜์ง€๋งŒ Server API๋‚˜ Local storage๊ฐ™์€ ์™ธ๋ถ€ํ™˜๊ฒฝ์— ์ ‘๊ทผ์€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— redux-saga์— ๋ฏธ๋“ค์›จ์–ด์™€ ์ค‘๊ฐ„์— ์ด๋ฒคํŠธ๋ฅผ interceptํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ์ ์— ์ฐฉ์•„ํ•˜์˜€๊ณ , react-query์˜ ๋ณ‘๋ ฌ์  API ๊ตฌ์„ฑ์— ์˜ํ–ฅ์„ ๋ฐ›์•„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

๊ทธ๋ฆผ ์˜ˆ์‹œ hyundux-saga

์ฃผ์•ˆ์ 

  1. ๋‹ค์–‘ํ•œ ๋น„๋™๊ธฐ๋กœ์ง์˜ ๋ณ‘๋ ฌ์  ๋™๊ธฐ์  ์ˆ˜ํ–‰ : ์–ด๋–ค ๋น„๋™๊ธฐ๋กœ์ง์ด ๋‹ค๋ฅธ ๋น„๋™๊ธฐ๋กœ์ง์„ ๋ถˆ๋Ÿฌ์„œ chain์„ ํ˜•์„ฑํ•˜๊ณ  ์‹ถ์ง€ ์•Š์•˜๋‹ค. ์ด๋Š” ํ…Œ์ŠคํŠธ๋„ ์–ด๋ ต๊ณ  ์–ด๋–ค ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ์˜ˆ์ƒํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ๋ณ‘๋ ฌ์ ์œผ๋กœ ๋กœ์ง์ด ์‹คํ–‰๋˜๊ณ  ๊ทธ ๊ฐ’๋“ค์„ spreadํ•˜๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์˜€๋‹ค.
  2. ํ…Œ์ŠคํŠธ ์šฉ์ด : ๋„คํŠธ์›Œํฌ๋‚˜ local stroage๋ฅผ ๋ฐ›๋Š” ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ๊ฐ€ ํž˜๋“ค๋‹ค๋Š” ์ ์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ๋กœ์ง๊ณผ action์„ ๋ถ„๋ฆฌํ•˜๊ณ  ๋ชจ๋“  ๋น„๋™๊ธฐ๋กœ์ง์˜ ๊ฒฐ๊ณผ๋ฅผ action์˜ ๋„ฃ๊ณ  ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๊ฐ ๋‚˜๋ˆ ์„œ ํ…Œ์ŠคํŠธ๋„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€๋‹ค.

hyundux-saga ๊ตฌ์กฐ

Story

export type Story = (object: object) => Promise<object>;
export type RunStory = () => Promise<object>;

export function createStory(story: Story, parameter: object): RunStory {
	return function () {
		return story(parameter);
	};
}
  1. story : API์— 1๋Œ€1 ๋Œ€์‘๋˜๋Š” ํ•˜๋‚˜์˜ story๋กœ์จ ํ•จ์ˆ˜ํ˜•ํƒœ๋กœ ์ž‘๋™ํ•œ๋‹ค.
  2. RunStory : Hyundux-saga ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ํƒ€์ž…์œผ๋กœ์จ ๋ฆฌํ„ด๊ฐ’์€ saga๋‚ด์—์„œ ์‹คํ–‰๋œ๋‹ค.
  3. createStory : component๋Š” story๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜์ง€์•Š๊ณ  saga๊ฐ€ ์‹คํ–‰ํ•˜์—ฌ์„œ ์‹คํ–‰์‹œ๊ฐ„์„ ์กฐ์ ˆํ•œ๋‹ค.

Saga

class Saga {
  store: Store | null = null;

  constructor(store: Store) {		
    this.store = store;
  }	

  async run(action: (object: object) => Action, stories: RunStory[]) {
    try {
      const asyncResult = await Promise.all(
        stories.map(async (story) => {  return await story();  })
      );
      const newResult = asyncResult.reduce((originObject, newObject) => {
        return { ...originObject, ...newObject };
      }, {});

      this.store?.dispatch(action(newResult));
    } catch (e) {
      console.log(`Saga error: ${e}`);
      throw "some story is problem";
    }
  }
}
const saga = new Saga(store);
export default saga;
  • contructor
    1. store๋ฅผ ๋ฐ›์•„์„œ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
  • run
    1. action๊ณผ ์Šคํ† ๋ฆฌ๋“ค์„ ๋ฐ›์•„์„œ promise.all์„ ํ†ตํ•ด ๋ชจ๋“  ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ฆฌํ„ด ๊ฐ’์„ spreadํ•ด์„œ action์— ์ „๋‹ฌํ•œ๋‹ค.

๋ฌธ์ œ์ 

  1. ์šฐ์„  story์˜ ๋ฆฌํ„ด๊ฐ’์— key๊ฐ€ ๊ฐ™์œผ๋ฉด ๊ฒน์ณ์ง„๋‹ค.
  2. ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•˜๋‹ค. ๋ณด๋ฉด action๋„ ๊ทธ๋ƒฅ ๋ฐ›๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์•ก์…˜์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ›์•„์•ผํ•˜๋Š”๊ฒƒ๋„ ์ด์ƒํ•˜๊ณ  ๋ถˆํŽธํ•œ๊ฒƒ ๊ฐ™๋‹ค.

๋„ค์ด๋ฐ
Saga๋ผ๋Š”๊ฒƒ์€ redux-saga์—์„œ ๋”ฐ์˜จ ๋„ค์ด๋ฐ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด Story? saga๋Š” ๋ฌด์šฉ๋‹ด, ์‹ ํ™”์™€ ๊ฐ™์€ ์„œ์‚ฌ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ณ , ๊ทธ์•ˆ์—๋Š” story๋“ค๊ณผ action์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ์œ„์™€๊ฐ™์ด ๋„ค์ด๋ฐ์„ ์ง€์—ˆ๋‹ค.(์ฐธ๊ณ ๋กœ ๋‚ด๊ฐ€ ๋งŒ๋“  ๋„ค์ด๋ฐ์ค‘ ๊ฐ€์žฅ ๋ง˜์— ๋“ ๋‹ค.)(์•„๋ž˜์—์„œ ๋‚˜์˜ค๊ฒ ์ง€๋งŒ ์‹ค์ œ๋กœ ์ € saga๋ฅผ ์‹คํ–‰ํ•˜๋Š”๊ฒƒ๋„ teller๋ผ๊ณ  ์ด๋ฆ„์„ ์ง€์—ˆ๋‹ค ใ…Ž)

custom hook

type SagaStatus = "isLoading" | "isSuccess" | "isError";
  
const useSaga = () => {
	const [sagaStatus, setSagaStatus] = useState<SagaStatus>("isLoading");
	const teller = async (
	action: (object: object) => Action, 
	stories: RunStory[]
	) => {
		setSagaStatus("isLoading");
		try {
			await saga.run(action, stories);
			setSagaStatus("isSuccess");
		} catch (e) {
			console.log(`saga Error: ${e}`);
			setSagaStatus("isError");
		}
	}
	return [sagaStatus, teller] as const;
};

export default useSaga;

๊ฐ ์ „์ฒด์ ์ธ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์œ„ํ•ด status๋ฅผ ์ถ”๊ฐ€ํ•ด fetch ์ƒํƒœ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๊ณ , ๋Œ€๋ถ€๋ถ„ ์ค‘์š”ํ•œ ์‚ฌํ•ญ์€ saga์—์„œ ์‹คํ–‰๋œ๋‹ค.

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

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

const TestComponent = () => {
	const [state, store] = useWork(initTestState, countReducer);
	const [status, teller] = useSaga();
	
	useEffect(() => {
		setTimeout(() => {
			const testStory = createStory(HealthCheckStory, {});
			teller(action.init, [testStory]);
		}, 5000);
	}, []);
	
	let content = <p> is Loading... </p>;
	
	if (status == "isSuccess") {
		content = <p>{state.data}</p>;
	} 
	else if (status == "isError") {
		content = <p>"error"</p>;
	}
	return content;
};

์œ„์ฒ˜๋Ÿผ action๊ณผ ์Šคํ† ๋ฆฌ๋“ค์„ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค.

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

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