๊ฐœ๋ฐœ/FRONTEND

Easyfetch ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌํ˜„๊ธฐ

woogie0303 2024. 8. 23. 12:17

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋งŒ๋“  ๊ณ„๊ธฐ

Next App Router๋ฅผ ์ด์šฉํ•ด์„œ ์ฒ˜์Œ ํŒ€ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋Š”๋ฐ ๊ธฐ์ˆ ์Šคํƒ์— ๊ด€ํ•ด์„œ ํŒ€์›๋“ค๊ณผ ๋…ผ์˜๋ฅผ ํ•˜๋˜ ์ค‘ ์ด์ „ React๋กœ๋งŒ ์ž‘์—…์„ ํ–ˆ์„ ๋•Œ์™€ ๊ฐ™์ด Axios๋ฅผ ํ™œ์šฉํ•˜๋ ค ํ–ˆ๋Š”๋ฐ Next App Router์—์„œ ์ ์šฉํ•˜๋ ค๊ณ  ๋ณด๋‹ˆ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋‹ค. ๋ฐ”๋กœ Next App Router๋Š” ์ž์ฒด์ ์œผ๋กœ fetch๋ฅผ ํ™•์žฅ์—์„œ ์“ฐ๊ณ  ์žˆ๋Š”๋ฐ ๊ทธ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const data = await fetch('~~', {
    next: {revalidate: 300}
})

์œ„์™€ ๊ฐ™์€ Next ์บ์‹ฑ์›๋ฆฌ๊ฐ€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ์ด์ „์— ์“ด ๊ธ€์ธ ์บ์‹ฑ์›๋ฆฌ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ์กฐํ•˜๋ฉด ์ข‹๋‹ค.

์ผ๋‹จ ๊ทธ๋•Œ๋Š” ํ”„๋กœ์ ํŠธ ๊ตฌํ˜„์ด ์šฐ์„ ์ด์˜€๊ธฐ์— ํŒ€์› ๋ชจ๋‘ fetch๋ฅผ ์“ฐ๊ธฐ๋กœ ํ–ˆ๋‹ค.
ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ fetch๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋‹ˆ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ถˆํŽธํ•จ์ด ์กด์žฌํ–ˆ๋‹ค.

  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ interceptor ๊ธฐ๋Šฅ์˜ ๋ถ€์žฌ
  • res.json(), JSON.stringify ์ง๋ ฌํ™” ์—ญ์ง๋ ฌํ™”๋ฅผ ๋งค๋ฒˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ
  • 400๋ฒˆ ์ด์ƒ์˜ ์—๋Ÿฌ๋Š” res.ok๋กœ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
  • fetch์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” axios.post์™€ ๊ฐ™์€ API ๋ฉ”์„œ๋“œ

interceptor ๊ธฐ๋Šฅ์„ ์ œ์™ธํ•˜๊ณ  ์œ„์™€ ๊ฐ™์€ ๋ถˆํŽธํ•œ ์‚ฌํ•ญ๋“ค์€ ์ถฉ๋ถ„ํžˆ ์ฝ”๋“œ์ƒ์—์„œ ํ•จ์ˆ˜๋กœ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ์ผ๋‹จ์€ ์œ„์™€ ๊ฐ™์€ ๋ถˆํŽธํ•จ์€ ๋’ค๋กœ ํ•œ ์ฑ„ ํŒ€ ํ”„๋กœ์ ํŠธ๋ฅผ ์™„์„ฑํ•œ ๋’ค ๊ฐ์ž ํŒ€์›๋“ค๋ผ๋ฆฌ ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๊ฐ„์„ ํ•œ๋‹ฌ ๊ฐ–๊ธฐ๋กœ ํ•˜๊ณ  ๋‹ค์‹œ ์ถ”๊ฐ€๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

ํŒ€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋‚˜ ๋˜๋Š” ํŒ€์›๋“ค์˜ ๋„์›€์„ ๋ฐ›์œผ๋ฉด์„œ ๊ฐ€์žฅ ๋ฉ‹์žˆ๊ณ  ์ฝ”๋”ฉ์— ๋งค๋ ฅ์„ ๋Š๋‚€์ ์ด ๋”ฑ ํ•œ๊ฐ€์ง€ ์žˆ๋‹ค. ๋‚ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง€๋Š” ๊ฒƒ์ด๋‹ค. ๋ˆ„๊ตฐ๊ฐ€ ๋˜๋Š” ๋‚ด ์ž์‹ ์ด ๋ณต์žกํ•œ ๋กœ์ง์—์„œ ๋ณด๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ๊ฐ€์žฅ ์ž๊ทน์„ ๋ฐ›๊ณ  ์ฝ”๋”ฉ์˜ ๋งค๋ ฅ์„ ๋Š๋ผ๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ์ฆ‰ ๋ถˆํŽธ์„ ํ•ด์†Œํ–ˆ์„๋•Œ ๋ผ๊ณ ๋„ ๋งํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฒˆ ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๊ฐ„ ํ•œ๋‹ฌ๋™์•ˆ ์šฐ๋ฆฌํŒ€์˜ ๋ถˆํŽธ์„ ํ•ด์†Œํ•˜๊ณ  ํ•œ๋‹จ๊ณ„ ์„ฑ์žฅํ•˜๊ณ  ์‹ถ์–ด์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ๊ฒฐ์‹ฌํ–ˆ๋‹ค.

์ž๋ฃŒ์กฐ์‚ฌ

์ผ๋‹จ ๋‚ด๊ฐ€ ๋ญ˜ ๋งŒ๋“ค๊ณ  ์‹ถ์€์ง€ ๋ช…ํ™•ํžˆ ์ •ํ•ด์•ผ ํ–ˆ๋‹ค.

Next App Router์—์„œ Axios๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š” Fetch ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋งŒ๋“ค๊ธฐ

๋ช…ํ™•ํžˆ ์ •ํ–ˆ์œผ๋‹ˆ ์ด์™€ ๋น„์Šทํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ˆ„๊ฐ€ ๋งŒ๋“ค์—ˆ๋Š”์ง€ ์ฐพ์•„๋ดค๋‹ค.
2๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ ํ•˜๋‚˜๋Š” ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” Axios๊ณ  ๋‚˜๋จธ์ง€ ํ•˜๋‚˜๋Š” ๋ฐ”๋กœ return-fetch์ด๋‹ค. ์†”์งํžˆ return-fetch๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด๋ฉด์„œ axios.get, axios.post์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ์™ธํ•˜๋ฉด ๋‚ด๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜ ๋ฌธ์ œ๋“ค์ด ํ•ด๊ฒฐ๋  ์ˆ˜ ์žˆ์—ˆ๊ณ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋ ค๋ฉด ์ด์ •๋„๊นŒ์ง€ ๊ตฌํ˜„์„ ํ•ด์•ผํ•˜๊ตฌ๋‚˜ ํ•˜๋ฉด์„œ ๋Œ€๋‹จํ•˜์‹  ๋ถ„๋“ค์€ ์ •๋ง ๋งŽ๋‹ค๋Š”๊ฒƒ์„ ์ƒ๊ฐํ•˜๋ฉฐ ์‚ด์ง ๋ฒฝ์„ ๋Š๋ผ๊ธฐ๋„ ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ๋ฐ์„œ๋ถ€ํ„ฐ ๋ง˜ ์•ฝํ•ด์ง€๋ฉด ๋‚˜์˜ ์„ฑ์žฅ์€ ๋๋‚ ๊ฒƒ ๊ฐ™์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์‹œ ์ •์‹ ์„ ์ฐจ๋ ธ๋‹ค!!! ์ด๋ฏธ ๋ˆ„๊ฐ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋„ ๊ฑฐ๊ธฐ์—์„œ ๋„์›€์„ ๋ฐ›์•„ ๋˜ ๋‚˜๋งŒ์˜ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ๊ทธ๊ฒƒ ๋˜ํ•œ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์•„์„œ ๋ณธ๊ฒฉ์ ์œผ๋กœ Axios, return-fetch, fetch, promise๋ฅผ ๊ณต๋ถ€ํ•ด ๋‚˜๊ฐ”๋‹ค.

๊ณผ์ •

์ด๋ฒˆ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋Š” Class๋ฅผ ์ด์šฉํ–ˆ๋‹ค.๊ทธ ์ด์œ ๋Š” ์ฝ”๋“œ๋ฅผ ํ•จ์ˆ˜๋ฅผ ๊ตฌ์„ฑํ–ˆ์„ ๋•Œ ๋ณด๋‹ค ์ฝ”๋“œ์˜ ํ๋ฆ„์ด ์ง๊ด€์ ์ผ ๊ฒƒ ๊ฐ™์•„์„œ ์„ ํƒ์„ ํ–ˆ๋‹ค.

์–ด๋–ค ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ•ด๋‹น ๊ธฐ๋Šฅ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋ ค๊ณ ํ–ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด๋ฆ„์€ easyFetch์ด๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

const easy = easyFetch({
    baseUrl: 'https://attraction/'
      headers: {}
})

easy.get('api/v1/user')
easy.post('api/v1/user')

easyFetch๋ผ๋Š” ํ•จ์ˆ˜๋Š” default ์„ค์ •์„ ์ธ์ž๋กœ ๋ฐ›์•„ ์ธ์Šคํ„ด์Šค์—๊ฒŒ ์ „๋‹ฌํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

axios.get๊ณผ ๊ฐ™์€ API๋ฉ”์„œ๋“œ ๊ตฌํ˜„๊ธฐ

๋‚˜๋Š” ์†”์งํžˆ ์ด ๋ถ€๋ถ„์—์„œ๋Š” ๋ณ„๊ฑฐ ์—†์„ ๊ฒƒ ๊ฐ™์•˜๋Š”๋ฐ ๋˜๊ฒŒ ๊ดœ์ฐฎ์€ ๊ตฌํ˜„๋ฐฉ๋ฒ•์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋œ๋‹ค. ์›๋ž˜ ๊ธฐ๋ณธ ์ƒ๊ฐ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค.

class EasyFetch{
  #baseUrl: string| URL | undefined;
  #headers: HeadersInit | undefined;
    constructor() {
        this.#baseUrl = defaultConfig?.baseUrl;
        this.#headers = defaultConfig?.headers;
    }

    get() {}
    delete() {}
    post() {}
    patch() {}
    put() {}
}

ํ•˜์ง€๋งŒ Axios ์˜คํ”ˆ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณธ ๊ฒฐ๊ณผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ผ๋‹จ ์„ค๋ช…ํ•˜๊ธฐ์ „์— ์˜คํ”ˆ์†Œ์Šค์ฝ”๋“œ ๋ถ„์„์ด ์ฒ˜์Œ์ธ ๋‚˜์—๊ฒ ๋ˆ„๊ตฐ๊ฐ€์˜ ์ฝ”๋“œ๋ฅผ ์ฝ๋Š”๋‹ค๋Š” ๊ฒƒ์ด ์‰ฝ์ง€ ์•Š์•˜๋Š”๋ฐ ์ด๋ฒˆ ๊ธฐํšŒ๋ฅผ ํ†ตํ•ด ์˜คํ”ˆ์†Œ์Šค ์ฝ”๋“œ๋“ค์„ ๋ถ„์„ํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ๊ฒƒ์ด ์‰ฌ์›Œ์กŒ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ ‘ํ•˜๋ฉด์„œ ๋ฐฉ๊ตฌ์„์—์„œ๋งŒ ์ฝ”๋”ฉ์„ ํ•ด๋„ ์ฝ”๋“œ๋ฅผ ์ƒ๊ฐํ•˜๋Š” ์‹œ์•ผ๊ฐ€ ๋„“์–ด์กŒ๋‹ค.

๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด axios.get ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋“ค์€ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๋กœ ์ง์ ‘ ๋ช…์‹œ๋˜๋Š”๊ฒƒ์ด ์•„๋‹Œ prototype์„ ์‚ฌ์šฉํ•ด์„œ ๋™์ ์œผ๋กœ ๋ฉ”์„œ๋“œ ์ฝ”๋“œ๋“ค์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๊ณ  ์žˆ๋‹ค.

ํ”„๋กœํ† ํƒ€์ž…์€ JS์—์„œ๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋“ค์€ ๋ถ€๋ชจ๊ฐ์ฒด์™€ ์—ฐ๊ฒฐ๋˜์–ด์ ธ์žˆ๋Š”๋ฐ ๊ฐ์ฒด ์ง€ํ–ฅ์—์„œ์˜ ์ƒ์†๊ณผ ๊ฐ™์ด ๋ถ€๋ชจ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ž์‹๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ์˜ ๋ถ€๋ชจ๊ฐ์ฒด๋ฅผ ํ”„๋กœํ† ํƒ€์ž…์ด๋ผ๊ณ  ํ•œ๋‹ค. ๊ทธ๋ฆผ์œผ๋กœ ๋ณด๋ฉด ๋” ์‰ฝ๋‹ค.

const easy = new EasyFetch()

easy๋ผ๋Š” ๊ฐ์ฒด์— console์„ ์ฐ์–ด๋ณด๋ฉด Protoype์ด๋ผ๋Š” ์Šฌ๋กฏ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ์Šฌ๋กฏ์ด ์ƒ์œ„์˜ ๋ถ€๋ชจ ํ”„๋กœํ† ํƒ€์ž…์„ ์—ฐ๊ฒฐ์‹œ์ผœ์ฃผ๊ณ  ์ด๊ฒƒ์„ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹์ด๋ผ๊ณ  ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฐœ๋…์„ ์•Œ์•„๋‘๊ณ  Axios์ฝ”๋“œ๋กœ ๋Œ์•„๊ฐ€๋ณด๋ฉด ๊ทธ๋ฆผ๊ณผ ๋˜‘๊ฐ™์€ ๋ฌธ๋ฒ•์„ ํ•˜๋‚˜ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ”๋กœ Easyfetch.prototype์ด๋‹ค. ์ด๊ฒƒ์„ ํ™œ์šฉํ•ด์„œ ํ”„๋กœํ† ํƒ€์ž…์˜ ๋ฉ”์„œ๋“œ๋“ค์„ ํ™•์žฅํ•ด์ฃผ๋ฉด ๊ตณ์ด class์—์„œ ๋ฉ”์„œ๋“œ๋“ค์„ ์ •์˜๋ฅผ ์•ˆํ•ด๋„ easy๋ผ๋Š” ๊ฐ์ฒด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด easy ๊ฐ์ฒด Prototype ์Šฌ๋กฏ์— EasyFetch.Prototype์ด ๋ฐ”์ธ๋”ฉ์ด ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๊ธฐ๋ณธ์ด ์ค‘์š”ํ•˜๋‹ค๋Š”๊ฒƒ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ผˆ์ €๋ฆฌ๊ฒŒ ๋Š๋‚€๋‹ค

์ ์šฉ

๋ฐ”๋กœ ์ ์šฉ์„ ํ•ด๋ดค๋”๋‹ˆ ๊ทธ๋Ÿผ ๊ทธ๋ ‡์ง€ ์ˆœํƒ„์น˜ ์•Š์„์ค„ ์•Œ์•˜๋‹ค. ๋ฐ”๋กœ path,put,post,get,deleteํ”„๋กœํผํ‹ฐ๋Š” EasyFetch ํƒ€์ž…(์ •ํ™•ํžˆ ๋งํ•˜๋ฉด EasyFetch ์ธ์Šคํ„ด์Šค ํƒ€์ž…)์˜ ํ• ๋‹นํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์–ด์ฐŒ๋ณด๋ฉด ๋‹น์—ฐํ•œ๊ฒƒ์ด ts์—์„œ๋Š” class ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด EasyFetch ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค ํƒ€์ž…์€ constructor์˜ ์ •์˜๋œ ํ”„๋กœํผํ‹ฐ์™€ static์ด ์•„๋‹Œ class ๋ฉ”์„œ๋“œ๋“ค๋กœ ์ด๋ฃจ์–ด์ง„ ์ธ๋ฑ์Šค ํƒ€์ž…์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ˜„์žฌ ๋‚˜๋Š” ์ง์ ‘์ ์œผ๋กœ path,put,post,get,delete๋ฅผ ํด๋ž˜์Šค ๋‚ด๋ถ€์— ๋ฉ”์„œ๋“œ๋กœ ์„ค์ •ํ•˜๊ณ  ์žˆ์ง€ ์•Š์•„์„œ ์œ„์™€๊ฐ™์€ ํƒ€์ž…์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๊ฒƒ ์ด๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž… ๋‹จ์–ธ๊ณผ ์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐ์„ ํ–ˆ๋‹ค.

const METHOD_WITHOUT_BODY = ['get', 'delete'] as const;
const METHOD_WITH_BODY = ['patch', 'put', 'post'] as const;

type MethodWithBodyType = (typeof METHOD_WITH_BODY)[number];
type MethodWithoutBodyType = (typeof METHOD_WITHOUT_BODY)[number];

type MethodType = MethodWithBodyType | MethodWithoutBodyType;

type EasyFetchWithAPIMethodsType = EasyFetch & {
  [K in MethodType]: ???;
};

์ผ๋‹จ ๊ฐ€์žฅ ๋จผ์ € ํžŒํŠธ๋ฅผ ์–ป์€๊ฒƒ์ด EasyFetch๋Š” ์—๋Ÿฌ๊ตฌ๋ฌธ์—๋„ ๋‚˜์™€์žˆ๋“ฏ์ด ์ธ๋ฑ์Šค ํƒ€์ž…์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. key์™€ value๋กœ ์ด๋ฃจ์–ด์ง„ ํƒ€์ž…์ด๋‹ˆ ๊ทธ์™€ ๋™์ผํ•˜๊ฒŒ ์ธ๋ฑ์Šค ํƒ€์ž…์„ ์ƒ์„ฑํ•˜๊ณ  ์ธํ„ฐ์„น์…˜ํƒ€์ž…์œผ๋กœ EasyFetchWithAPIMethodsType๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌผ์Œํ‘œ ๋ถ€๋ถ„์—์„œ ์ƒ๋‹นํ•œ ๊ณ ๋ฏผ์„ ํ–ˆ๋‹ค. get,delete๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์ธ์ž์— body๊ฐ€ ํ•„์š”์—†์ง€๋งŒ patch,put,post๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” body๊ฐ€ ์žˆ์–ด์•ผํ•˜๋Š” ํ•จ์ˆ˜ ํƒ€์ž…์„ ์ •์˜ํ•ด์•ผํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ ํ•ด๊ฒฐ๋ฒ•์ด extend ํ‚ค์›Œ๋“œ๋ฅผ ํ™œ์šฉ์ด์˜€๋‹ค.

extend ํ‚ค์›Œ๋“œ๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ œํ•œ, ํƒ€์ž… ์ƒ์†, ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ทธ์ค‘ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์˜ ๊ธฐ๋Šฅ์„ extend ํ‚ค์›Œ๋“œ๋กœ ํ™œ์šฉํ–ˆ๋‹ค.

type MethodFunction<T> = T extends MethodWithBodyType
  ? <P>(
      url: string | URL,
      reqBody?: object,
      reqConfig?: Omit<RequestInitWithNextConfig, 'method' | 'body'>
    ) => Promise<EasyFetchResponse<P>>
  : <P>(
      url: string | URL,
      reqConfig?: Omit<RequestInitWithNextConfig, 'method'>
    ) => Promise<EasyFetchResponse<P>>;

type EasyFetchWithAPIMethodsType = EasyFetch & {
  [K in MethodType]: MethodFunction<K>;
};

export { EasyFetchWithAPIMethodsType, MethodType, MethodWithoutBodyType };

EasyFetchWithAPIMethodsType์—์„œ ์ธ๋ฑ์Šค ํƒ€์ž…์ธ K๋Š” path,put,post,get,delete๋“ค์ด๋ฏ€๋กœ ์ด๊ฒƒ์„ MethodFunction์ด๋ผ๋Š” ํƒ€์ž…์˜ ์ œ๋„ค๋ฆญ์œผ๋กœ ์ „๋‹ฌํ•ด์ฃผ๊ณ  extend๋ฅผ ํ™œ์šฉํ•ด ๋ถ„๊ธฐ์ฒ˜๋ฆฌํ•ด์„œ ํ•จ์ˆ˜ ํƒ€์ž…์„ ์ •์˜ ํ•ด์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ์œ„์™€๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค.

์œ„์˜ ํ•จ์ˆ˜ ํƒ€์ž…๋“ค์„ ๋ณด๋ฉด RequestInitWithNextConfig์—์„œ method๋ฅผ omitํ•ด์ฃผ๊ณ  ์žˆ๋Š”๋ฐ ์ด๊ฒƒ์˜ ์ด์œ ๋Š” axios๋ฅผ ์ด๊ฒƒ์ €๊ฒƒ ๋งŒ์ ธ๋ณด๋‹ค๊ฐ€ axios.get()์˜ config์—์„œ method๋ฅผ ๋˜ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜(?)๋ฅผ ๋ฐœ๊ฒฌํ•ด์„œ ํ•ด๋‹น์‚ฌํ•ญ์€ ํ˜ผ๋™์„ ์ค„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์„œ ๋‚ด๊ฐ€ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํƒ€์ž…์„ ์ •ํ™•ํžˆ ๋ช…์‹œํ•˜๋ ค๊ณ  Omit์„ ์‚ฌ์šฉํ•œ ๊ฒƒ ์ด๋‹ค.

// RequestInitWithNextConfig.type.ts
interface NextFetchRequestConfig {
  revalidate?: number | false;
  tags?: string[];
}

interface RequestInitWithNextConfig extends globalThis.RequestInit {
  next?: NextFetchRequestConfig | undefined;
}

Interceptor ๊ตฌํ˜„๊ธฐ

axios์˜ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ๋“ค์—ฌ๋‹ค ๋ณด๋ฉด promise ๋Œ€ํ•œ ์ดํ•ด๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค. ์ธํ„ฐ์…‰ํ„ฐ์˜ ํ•ต์‹ฌ์ ์ธ ๊ธฐ๋Šฅ๋งŒ ์ฝ”๋“œ๋กœ ๊ฐ€์ ธ์™€ ๋ถ„์„ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

axios.interceptor.request.use(fulfilled1, rejected1)
axios.interceptor.request.use(fulfilled2, rejected2)

Axios์—์„œ ์ธํ„ฐ์…‰ํ„ฐ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋‹ค๋ณด๋ฉด fullfilled, rejectedํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ดํ•˜๋Š”๋ฐ ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋“ค์„ ๋ฐฐ์—ด๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ interceptors request์˜ ๋ฐฐ์—ด์„ forEach๋กœ ๋Œ๋ฆฌ๊ณ  ์žˆ๋Š”๋ฐ ์ด๋Ÿฐ request ๋ฐฐ์—ด์˜ ๋ชจ์Šต์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์œ ์ถ”ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

// ์„ค๋ช…์„ ์œ„ํ•ด ๊ฐ„๋žตํ•˜๊ฒŒ ์ถ”๋ฆฐ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ๋Š” runWhen, synchronous ํ”„๋กœํผํ‹ฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
const request = [{fullfiled: fullfiled1, rejected: rejected1}, {fulfilled: fulfilled2, rejected: rejected2}]

์œ„์™€ ๊ฐ™์ด ๋Œ€๋žต์ ์ธ ๋ชจ์Šต์„ ๋จธ๋ฆฟ์†์— ๋„ฃ์–ด๋‘๊ณ  ๋นจ๊ฐ„ ๋„ค๋ชจ๋ฐ•์Šค์˜ ์ฝ”๋“œ๋“ค์„ ์‚ดํŽด๋ณด๋ฉด ์ดํ•ดํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด requestInterceptorChain์˜ ๋ชจ์Šต์€ ์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™๋‹ค.

// unshift ์ด๋ฏ€๋กœ
const requestInterceptorChain = [fulfilled2, rejected2, fullfiled1, rejected1]

responseInterceptorChain๋„ requestInterceptorChain์˜ ๋กœ์ง์ด๋ž‘ ๋น„์Šทํ•˜๊ณ  ๋‹ค๋ฅธ์ ์€ unshift ์•„๋‹Œ push๋ฅผ ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

๊ทธ ๋‹ค์Œ ๋ถ€๋ถ„์„ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์ขŒ์ธก์€ requestInterceptor๊ฐ€ ๋น„๋™๊ธฐ ์ผ๋•Œ์˜ ์ฝ”๋“œ์ด๊ณ  ์šฐ์ธก์€ requestInterceptor ์ฝ”๋“œ๊ฐ€ ๋™๊ธฐ์ผ๋•Œ์ด๋‹ค.

๋น„๋™๊ธฐ์ผ๋•Œ์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด chain์ด๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์•„๊นŒ ์ƒ์„ฑํ•œ request์™€ response์˜ ์ฒด์ธ ๋ฐฐ์—ด๋“ค์„ ํ•œ๊ตฐ๋ฐ๋กœ ํ•ฉ์ณ์ค€ ๋‹ค์Œ์— while๋ฌธ์„ ๋Œ๋ฉด์„œ then๋ฉ”์„œ๋“œ ์•ˆ์— ์ธ์ž๋กœ ๋„ฃ๊ณ  ์žˆ๋‹ค. while๋ฌธ์„ ๋‹ค ๋Œ์•˜์„ ๋•Œ์˜ ์ฝ”๋“œ๋ฅผ ์ƒ์ƒํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

// config๋Š” ์šฐ๋ฆฌ๊ฐ€ ์š”์ฒญ์„ ํ•  ๋•Œ ์„ค์ •ํ•œ config๋‹ค
Promise.resolve(config)
  .then(requestFulfilled2, requestRejected2)
  .then(requestFulfilled1, requestRejected1)
  .then(dispatchRequest.bind(this), undefined)
  .then(responseFullFilled1, responseRejected1)
  .then(responseFullFilled2, responseRejected2)

์ด๋•Œ์˜ dispatchRequest return๊ฐ’์€ fetch์˜ ์‘๋‹ต๊ฐ’์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํŽธํ•˜๋‹ค.

๋™๊ธฐ ์ผ๋•Œ๋Š” request interceptor๋ฅผ while๋ฌธ์—์„œ ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰์‹œํ‚จ ๋‹ค์Œ fetch๋ฅผ ํ•ด์ฃผ๋Š” dispatchRequestํ•จ์ˆ˜์— ์ „๋‹ฌ ํ•ด์ฃผ๋Š”๊ฒƒ๋งŒ ์ฐจ์ด์ ์ด๊ณ  ๋‚˜๋จธ์ง€ response interceptor๋กœ์ง์€ ์œ„์˜ ์„ค๋ช…ํ•œ ๋ฐ”์™€ ๊ฐ™๋‹ค.

์ ์šฉ

์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ๋ณด๋ฉด์„œ ๊ธฐ๋ณธ์ด ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋‹ค์‹œ ๋Š๊ผˆ๋‹ค. ํ•˜์ง€๋งŒ ์œ„์™€ ๊ฐ™์ด ๊ทธ๋Œ€๋กœ ๋กœ์ง์„ TS์—์„œ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๊ฐ€ ๋‚  ๊ฒƒ ๊ฐ™์€ ๋ถ€๋ถ„์„ ๋ฐ”๋กœ ์บ์น˜ํ–ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์‹œ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

let promiseChain = Promise.resolve(config) as Promise<RequestInit>;

promiseChain = promiseChain
  .then((res) => {
    return res;
  })
  .then(async (config) => {
    const sample = await fetch('http://attraction', config);
    const data = await sample.json() as { data: string };
    return data;
  })
  .then(res => res);

์ฒซ๋ฒˆ์งธ then์€ requestInterceptor์ด๊ณ  ๋‘๋ฒˆ์งธ then์€ requestInterceptor์˜ ์˜ํ•ด์„œ ์ ์šฉ๋œ config๋ฅผ fetchํ•œํ…Œ ์ „๋‹ฌํ•ด์ฃผ๊ณ  responseInterceptor๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‘๋‹ต๊ฐ’์„ ๋ฆฌํ„ด์„ ํ•ด์ฃผ๋Š” ๋กœ์ง์ด๊ณ  ๋งˆ์ง€๋ง‰ then์€ responseInterceptor์˜ fulfilled ํ•จ์ˆ˜์ด๋‹ค.
์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ์•„๊นŒ Axios๋ž‘ ๋˜‘๊ฐ™์€ ๋กœ์ง์ธ๋ฐ ํƒ€์ž…์—๋Ÿฌ๊ฐ€ ๋‚œ ์ด์œ ๋Š” promiseChain์˜ ์ดˆ๊ธฐ ํƒ€์ž…์€ Promise์ด๋‹ค. ์ฆ‰, ๋‹ค์‹œ ์žฌํ• ๋‹น์„ ํ•ด๋„ Promise์„ ๋ฐ˜ํ™˜ํ•ด์•ผํ•˜๋Š”๋ฐ while๋ฌธ์— ์˜ํ•ด์„œ ํ•œ๋ฒˆ์— request interceptor์™€ response interceptor๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ ํ•˜๋ฉด ํƒ€์ž… ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Request Interceptor์™€ Response Interceptor๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

// ์‹ค์ œ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ ์ผ๋ถ€ ๋ฐœ์ทŒ

interface InterceptorCallbackType<T> {
  (
    onFulfilled?: (arg: T) => T | Promise<T>,
    onRejected?: (err: any) => any
  ): void;
}

type InterceptorArgs<T> = Parameters<InterceptorCallbackType<T>>;


class Interceptor {

  flushInterceptors<T>(
    initVal: Promise<T>,
    interceptors: InterceptorArgs<T>[]
  ) {
    let promiseInit = initVal;

    for (let i = 0; i < interceptors.length; i++) {
      promiseInit = promiseInit.then(...interceptors[i]);
    }

    return promiseInit;
  }

  flushRequestInterceptors(initVal: Promise<EasyFetchRequestType>) {
    return this.flushInterceptors(initVal, this.requestCbArr);
  }

  flushResponseInterceptors(initVal: Promise<EasyFetchResponse<any>>) {
    return this.flushInterceptors(initVal, this.responseCbArr);
  }
}

export default Interceptor;

flushRequestInterceptors์™€ flushResponseInterceptors์˜ ์ธ์ž์— let์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์ „๋‹ฌํ•จ์œผ๋กœ์จ flushInterceptors ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด๋ฉด prmiseInit์œผ๋กœ ํ• ๋‹นํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋•Œ for๋ฌธ์„ ๋Œ๋ฉด์„œ promiseInit์— then์„ ํ• ๋‹นํ•ด๋„ interceptors์˜ ํƒ€์ž…์ธ InterceptorArgs์˜ ์ œ๋„ค๋ฆญ์œผ๋กœ ์ดˆ๊ธฐ๊ฐ’์˜ ํƒ€์ž…์„ ์ „๋‹ฌํ•ด์ฃผ๋ฉด์„œ ํƒ€์ž…์„ ์ผ์น˜์‹œํ‚ค๊ณ  ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํ”Œ๋กœ์šฐ ์ฐจํŠธ

๋‚ด๊ฐ€ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋Œ€ํ‘œ์ ์ธ ๊ธฐ๋Šฅ์„ ๋ธ”๋กœ๊ทธ๋ฅผ ์ •๋ฆฌํ–ˆ๋Š”๋ฐ ๋‚˜๋จธ์ง€ ๊ธฐ๋Šฅ๋“ค๋„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ตฌํ˜„ํ•˜๋ฉด์„œ ๋Š๋‚€์ 

์ด๋ฒˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์˜คํ”ˆ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ๋ถ„์„ํ•˜๋ฉด์„œ ์ „๋ณด๋‹ค ์ฝ”๋“œ๋ฅผ ํ•ด์„ํ•˜๋Š” ๋Šฅ๋ ฅ์ด ํ–ฅ์ƒ๋˜์—ˆ๊ณ , Promise์™€ then์˜ ํ™œ์šฉ๋ฒ•๋„ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. JavaScript ์ฝ”๋“œ๋ฅผ TypeScript๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ œ๋„ค๋ฆญ ํƒ€์ž…, ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…, extends ํ‚ค์›Œ๋“œ ๋“ฑ์„ ํ™œ์šฉํ•˜๋ฉด์„œ, TypeScript์— ๋Œ€ํ•œ ์ˆ™๋ จ๋„ ๋˜ํ•œ ํ•œ์ธต ๋” ์„ฑ์žฅํ•œ ๋Š๋‚Œ์ด์˜€๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ™œ์šฉํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ๋”ํ–ˆ๊ณ  Rollup์„ ์ด์šฉํ•ด esm, cjs์˜ ์ฐจ์ด๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋‘ ๋ชจ๋“ˆ์— ๋Œ€์‘ํ•˜๋„๋ก Rollup์„ ์„ค์ •ํ•˜๋ฉด์„œ ์ ์ฐจ ํ”„๋ก ํŠธ ๊ณต๋ถ€ ์˜์—ญ์„ ๋„“ํ˜€๊ฐ€๊ฒŒ๋˜๋Š” ๊ฒฝํ—˜์„ ํ–ˆ๋‹ค. ๋กค์—…์— ๊ด€ํ•œ ์„ค์ •์€ ๋‹ค์Œ ๋ธ”๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.