๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Lect & Tip/node, Angular, React

๋ฆฌ์•กํŠธ api axios ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ตฌํ˜„ react.JS filesaver.saveAs javascript blob response.headers ๊ฐ’์—์„œ filename์ถ”์ถœํ•˜๊ธฐ

by st๊ณต๊ฐ„ 2025. 2. 11.

๋ชฉ์ฐจ

    ๋ฆฌ์•กํŠธ api axios ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ตฌํ˜„ react.JS filesaver.saveAs javascript blob response.headers ๊ฐ’์—์„œ filename์ถ”์ถœํ•˜๊ธฐ

    ๋ฆฌ์•กํŠธ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์—‘์…€ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ API ํ˜ธ์ถœ ์‹œ Axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๋Š” ๊ณผ์ •์—์„œ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ œ์•ฝ๊ณผ ์ด์Šˆ๋ฅผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋ฆฌ์•กํŠธ ํ™˜๊ฒฝ์—์„œ Axios๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ํ•จ๊ป˜, response.headers์— ํฌํ•จ๋œ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•˜๋Š” ๊ผผ์ˆ˜์— ๋Œ€ํ•ด ์ž์„ธํžˆ ๋‹ค๋ฃจ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

    ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๊ตฌํ˜„ ๋ฐฐ๊ฒฝ

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

    ์‹ค์ œ ๊ตฌํ˜„ ์‹œ, Axios๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐฑ์—”๋“œ API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋กœ ๋ฐ›์•„์˜จ blob ๋ฐ์ดํ„ฐ๋ฅผ FileSaver ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ”๋กœ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ด ๊ณผ์ •์—์„œ ๋ฐฑ์—”๋“œ์—์„œ ์ „์†กํ•˜๋Š” HTTP ์‘๋‹ต ํ—ค๋”์— ํฌํ•จ๋œ ํŒŒ์ผ๋ช…์„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์ด๋Š” ๋ฆฌ์•กํŠธ์˜ response.headers ๊ฐ์ฒด๊ฐ€ Angular์™€ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ ์—์„œ ๊ธฐ์ธํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, HTTP ํ—ค๋”์˜ key ๊ฐ’์— ํ•˜์ดํ”ˆ(-)์ด ํฌํ•จ๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ, ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ๋Š” ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

    Axios๋ฅผ ์ด์šฉํ•œ API ํ˜ธ์ถœ๊ณผ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ

    ์šฐ์„ , ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Axios ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋ฐฑ์—”๋“œ API๋กœ๋ถ€ํ„ฐ ์—‘์…€ ํŒŒ์ผ์„ blob ํ˜•์‹์œผ๋กœ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ์—์„œ Axios๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ํ† ํฐ๊ณผ ๊ฐ™์€ ์ธ์ฆ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด Axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์‹œ ์ธํ„ฐ์…‰ํ„ฐ๋‚˜ ๊ธฐ๋ณธ ํ—ค๋”์— ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์•„๋ž˜๋Š” Axios๋ฅผ ํ™œ์šฉํ•˜์—ฌ API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ blob์„ ๋ฐ›์•„์˜ค๋Š” ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

    // axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์˜ˆ์‹œ (์ธ์ฆ ํ† ํฐ ํฌํ•จ)
    import axios from "axios";
    
    const authAxios = axios.create({
      baseURL: "/api/v1",
      headers: {
        "Content-Type": "application/json",
        // ํ† ํฐ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด Authorization ํ—ค๋” ์ถ”๊ฐ€
        // "Authorization": `Bearer ${token}`
      }
    });

    ์œ„์™€ ๊ฐ™์ด Axios ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•œ ํ›„, ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ API ํ˜ธ์ถœ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ responseType์„ "blob"์œผ๋กœ ์„ค์ •ํ•ด์•ผ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๋Œ€๋กœ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹œ FileSaver ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ

    ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์„ ๋ณด๋‹ค ์†์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด FileSaver ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. FileSaver๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ blob ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์— FileSaver๋ฅผ ์„ค์น˜ํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    yarn add file-saver

    ์„ค์น˜ ํ›„, ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ FileSaver๋ฅผ ์ž„ํฌํŠธํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    import FileSaver from "file-saver";

    ์ด์ œ FileSaver๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ›์•„์˜จ blob ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ์— ํŒŒ์ผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    react.JS์—์„œ๋Š” ๋ฆฌ์Šคํฐ์Šค ํ—ค๋” ๊ฐ’์—์„œ let filename = response.headers.content-disposition ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค.

    ์‘๋‹ต ํ—ค๋”์—์„œ ํŒŒ์ผ๋ช… ์ถ”์ถœ์˜ ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

    ๋Œ€๋ถ€๋ถ„์˜ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋Š” ํŒŒ์ผ์„ ์ „์†กํ•  ๋•Œ HTTP ์‘๋‹ต ํ—ค๋”์˜ content-disposition ํ•ญ๋ชฉ์— ํŒŒ์ผ๋ช…์„ ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ํ˜•ํƒœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    Content-Disposition: attachment; filename="sample.xlsx"

    Angular์™€ ๊ฐ™์ด ์ผ๋ถ€ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋Š” response.headers.get('content-disposition')์™€ ๊ฐ™์ด ๊ฐ„๋‹จํžˆ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฆฌ์•กํŠธ์—์„œ Axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์„ ๋ฐ›์„ ๊ฒฝ์šฐ, response.headers๋Š” ๋‹จ์ˆœํ•œ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์ œ๊ณต๋˜๋ฉฐ, ํ•˜์ดํ”ˆ(-)์ด ํฌํ•จ๋œ key ๊ฐ’์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๋ฐ ์ œ์•ฝ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ์‹œ๋„ํ•˜๋ฉด ์ œ๋Œ€๋กœ ๋œ ๊ฐ’์„ ์–ป๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

    let filename = response.headers.content-disposition; // Syntax Error ๋ฐœ์ƒ

    ๋˜๋Š”

    let filename = response.headers.get("content-disposition"); // null ๋ฐ˜ํ™˜

    ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, JSON.stringify()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ headers ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ ํ›„, ๋ฌธ์ž์—ด ๋‚ด์—์„œ "filename=" ๋ถ€๋ถ„์„ ์ฐพ์•„ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ๊ตฌํ˜„ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    let filename = JSON.stringify(res.headers)
      .split("filename=")[1]
      .split('",')[0];

    ์ด ๋ฐฉ์‹์€ headers ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ ํ›„, ํŠน์ • ํŒจํ„ด("filename=")์„ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌ(split)ํ•˜์—ฌ ํŒŒ์ผ๋ช…๋งŒ ์ถ”์ถœํ•˜๋Š” ๊ผผ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด ๋ฐฉ์‹์€ ๋‹ค์†Œ ์šฐํšŒ์ ์ธ ๋ฐฉ๋ฒ•์ผ ์ˆ˜ ์žˆ์œผ๋‚˜, ๋ฆฌ์•กํŠธ์—์„œ ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•œ ํšจ๊ณผ์ ์ธ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

    ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ตฌํ˜„ ์˜ˆ์ œ ์ฝ”๋“œ ์ƒ์„ธ ๋ถ„์„

    ๋‹ค์Œ์€ ์‹ค์ œ๋กœ ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ ๋™์ž‘ํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜์˜ ์ „์ฒด ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ๊ฒ€์ƒ‰ ์กฐ๊ฑด์— ๋”ฐ๋ผ API ํ˜ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , Axios๋กœ ์—‘์…€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ blob ํ˜•์‹์œผ๋กœ ๋ฐ›์•„์˜จ ํ›„, FileSaver๋ฅผ ์ด์šฉํ•˜์—ฌ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๋Š” ๋กœ์ง์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

    // ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜
    const handleDown = async (e) => {
      // ๊ฒ€์ƒ‰ ์กฐ๊ฑด ๋ฐ์ดํ„ฐ (์˜ˆ: ์บ ํŽ˜์ธ ์ œ๋ชฉ, ๋ฐœ์†ก ์‹œ์ž‘์ผ, ๋ฐœ์†ก ์ข…๋ฃŒ์ผ)
      const { cmpgn_title, send_start_dt, send_end_dt } = campaignSearchData;
    
      // ๋‹ค์šด๋กœ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ์ฒด ์ƒ์„ฑ
      let downXls = { send_start_dt: "", send_end_dt: "" };
      downXls.cmpgn_title = cmpgn_title;
    
      // ๋‚ ์งœ ํ˜•์‹ ๋ณ€ํ™˜: null ์ฒดํฌ ํ›„ YYYY-MM-DD ํฌ๋งท์œผ๋กœ ๋ณ€๊ฒฝ
      if (send_start_dt !== null) {
        downXls.send_start_dt = changeFormat(send_start_dt, "YYYY-MM-DD");
      }
      if (send_end_dt !== null) {
        downXls.send_end_dt = changeFormat(send_end_dt, "YYYY-MM-DD");
      }
    
      try {
        // API ํ˜ธ์ถœ: ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์š”์ฒญ, responseType์„ blob์œผ๋กœ ์„ค์ •
        const res = await authAxios.get(
          `/api/v1/campaign-report/download?send_start_dt=${downXls.send_start_dt}&send_end_dt=${downXls.send_end_dt}&cmpgn_title=${downXls.cmpgn_title}`,
          { responseType: "blob" }
        );
    
        console.log("์‘๋‹ต ํ—ค๋”:", res.headers);
    
        // ์‘๋‹ต ํ—ค๋”์—์„œ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•˜๋Š” ๋กœ์ง
        let filename = JSON.stringify(res.headers)
          .split("filename=")[1]
          .split('",')[0];
    
        // FileSaver๋ฅผ ์ด์šฉํ•˜์—ฌ blob ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅ
        FileSaver.saveAs(res.data, filename);
      } catch (error) {
        // ์—๋Ÿฌ ์ฒ˜๋ฆฌ: ํ•„์š”์— ๋”ฐ๋ผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ๋˜๋Š” ๋กœ๊น… ์ฒ˜๋ฆฌ
        console.error("์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:", error);
      }
    };

    ์œ„ ์ฝ”๋“œ์—์„œ ์ฃผ๋ชฉํ•  ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    1. ๊ฒ€์ƒ‰ ์กฐ๊ฑด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
      ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋˜๋Š” ๊ฒ€์ƒ‰ ์กฐ๊ฑด์— ๋”ฐ๋ผ API ํ˜ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋™์ ์œผ๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์บ ํŽ˜์ธ ์ œ๋ชฉ๊ณผ ๋ฐœ์†ก ๋‚ ์งœ(์‹œ์ž‘์ผ, ์ข…๋ฃŒ์ผ)๋ฅผ ๋ฐ›์•„์™€์„œ, ๋‚ ์งœ ํ˜•์‹์€ ๋ณ„๋„์˜ changeFormat ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด "YYYY-MM-DD" ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    2. Axios API ํ˜ธ์ถœ ์‹œ responseType ์„ค์ •
      ์—‘์…€ ํŒŒ์ผ๊ณผ ๊ฐ™์ด ์ด์ง„ ๋ฐ์ดํ„ฐ(blob)๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ์—๋Š” responseType: "blob"์„ ์„ค์ •ํ•˜์—ฌ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์˜ฌ๋ฐ”๋ฅธ ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. ์ด ์„ค์ •์ด ์—†์œผ๋ฉด ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊นจ์ง€๊ฑฐ๋‚˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    3. ์‘๋‹ต ํ—ค๋”์—์„œ ํŒŒ์ผ๋ช… ์ถ”์ถœ
      ์„œ๋ฒ„๊ฐ€ ์ „์†กํ•œ HTTP ์‘๋‹ต ํ—ค๋”์—๋Š” Content-Disposition ํ•ญ๋ชฉ์— ํŒŒ์ผ๋ช…์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฆฌ์•กํŠธ์˜ Axios ์‘๋‹ต ๊ฐ์ฒด์—์„œ๋Š” ํ•ด๋‹น ํ—ค๋”์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, JSON.stringify๋ฅผ ์ด์šฉํ•ด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ ๋ฌธ์ž์—ด ์กฐ์ž‘์„ ํ†ตํ•ด ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
    4. FileSaver๋ฅผ ํ†ตํ•œ ํŒŒ์ผ ์ €์žฅ
      ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•ด FileSaver.saveAs() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ blob ๋ฐ์ดํ„ฐ๋ฅผ, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ €์žฅํ•  ํŒŒ์ผ๋ช…์„ ์ „๋‹ฌํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    ํŒŒ์ผ๋ช… ์ถ”์ถœ ๋ฐฉ์‹์˜ ํ•œ๊ณ„์™€ ๋ณด์™„์ 

    ์•ž์„œ ์†Œ๊ฐœํ•œ ๋ฌธ์ž์—ด ์กฐ์ž‘ ๋ฐฉ์‹์„ ํ†ตํ•œ ํŒŒ์ผ๋ช… ์ถ”์ถœ์€ ๋ฆฌ์•กํŠธ ํ™˜๊ฒฝ์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•œ ์ž„์‹œ๋ฐฉํŽธ์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ณด์™„์ ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    1. ๋ฐฑ์—”๋“œ์—์„œ ํŒŒ์ผ๋ช… ์ „์†ก ๋ฐฉ์‹ ๊ฐœ์„ 
      ๋งŒ์•ฝ ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋ฐฑ์—”๋“œ์—์„œ ํŒŒ์ผ๋ช…์„ ๋ณ„๋„์˜ HTTP ํ—ค๋”(์˜ˆ: custom-header: filename)๋กœ ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ณด๋‹ค ์•ˆ์ •์ ์œผ๋กœ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    2. Axios ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ ํ™œ์šฉ
      Axios์˜ ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ์‘๋‹ต์— ๋Œ€ํ•ด ๊ณตํ†ต์ ์œผ๋กœ ํŒŒ์ผ๋ช… ์ถ”์ถœ ๋กœ์ง์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    3. ์ •๊ทœ ํ‘œํ˜„์‹ ์‚ฌ์šฉ
      ๋ฌธ์ž์—ด ๋ถ„๋ฆฌ(split) ๋ฐฉ์‹ ๋Œ€์‹  ์ •๊ทœ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜๋ฉด, ํŒŒ์ผ๋ช…์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด์—์„œ ๋ณด๋‹ค ์œ ์—ฐํ•˜๊ฒŒ ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.์ด ๋ฐฉ์‹์€ ํŒŒ์ผ๋ช…์ด ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ ํŒŒ์ผ๋ช…("download.xlsx")์„ ์‚ฌ์šฉํ•˜๊ณ , ์ •๊ทœ ํ‘œํ˜„์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
    4. const contentDisposition = res.headers["content-disposition"] || ""; const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; const matches = filenameRegex.exec(contentDisposition); const filename = matches != null && matches[1] ? matches[1].replace(/['"]/g, '') : "download.xlsx";

    ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ๋‚ด ๊ตฌํ˜„ ์˜ˆ์ œ

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

    import React, { useState } from "react";
    import axios from "axios";
    import FileSaver from "file-saver";
    
    const ExcelDownloadComponent = () => {
      const [campaignSearchData, setCampaignSearchData] = useState({
        cmpgn_title: "",
        send_start_dt: null,
        send_end_dt: null,
      });
    
      // ๋‚ ์งœ ํ˜•์‹์„ YYYY-MM-DD๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์˜ˆ์‹œ
      const changeFormat = (date, format) => {
        // ์‹ค์ œ ๊ตฌํ˜„ ์‹œ moment.js๋‚˜ date-fns์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ˜•์‹ ๋ณ€ํ™˜ ๊ฐ€๋Šฅ
        const d = new Date(date);
        const year = d.getFullYear();
        const month = ("0" + (d.getMonth() + 1)).slice(-2);
        const day = ("0" + d.getDate()).slice(-2);
        return `${year}-${month}-${day}`;
      };
    
      // Axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (์ธ์ฆ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ—ค๋” ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)
      const authAxios = axios.create({
        baseURL: "/api/v1",
        headers: {
          "Content-Type": "application/json",
          // "Authorization": `Bearer ${token}`,
        },
      });
    
      // ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜
      const handleDown = async (e) => {
        const { cmpgn_title, send_start_dt, send_end_dt } = campaignSearchData;
        let downXls = { send_start_dt: "", send_end_dt: "" };
        downXls.cmpgn_title = cmpgn_title;
    
        if (send_start_dt !== null) {
          downXls.send_start_dt = changeFormat(send_start_dt, "YYYY-MM-DD");
        }
        if (send_end_dt !== null) {
          downXls.send_end_dt = changeFormat(send_end_dt, "YYYY-MM-DD");
        }
    
        try {
          const res = await authAxios.get(
            `/campaign-report/download?send_start_dt=${downXls.send_start_dt}&send_end_dt=${downXls.send_end_dt}&cmpgn_title=${downXls.cmpgn_title}`,
            { responseType: "blob" }
          );
    
          console.log("์‘๋‹ต ํ—ค๋”:", res.headers);
    
          // ์‘๋‹ต ํ—ค๋”์—์„œ ํŒŒ์ผ๋ช… ์ถ”์ถœ (์ •๊ทœํ‘œํ˜„์‹ ์‚ฌ์šฉ)
          const contentDisposition = res.headers["content-disposition"] || "";
          const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
          const matches = filenameRegex.exec(contentDisposition);
          const filename =
            matches != null && matches[1]
              ? matches[1].replace(/['"]/g, "")
              : "download.xlsx";
    
          // FileSaver๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹คํ–‰
          FileSaver.saveAs(res.data, filename);
        } catch (error) {
          console.error("์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:", error);
        }
      };
    
      return (
        <div>
          <h2>์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ</h2>
          <div>
            {/* ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ํผ ์˜ˆ์‹œ */}
            <input
              type="text"
              placeholder="์บ ํŽ˜์ธ ์ œ๋ชฉ"
              value={campaignSearchData.cmpgn_title}
              onChange={(e) =>
                setCampaignSearchData({
                  ...campaignSearchData,
                  cmpgn_title: e.target.value,
                })
              }
            />
            <input
              type="date"
              placeholder="๋ฐœ์†ก ์‹œ์ž‘์ผ"
              onChange={(e) =>
                setCampaignSearchData({
                  ...campaignSearchData,
                  send_start_dt: e.target.value,
                })
              }
            />
            <input
              type="date"
              placeholder="๋ฐœ์†ก ์ข…๋ฃŒ์ผ"
              onChange={(e) =>
                setCampaignSearchData({
                  ...campaignSearchData,
                  send_end_dt: e.target.value,
                })
              }
            />
          </div>
          <button onClick={handleDown}>์—‘์…€ ๋‹ค์šด๋กœ๋“œ</button>
        </div>
      );
    };
    
    export default ExcelDownloadComponent;

    ์œ„ ์ปดํฌ๋„ŒํŠธ๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์บ ํŽ˜์ธ ์ œ๋ชฉ๊ณผ ๋ฐœ์†ก ๋‚ ์งœ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„, ํ•ด๋‹น ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์—‘์…€ ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๋„๋ก ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, API ํ˜ธ์ถœ ํ›„ response.headers์— ํฌํ•จ๋œ content-disposition์—์„œ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•˜๋Š” ๋ถ€๋ถ„์€ ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ๋„ ์ž์ฃผ ํ•„์š”ํ•œ ๊ธฐ์ˆ ๋กœ, ๋ณธ ์˜ˆ์ œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๊ตฌํ˜„ ์‹œ ๊ณ ๋ คํ•ด์•ผ ํ•  ๋ณด์•ˆ ๋ฐ ์„ฑ๋Šฅ ์ด์Šˆ

    ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์™ธ์—๋„ ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€์ ์ธ ๊ณ ๋ ค ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    1. ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ
      API ํ˜ธ์ถœ ์‹œ ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. Axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์‹œ Authorization ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์ฒ ์ €ํžˆ ๊ด€๋ฆฌํ•˜๊ณ , ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์‹œ๋„ํ•  ๊ฒฝ์šฐ ์ ์ ˆํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    2. ํŒŒ์ผ ํฌ๊ธฐ ๋ฐ ๋‹ค์šด๋กœ๋“œ ์‹œ๊ฐ„
      ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•  ๊ฒฝ์šฐ, ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๊ณผ ๋‹ค์šด๋กœ๋“œ ์‹œ๊ฐ„์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”์— ๋”ฐ๋ผ ๋‹ค์šด๋กœ๋“œ ์ง„ํ–‰ ์ƒํƒœ(progress)๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹œ๊ฐ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ UX๋ฅผ ์œ„ํ•ด ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ
      ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ค‘ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๋‚˜ ์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•˜๊ณ , ์žฌ์‹œ๋„ ์˜ต์…˜์ด๋‚˜ ๋Œ€์ฒด ๊ฒฝ๋กœ๋ฅผ ์•ˆ๋‚ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
    4. ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ
      FileSaver ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜์ง€๋งŒ, ํŠน์ • ํ™˜๊ฒฝ์—์„œ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์ด ์›ํ™œํžˆ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    ๊ฒฐ๋ก 

    ๋ฆฌ์•กํŠธ ํ™˜๊ฒฝ์—์„œ ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ๋‹จ์ˆœํžˆ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ๋ณต์žกํ•œ ๊ณผ์ •์„ ๋‚ดํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Axios๋ฅผ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ blob ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , FileSaver๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๊ณผ์ •์—์„œ HTTP ํ—ค๋”์— ํฌํ•จ๋œ ํŒŒ์ผ๋ช…์„ ์ถ”์ถœํ•˜๋Š” ๋ถ€๋ถ„์€ ํŠนํžˆ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ํŒŒ์ผ๋ช… ์ถ”์ถœ์„ ์œ„ํ•ด JSON.stringify()์™€ ๋ฌธ์ž์—ด ์กฐ์ž‘, ๋˜๋Š” ์ •๊ทœ ํ‘œํ˜„์‹์„ ํ™œ์šฉํ•œ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜์˜€์œผ๋ฉฐ, ์‹ค์ œ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ๋ณด๋‹ค ๊ตฌ์ฒด์ ์œผ๋กœ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…๋“œ๋ ธ์Šต๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•˜๋ฉด, ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ ๋‚ด์—์„œ๋„ ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์„ ์†์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ์—‘์…€ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€