-
Notifications
You must be signed in to change notification settings - Fork 1
๐ Hyunduxโsaga: ์์ฒด ๋ฏธ๋ค์จ์ด ๋น๋๊ธฐ์ฒ๋ฆฌ
hyundux๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์ ์ฒด์ ์ธ ํ๋ฆ์ด ๋ฐฉํด๋์ง ์๋๋ก ๋ง๋ client๋ฅผ ์ํ ์ํ๊ด๋ฆฌ์ด๋ค. ํ์ง๋ง Server API๋ Local storage๊ฐ์ ์ธ๋ถํ๊ฒฝ์ ์ ๊ทผ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํด์ผํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ redux-saga์ ๋ฏธ๋ค์จ์ด์ ์ค๊ฐ์ ์ด๋ฒคํธ๋ฅผ interceptํด์ ์ฒ๋ฆฌํ๋ค๋ ์ ์ ์ฐฉ์ํ์๊ณ , react-query์ ๋ณ๋ ฌ์ API ๊ตฌ์ฑ์ ์ํฅ์ ๋ฐ์ ๊ตฌํํ์๋ค.
๊ทธ๋ฆผ ์์
- ๋ค์ํ ๋น๋๊ธฐ๋ก์ง์ ๋ณ๋ ฌ์ ๋๊ธฐ์ ์ํ : ์ด๋ค ๋น๋๊ธฐ๋ก์ง์ด ๋ค๋ฅธ ๋น๋๊ธฐ๋ก์ง์ ๋ถ๋ฌ์ chain์ ํ์ฑํ๊ณ ์ถ์ง ์์๋ค. ์ด๋ ํ ์คํธ๋ ์ด๋ ต๊ณ ์ด๋ค ์ฌ์ด๋์ดํํธ๊ฐ ๋ฐ์ํ๋์ง ์์ํ ์๊ฐ ์๋ค. ๊ทธ๋์ ๋ณ๋ ฌ์ ์ผ๋ก ๋ก์ง์ด ์คํ๋๊ณ ๊ทธ ๊ฐ๋ค์ spreadํ๋ ๋ฐฉ์์ ์ฑํํ์๋ค.
- ํ ์คํธ ์ฉ์ด : ๋คํธ์ํฌ๋ local stroage๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ ํ ์คํธ๊ฐ ํ๋ค๋ค๋ ์ ์ด ์๋ค. ํ์ง๋ง ๋น๋๊ธฐ ๋ก์ง๊ณผ action์ ๋ถ๋ฆฌํ๊ณ ๋ชจ๋ ๋น๋๊ธฐ๋ก์ง์ ๊ฒฐ๊ณผ๋ฅผ action์ ๋ฃ๊ณ ์ํ๊ด๋ฆฌ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ ๊ฐ๊ฐ ๋๋ ์ ํ ์คํธ๋ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์๋ค.
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);
};
}
- story : API์ 1๋1 ๋์๋๋ ํ๋์ story๋ก์จ ํจ์ํํ๋ก ์๋ํ๋ค.
- RunStory : Hyundux-saga ๋ด๋ถ์์๋ง ์ฌ์ฉ๋๋ ํ์ ์ผ๋ก์จ ๋ฆฌํด๊ฐ์ saga๋ด์์ ์คํ๋๋ค.
- createStory : component๋ story๋ฅผ ์ง์ ์คํํ์ง์๊ณ 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
- store๋ฅผ ๋ฐ์์ ์ด๊ธฐํํ๋ค.
- run
- action๊ณผ ์คํ ๋ฆฌ๋ค์ ๋ฐ์์ promise.all์ ํตํด ๋ชจ๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์คํํ๊ณ ๋ฆฌํด ๊ฐ์ spreadํด์ action์ ์ ๋ฌํ๋ค.
๋ฌธ์ ์
- ์ฐ์ story์ ๋ฆฌํด๊ฐ์ key๊ฐ ๊ฐ์ผ๋ฉด ๊ฒน์ณ์ง๋ค.
- ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ๋๋ฌด ๋ณต์กํ๋ค. ๋ณด๋ฉด action๋ ๊ทธ๋ฅ ๋ฐ๋๊ฒ ์๋๋ผ ์ก์ ์ ์์ฑํ๋ ํจ์๋ฅผ ๋ฐ์์ผํ๋๊ฒ๋ ์ด์ํ๊ณ ๋ถํธํ๊ฒ ๊ฐ๋ค.
๋ค์ด๋ฐ
Saga๋ผ๋๊ฒ์ redux-saga์์ ๋ฐ์จ ๋ค์ด๋ฐ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด Story? saga๋ ๋ฌด์ฉ๋ด, ์ ํ์ ๊ฐ์ ์์ฌ๋ผ๊ณ ์๊ฐํ๊ณ , ๊ทธ์์๋ story๋ค๊ณผ action์ด ์๋ค๊ณ ์๊ฐํด์ ์์๊ฐ์ด ๋ค์ด๋ฐ์ ์ง์๋ค.(์ฐธ๊ณ ๋ก ๋ด๊ฐ ๋ง๋ ๋ค์ด๋ฐ์ค ๊ฐ์ฅ ๋ง์ ๋ ๋ค.)(์๋์์ ๋์ค๊ฒ ์ง๋ง ์ค์ ๋ก ์ saga๋ฅผ ์คํํ๋๊ฒ๋ teller๋ผ๊ณ ์ด๋ฆ์ ์ง์๋ค ใ )
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๊ณผ ์คํ ๋ฆฌ๋ค์ ๋ณด๋ด๋ฉด ๋๋ค.
- spreadํ ๋ ๊ฐ์ ์ด๋ฆ์ ๋ณ์๋ฅผ ์จ์ผํ๊ณ ๋ชจ๋ ๊ฒ DTO์ ์ด๋ฆ์ด ๊ฐ์์ผํ๋๊ฒ ์กฐ๊ธ ๊ฑธ๋ฆฐ๋ค.
- ์ค์ผ๋งํผ ์ค์์ง๋ง ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๊ฐ ๋๋ฌด ๊ธด๊ฑฐ ๊ฐ๋ค..
- ์ปดํฌ๋ํธ ๋จ์๋ก state๋ฅผ ๊ด๋ฆฌํ๋ค๋ณด๋ ํด๋น state์ ๋ํ ์์กด์ฑ๋ฌธ์ ๊ฐ ์๊ธด๋ค... ์ด ์ ์ ๊ณ ์น๊ธฐ ์ํด ๋ง์ ์๊ฐ์ด ๋ค๊ฒ๊ฐ๋ค.
- store์ ๋ผ์ดํ์ฌ์ดํด์ ์ข ๋ ์ ๊ด๋ฆฌํ ์ ์๋๋ก ๋ง๋ค์ด์ผํ ๊ฒ ๊ฐ๋ค.