๋ฐ˜์‘ํ˜•

๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ์—์„œ CSS ๋ฐฉ์‹, dangerouslySetInnerHTML, ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹ ๋“ฑ์œผ๋กœ ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ์˜ ์ค„๋ฐ”๊ฟˆ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ตœ์ข…์ ์œผ๋กœ ๊ถŒ์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹์ด๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ๊ฐ€์žฅ ์ ํ•ฉํ•˜๋‹ค:

  • ์žฌ์‚ฌ์šฉ์„ฑ: ๊ฐ™์€ ์ค„๋ฐ”๊ฟˆ ๋กœ์ง์„ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜: ๋ฉ”์‹œ์ง€ ํฌ๋งคํŒ… ๋กœ์ง์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด, ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ์„ ๋•Œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ์ผ๊ด€๋˜๊ฒŒ ์ ์šฉํ•˜๊ธฐ ์‰ฝ๋‹ค.
  • ํ™•์žฅ์„ฑ: ์ถ”ํ›„์— ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์— ์ถ”๊ฐ€์ ์ธ ๋ณ€ํ™˜ ๋กœ์ง(์˜ˆ: ํŠน์ • ๋‹จ์–ด์— ์Šคํƒ€์ผ ์ ์šฉ)์ด ํ•„์š”ํ•  ๋•Œ ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™•์žฅํ•˜๊ธฐ ์‰ฝ๋‹ค.

์ด ๋ฐฉ๋ฒ•์€ ๋ณด์•ˆ์ ์œผ๋กœ ์•ˆ์ „ํ•˜๋ฉฐ, dangerouslySetInnerHTML์˜ ๋ณด์•ˆ ์ทจ์•ฝ์  ์—†์ด ์œ ์‚ฌํ•œ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•œ๋‹ค. CSS ๋ฐฉ์‹์€ ๋งค์šฐ ๋‹จ์ˆœํ•˜์ง€๋งŒ, ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ์— ์žˆ์–ด ๋‹ค๋ฅธ HTML ํƒœ๊ทธ ์ ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์–ด ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค.

1. CSS ์‚ฌ์šฉ - ๊ฐ€์žฅ ๊ฐ„๋‹จ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค. CSS์˜ white-space: pre-line; ์†์„ฑ์„ ์‚ฌ์šฉํ•ด ๋ฌธ์ž์—ด ๋‚ด์˜ ์ค„๋ฐ”๊ฟˆ(\n)์„ HTML์—์„œ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

interface Props {
  message: string;
}

export default function Intro({ message }: Props) {
  return (
    <div style={{ whiteSpace: 'pre-line' }}>
      {message}
    </div>
  );
}

 

2. dangerouslySetInnerHTML ๋ฐฉ์‹ - ๊ถŒ์žฅ๋˜์ง€ ์•Š์Œ

dangerouslySetInnerHTML๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HTML๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ง์ ‘ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

interface Props {
  message: string;
}

export default function Intro({ message }: Props) {
  const formattedMessage = { __html: message.replace(/\n/g, '<br>') };
  return (
    <div dangerouslySetInnerHTML={formattedMessage} />
  );
}

3. ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹ โญ๊ถŒ์žฅ

๋ฉ”์‹œ์ง€ ๋‚ด์˜ \n์„ <br /> ํƒœ๊ทธ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ Œ๋”๋งํ•˜๋Š” ๋ณ„๋„์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ฐ€์žฅ ์œ ์—ฐํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์€ ๋ฐฉ๋ฒ•์ด๋‹ค.

interface Props {
  message: string;
}

export default function Intro({ message }: Props) {
  const formattedMessage = message.split('\n').map((part, index, array) => (
    <React.Fragment key={index}>
      {part}{index !== array.length - 1 && <br />}
    </React.Fragment>
  ));

  return (
    <div>
      {formattedMessage}
    </div>
  );
}
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐ŸŽˆ dangerouslySetInnerHTML

 

dangerouslySetInnerHTML์€ React์—์„œ HTML์„ ์ง์ ‘์ ์œผ๋กœ DOM์— ์‚ฝ์ž…ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์†์„ฑ์ด๋‹ค. ์ด ์ด๋ฆ„์— dangerously๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋“ค์–ด๊ฐ„ ์ด์œ ๋Š” HTML์„ ์ง์ ‘ ์กฐ์ž‘ํ•  ๋•Œ ๋ณด์•ˆ ์œ„ํ—˜์ด ์žˆ์„ ์ˆ˜ ์žˆ์Œ์„ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค.

์‚ฌ์šฉ๋ฒ•

dangerouslySetInnerHTML์€ ๊ฐ์ฒด๋ฅผ ์†์„ฑ ๊ฐ’์œผ๋กœ ๋ฐ›๊ณ , ์ด ๊ฐ์ฒด๋Š” __html ํ‚ค๋ฅผ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค. ์ด ํ‚ค์˜ ๊ฐ’์œผ๋กœ๋Š” HTML ๋ฌธ์ž์—ด์ด ๋“ค์–ด๊ฐ„๋‹ค. ์™ธ๋ถ€์—์„œ ๊ฐ€์ ธ์˜จ HTML์„ ์‚ฝ์ž…ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š”๋ฐ, React๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง์ ‘์ ์ธ HTML ์‚ฝ์ž…์„ ๋ฐฉ์ง€ํ•ด์„œ XSS ๊ณต๊ฒฉ์„ ๋ง‰์œผ๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

function MyComponent() {
  return <div dangerouslySetInnerHTML={{ __html: '<strong>์•ˆ๋…•ํ•˜์„ธ์š”</strong>' }} />;
}

์žฅ์ 

  • ์ง์ ‘์ ์ธ HTML ์‚ฝ์ž…: ๊ธฐ์กด HTML ๋ฌธ์ž์—ด์„ DOM์— ๋ฐ”๋กœ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์–ด์„œ, ์„œ๋ฒ„๋‚˜ ์™ธ๋ถ€์—์„œ ๊ฐ€์ ธ์˜จ ๋งˆํฌ์—…์„ ๊ทธ๋Œ€๋กœ ๋ Œ๋”๋งํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.
  • ์œ ์—ฐ์„ฑ: ํŠน์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๋„๊ตฌ์—์„œ ์ƒ์„ฑ๋œ HTML์„ React ์ปดํฌ๋„ŒํŠธ์™€ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • ๋ณด์•ˆ ์œ„ํ—˜: XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜๋‹ค. ์™ธ๋ถ€์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฒ€์ฆ๋˜์ง€ ์•Š์œผ๋ฉด ์•…์˜์ ์ธ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค.
  • SEO ์ตœ์ ํ™”: ์ •์ ์ธ ํŽ˜์ด์ง€ ์ƒ์„ฑ์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค. ํฌ๋กค๋Ÿฌ๊ฐ€ JavaScript๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ์„ฑ๋Šฅ ๋ฌธ์ œ: React์˜ ๊ฐ€์ƒ DOM๊ณผ์˜ ๋™๊ธฐํ™” ๋ฌธ์ œ๋กœ ์ธํ•ด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ๋‹ค.

๋Œ€์ฒด ๋ฐฉ๋ฒ•

  • React ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ: HTML์„ React ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ XSS ๊ณต๊ฒฉ์˜ ์œ„ํ—˜์„ ์ค„์ด๊ณ , React์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • DOMPurify์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ: ์™ธ๋ถ€ HTML์„ ์‚ฝ์ž…ํ•˜๊ธฐ ์ „์— ํด๋ฆฐ์—…ํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์œ„ํ—˜ํ•œ ํƒœ๊ทธ์™€ ์†์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ์•ˆ์ „ํ•œ HTML๋งŒ ์‚ฝ์ž…๋˜๋„๋ก ํ•œ๋‹ค.
  • Markdown ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ: ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ Markdown ํ…์ŠคํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ HTML๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์ด๋‚˜ ์ฝ”๋ฉ˜ํŠธ ์‹œ์Šคํ…œ์— ์œ ์šฉํ•˜๋‹ค.

dangerouslySetInnerHTML์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ณด์•ˆ์„ ํŠนํžˆ ์ฃผ์˜ํ•ด์•ผ ํ•˜๊ณ , ๊ฐ€๋Šฅํ•˜๋ฉด ์•ˆ์ „ํ•œ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ์ข‹๋‹ค.

๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐ŸŽˆ date-fns

date-fns๋Š” ๋‚ ์งœ์™€ ์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋Ÿ‰ํ™”๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

 

โœ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

yarn add date-fns

 

โœ… date-fns ์žฅ์ 

  1. ๋‹จ์ˆœ์„ฑ: date-fns๋Š” ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋ณต์žก์„ฑ์„ ์ค„์ด๊ณ  ํ•„์š” ์—†๋Š” ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
  2. ๋ชจ๋“ˆํ™”: ๋ชจ๋“  ํ•จ์ˆ˜๊ฐ€ ๋…๋ฆฝ์ ์ด๋ฏ€๋กœ ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์š”ํ•œ ๋ชจ๋“ˆ๋งŒ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.
  3. ์ผ๊ด€๋œ API: ๋‚ ์งœ์™€ ์‹œ๊ฐ„ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ํ•จ์ˆ˜๋“ค์ด ํ†ต์ผ๋œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.
  4. ํƒ€์ž„์กด ์ง€์› ๋ฐ ํ™•์žฅ์„ฑ: date-fns๋Š” ๋ณต์žกํ•œ ํƒ€์ž„์กด ๋ณ€ํ™˜ ๋“ฑ์„ ์ง€์›ํ•˜๋ฉฐ, Date ๊ฐ์ฒด์˜ ๋ณต์žก์„ฑ์„ ๊ฐ„์†Œํ™”ํ•ด์ค€๋‹ค.
  5. ํผํฌ๋จผ์Šค: Moment.js์™€ ๋น„๊ตํ•ด ๊ฐ€๋ณ๊ณ  ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋ฉฐ, ๋ฒˆ๋“ค ํฌ๊ธฐ๋„ ๋” ์ž‘๋‹ค.

โœ… date-fns ์ฃผ์š” ์‚ฌ์šฉ ์˜ˆ์‹œ

1. parseISO()

๋ฌธ์ž์—ด๋กœ ๋œ ISO ๋‚ ์งœ๋ฅผ Date ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜.

const weddingDate = parseISO(date);


date๋Š” ISO 8601 ํฌ๋งท(์˜ˆ: "2024-11-12T12:30:00")์ธ ๋ฌธ์ž์—ด๋กœ ์ „๋‹ฌ๋˜๋ฉฐ, ์ด๋ฅผ Date ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด ๋‹ค์–‘ํ•œ ๋‚ ์งœ ์—ฐ์‚ฐ์— ์‚ฌ์šฉ๋œ๋‹ค.

 

2. format()

  • ๋‚ ์งœ ๊ฐ์ฒด๋ฅผ ํŠน์ • ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฌธ์ž์—ด๋กœ ์ถœ๋ ฅ.
    format(weddingDate, 'yy.MM.dd')
    weddingDate๋ฅผ yy.MM.dd ํฌ๋งท(์˜ˆ: "24.11.12")์œผ๋กœ ํฌ๋งทํŒ…ํ•œ๋‹ค.

3. getDay()

  • ์ฃผ์–ด์ง„ Date ๊ฐ์ฒด์—์„œ ์š”์ผ์„ ์ˆซ์ž(0~6)๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (0์€ ์ผ์š”์ผ, 6์€ ํ† ์š”์ผ)
    const dayIndex = getDay(weddingDate);
    DAYS[dayIndex]์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์š”์ผ์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
          <div className={cx('txt-day')}>{DAYS[dayIndex]}</div>

์™œ date-fns๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€?

  1. ๋‚ ์งœ ์—ฐ์‚ฐ์˜ ๋ณต์žก์„ฑ: ๊ธฐ๋ณธ Date ๊ฐ์ฒด๋กœ ๋‚ ์งœ ์—ฐ์‚ฐ์„ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด ๋ณต์žกํ•˜๊ณ  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๊ฐ€ ๋˜๊ธฐ ์‰ฝ๋‹ค. date-fns๋Š” ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.
  2. ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ: ๋‚ ์งœ๋ฅผ ํฌ๋งทํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™˜ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค format() ๊ฐ™์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ธ๋‹ค.
  3. ์œ ์ง€ ๋ณด์ˆ˜์„ฑ: ๋‚ด์žฅ Date ๊ฐ์ฒด ๋ฉ”์„œ๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ฐ„ ์ผ๊ด€์„ฑ์ด ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, date-fns๋Š” ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ ์ผ๊ด€๋˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

 

๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐ŸŽˆ JSON Server ์‚ฌ์šฉํ•˜๊ธฐ

1. REST / REST API / Json Server

  • REST: ์›น์˜ ๊ธฐ๋ณธ ํ”„๋กœํ† ์ฝœ์ธ HTTP ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜๋ฉฐ , HTTP ๋ฉ”์„œ๋“œ์™€ URL ์„ ์ด์šฉํ•˜์—ฌ ์ž์›๊ณผ ํ•˜๊ณ ์žํ•˜๋Š” ํ–‰๋™์„ ํ‘œํ˜„ํ•จ.
  • REST API: REST ํ•œ ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„๋œ API.
  • Json Server: JSON ํŒŒ์ผ์„ ์ด์šฉํ•˜์—ฌ Rest API ์„œ๋ฒ„๋ฅผ ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ์„ฑ ํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ. 
    ex) (GET) http://example.com/wedding/1 : 1๋ฒˆ id ์ฒญ์ฒฉ์žฅ ๊ฐ€์ ธ์˜ค๊ธฐ

2. JSON Server ์‚ฌ์šฉํ•˜๊ธฐ

https://github.com/typicode/json-server

01. JSON Server ์„ค์น˜

yarn add -D json-server
  • ๋ฃจํŠธ๊ฒฝ๋กœ์— db.json ์ƒ์„ฑ
{
  "posts": [
    { "id": "1", "title": "a title", "views": 100 },
    { "id": "2", "title": "another title", "views": 200 },
    { "id": "3", "title": "json-server", "views": 200 }

  ],
  "comments": [
    { "id": "1", "text": "a comment about post 1", "postId": "1" },
    { "id": "2", "text": "another comment about post 1", "postId": "1" }
  ],
  "profile": {
    "name": "typicode"
  }
}
  • package.json์— ์•„๋ž˜ ๋‚ด์šฉ ์ถ”๊ฐ€
"dev:db": "json-server --watch db.json --port=8888" 
// watch์˜ต์…˜์„ ์ฃผ๋ฉด db.json์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ฐœ๋ฐœ์„œ๋ฒ„์— ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜ํ•ด์ค€๋‹ค.
  • ์‹คํ–‰
yarn dev:db

json server ์„ค์น˜์™„๋ฃŒ!

 

02. ๋™์ž‘ test (https://github.com/typicode/json-server?tab=readme-ov-file)

  • Filter
  • ์‹คํ–‰๊ฒฐ๊ณผ 
    json server filter ์‹คํ–‰๊ฒฐ๊ณผ

  • Paginate
    Use _page and optionally _limit to paginate returned data. In the Link header you'll get firstprevnext and last links.
    GET /posts?_page=7   
    โ€‹
    • _page=7 : ์ผ๊ณฑ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•œ๋‹ค.
    • _limit ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ช…์‹œ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ, json-server๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํŽ˜์ด์ง€๋‹น 10๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • ๊ฒฐ๊ณผ : ๊ธฐ๋ณธ ํŽ˜์ด์ง€๋‹น 10๊ฐœ ํ•ญ๋ชฉ์„ ์‚ฌ์šฉํ•ด 61~70๋ฒˆ์งธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    GET /posts?_page=7&_limit=20
    
    • _page=7: _page=7์œผ๋กœ ์ผ๊ณฑ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•œ๋‹ค.
    • _limit: _limit=20์„ ์ง€์ •ํ•˜์—ฌ ํŽ˜์ด์ง€๋‹น 20๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
    • ์ผ๊ณฑ ๋ฒˆ์งธ ํŽ˜์ด์ง€์—์„œ 20๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, posts ๋ฐ์ดํ„ฐ ์ค‘ 121๋ฒˆ์งธ์—์„œ 140๋ฒˆ์งธ ํ•ญ๋ชฉ๊นŒ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. 
  • Sort
    _sort=f1,f2
    ๋ฐ์ดํ„ฐ๋ฅผ ํŠน์ • ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ •๋ ฌํ•  ๋•Œ๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ _sort๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์ •๋ ฌ ์ˆœ์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์˜ค๋ฆ„์ฐจ์ˆœ์ด๋ฉฐ, ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์„ค์ •ํ•˜๋ ค๋ฉด ํ•„๋“œ ์•ž์— -๋ฅผ ๋ถ™์ธ๋‹ค.
    GET /posts?_sort=id,-views
    1. GET /posts?_sort=id
    • id ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ
    • posts ๋ฐ์ดํ„ฐ๊ฐ€ id ๊ฐ’์ด ์ž‘์€ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ๋˜์–ด ๋ฐ˜ํ™˜ ๋œ๋‹ค.
    2. GET /posts?_sort=-views
    • views ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.
    • posts ๋ฐ์ดํ„ฐ๊ฐ€ views ๊ฐ’์ด ํฐ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ๋˜์–ด ๋ฐ˜ํ™˜ ๋œ๋‹ค.
    3. ๋‹ค์ค‘ ์ •๋ ฌ ์˜ˆ์‹œ: GET /posts?_sort=id,-views
    • ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•„๋“œ๋ฅผ ์ •๋ ฌ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, id ํ•„๋“œ๋ฅผ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ๋จผ์ € ์ •๋ ฌํ•œ ๋‹ค์Œ, ๋™์ผํ•œ id ๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ views ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ•œ๋‹ค.
    • ์šฐ์„  id ๊ฐ’์ด ์ž‘์€ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ๋˜๋ฉฐ, ๊ฐ™์€ id ๊ฐ’์„ ๊ฐ€์ง„ ํ•ญ๋ชฉ๋“ค์€ views ๊ฐ’์ด ํฐ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ๋œ๋‹ค.

 

 

 

๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐Ÿ‘ฟ ์—๋Ÿฌ์ƒํ™ฉ

eslint prettier ์ค„๋ฐ”๊ฟˆ ์—๋Ÿฌ ์™•์ฐฝ………

ERROR in [eslint]
src\\App.tsx
  Line 1:26:   Delete `โ`  prettier/prettier
  Line 2:30:   Delete `โ`  prettier/prettier
  Line 3:19:   Delete `โ`  prettier/prettier
  Line 4:1:    Delete `โ`  prettier/prettier
  Line 5:41:   Delete `โ`  prettier/prettier
  Line 6:39:   Delete `โ`  prettier/prettier
  Line 7:1:    Delete `โ`  prettier/prettier
  Line 8:35:   Delete `โ`  prettier/prettier
  Line 9:17:   Delete `โ`  prettier/prettier
  Line 10:11:  Delete `โ`  prettier/prettier
  Line 11:38:  Delete `โ`  prettier/prettier
  Line 12:38:  Delete `โ`  prettier/prettier
  Line 13:59:  Delete `โ`  prettier/prettier
  Line 14:12:  Delete `โ`  prettier/prettier
  Line 15:60:  Delete `โ`  prettier/prettier
  Line 16:13:  Delete `โ`  prettier/prettier
  Line 17:11:  Delete `โ`  prettier/prettier
  Line 18:31:  Delete `โ`  prettier/prettier
  Line 19:37:  Delete `โ`  prettier/prettier
  Line 20:26:  Delete `โ`  prettier/prettier
  Line 21:36:  Delete `โ`  prettier/prettier
  Line 22:10:  Delete `โ`  prettier/prettier
Failed to compile.

๐Ÿง ์—๋Ÿฌ ์›์ธ

Git ์„ค์ •๊ณผ VSCode ์„ค์ •์ด ์„œ๋กœ ์ถฉ๋Œํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ ๋ฐœ์ƒ. "files.eol": "\n" ์„ค์ •์ด ์ด๋ฏธ VSCode์— ์žˆ์—ˆ์ง€๋งŒ, Git์ด ์ค„ ๋ฐ”๊ฟˆ ํ˜•์‹์„ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์„ค์ •(core.autocrlf)์ด ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์˜€๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๋ฌธ์ œ

 

โœจ ์—๋Ÿฌ ํ•ด๊ฒฐ

โœ… Git ์„ค์ • ์—…๋ฐ์ดํŠธ

Windows์—์„œ CRLF๊ฐ€ ๊ธฐ๋ณธ ์ค„ ๋ฐ”๊ฟˆ์ด ๋˜์ง€ ์•Š๋„๋ก ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ Git ์„ค์ •์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

# ํ”„๋กœ์ ํŠธ ๋กœ์ปฌ์—์„œ ์‹คํ–‰
git config core.autocrlf false

# ๊ธ€๋กœ๋ฒŒ ์„ค์ •์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒฝ์šฐ
git config --global core.autocrlf false
  • ๋ชจ๋“  ํŒŒ์ผ LF๋กœ ๋ณ€ํ™˜
# Windows ํ™˜๊ฒฝ์—์„œ Git Bash ๋˜๋Š” WSL ์‚ฌ์šฉ ์‹œ
git ls-files -z | xargs -0 dos2unix

 

::์ฐธ๊ณ :: 

Prettier ๋ฐ ESLint ๊ฐ•์ œ ์ ์šฉ

npx prettier --write .
yarn eslint --fix .

VSCode์—์„œ ์ค„ ๋ฐ”๊ฟˆ ์ผ๊ด„ ๋ณ€๊ฒฝ

  • ๋ช…๋ น ํŒ”๋ ˆํŠธ(Ctrl+Shift+P)์—์„œ “Change All End Of Line Sequence”๋ฅผ ์„ ํƒํ•˜๊ณ , LF๋กœ ๋ณ€๊ฒฝ
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐Ÿ‘ฟ ์—๋Ÿฌ ์ƒํ™ฉ

Compiled with problems:
ERROR

[eslint] Invalid Options: - Unknown options: extensions, resolvePluginsRelativeTo - 'extensions' has been removed. - 'resolvePluginsRelativeTo' has been removed.

๐Ÿง ์—๋Ÿฌ ์›์ธ

ESLint 9 ๋ฒ„์ „ ๋ฌธ์ œ๋กœ ๋ฐœ์ƒ

โœจ ์—๋Ÿฌ ํ•ด๊ฒฐ

  • eslint 9๋ฒ„์ „ ์ œ๊ฑฐ
yarn remove eslint
  • ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ
yarn add -D eslint@8.57.0
  • yarn berry zero install - sdk ์—…๋ฐ์ดํŠธ
yarn dlx @yarnpkg/sdks vscode

 

 

์ถœ์ฒ˜: https://mariais.tistory.com/entry/Eslint-error-ํ•ด๊ฒฐ-๋ฐฉ๋ฒ•-extensions-has-been-removed-resolvepluginsrelativeto-has-been-removed

๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

๐Ÿ‘ฟYarn Berry ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ์„ค์ • ์ค‘ .yarn/cache ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ

Yarn Berry ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ์„ค์ • ์ค‘ .yarn/cache ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ. yarn install์„ ์•„๋ฌด๋ฆฌ ์‹œ๋„ํ•ด๋„ ์บ์‹œํด๋”๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ..

๐Ÿง ์—๋Ÿฌ ์›์ธ

  • ์ด์ „์—๋Š” enableGlobalCache์˜ ๊ธฐ๋ณธ๊ฐ’์ด false์˜€๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋กœ์ปฌ .yarn/cache ํด๋”์— ์˜์กด์„ฑ์„ ์ €์žฅํ•˜๋„๋ก ๋˜์–ด ์žˆ์—ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์ตœ๊ทผ ์—…๋ฐ์ดํŠธ์—์„œ enableGlobalCache์˜ ๊ธฐ๋ณธ๊ฐ’์„ true๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ, ์ „์—ญ ์บ์‹œ ์‚ฌ์šฉ์„ ๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ ์ ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ Yarn์˜ ์ตœ์‹  ๋ฒ„์ „์—์„œ๋Š” enableGlobalCache: false ์„ค์ •์„ .yarnrc.yml์— ์ง์ ‘ ์ถ”๊ฐ€ํ•ด์•ผ๋งŒ ๋กœ์ปฌ ์บ์‹œ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. ์„ค์ • ํŒŒ์ผ์— ํ•ด๋‹น ์˜ต์…˜์„ ์ง์ ‘ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฉด, Yarn์€ ์ „์—ญ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ .yarn/cache ํด๋”๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ ํ–ˆ๋˜ ๊ฒƒ!

โœจ ์—๋Ÿฌ ํ•ด๊ฒฐ

  • .yarnrc.yml ํŒŒ์ผ์— enableGlobalCache: false ์„ค์ • ์ถ”๊ฐ€
    //.yarnrc.yml 
    nodeLinker: pnp
    enableGlobalCache: false
    โ€‹
  • ์„ค์ • ๋ณ€๊ฒฝ ํ›„ ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ์žฌ์„ค์น˜
    yarn install

๋“œ๋””์–ด cacheํŒŒ์ผ์ด ์ƒ์„ฑ๋˜์—ˆ๋‹ค!!

๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ์„ค์ •

๐Ÿ€ ES Lint / Prettier ์„ค์ •

  1. eslint, prettier ํŒจํ‚ค์ง€ ์„ค์น˜
    • yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-react eslint-config-react-app
  2. Config ์„ค์ • ๋ถ„๋ฆฌ
    • package.json์˜ "eslintConfig”๋ถ€๋ถ„์„ ๋ณ„๋„์˜ .eslintrc.jsonํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ ํ›„ ์ถ”๊ฐ€ ์„ค์ • ์ž‘์„ฑ
      //.eslintrc.json
      
      {
          "extends": [
              "react-app",
              "react-app/jest",
              "plugin:prettier/recommended"
          ],
          "plugins":["prettier"],
          "rules":{
              "prettier/prettier":"error"
          }
      }
      โ€‹
    • .prettierrc ํŒŒ์ผ ์ƒ์„ฑ
      //prettierrc 
      
      {
          "useTabs": false,
          "printWidth": 80,
          "tabWidth": 2,
          "singleQuote": true,
          "trailingComma": "all",
          "endOfLine": "lf",  // ํŒŒ์ผ์˜ ์ค„ ๋(Line Ending) ํ˜•์‹์„ ์ง€์ •
          "semi": false,
          "arrowParens": "always"
          }
      โ€‹
  3. setting.json ํŒŒ์ผ์— ์•„๋ž˜ ์ฝ”๋“œ ์ถ”๊ฐ€
    • Ctrl + Shift + P๋ฅผ ๋ˆŒ๋Ÿฌ ๋ช…๋ น ํŒ”๋ ˆํŠธ๋ฅผ ์—ด๊ธฐ → "Preferences: Open Settings (JSON)"์„ ์ž…๋ ฅํ•˜๊ณ  ์„ ํƒ
      // ์ˆ˜์ • ํ›„ ์ €์žฅํ•  ๋•Œ eslint๋กœ autofix ์‹คํ–‰
      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": "true"
      },
      // ํŒŒ์ผ์˜ ์ค„ ๋(Line Ending) ํ˜•์‹์„ ์ง€์ •. ์•ž์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ํŒŒ์ผ๋“ค์€ lf(mac)๋กœ ์ƒ์„ฑ๋จ
      "files.eol":"\\n",
    
    โœ… ํŒŒ์ผ์˜ ์ค„ ๋ ํ˜•์‹ :: mac vs window
    "files.eol": "\n": ํŒŒ์ผ์˜ ์ค„ ๋์„ ์œ ๋‹‰์Šค/๋ฆฌ๋ˆ…์Šค ์Šคํƒ€์ผ์ธ LF (Line Feed) ํ˜•์‹์œผ๋กœ ์ง€์ •
    "files.eol": "\r\n" : ํŒŒ์ผ์˜ ์ค„ ๋์„ Windows ์‹œ์Šคํ…œ์˜ ๊ธฐ๋ณธ ํ˜•์‹์ธ CRLF ํ˜•์‹์œผ๋กœ ์ง€์ •

  4. vscode์— ์ ์šฉ
    yarn dlx @yarnpkg/sdks vscode
    
  5. prettier์—์„œ ์„ธ๋ฏธ์ฝœ๋ก  ์ œ๊ฑฐ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— package.json์— ์•„๋ž˜ ์ฝ”๋“œ ์ถ”๊ฐ€
    //package.json
        "lint": "eslint \\"src/**/*/{js,jsx,ts,tsx}\\"",
        "lint:fix": "eslint \\"src/**/*/{js,jsx,ts,tsx}\\""
    
    
    • ์ ์šฉ
    yarn lint:fix
    
  6. Craco Alias ์„ค์ •
    • ์„ค์น˜
      yarn add -D @craco/craco craco-alias
    • tsconfig.paths.json ์ •์˜
      // tsconfig.paths.json
      {
          "compilerOptions":{
              "baseUrl":".",
              "paths":{
                  "@/*": ["src/*"],
                  "@components/*":["src/components/*"]
              }
          }
      }
    • craco.config.js ์ •์˜
      // craco.config.js
      module.exports = {
        plugins: [
          {
            plugin: CracoAlias,
            options: {
              source: 'tsconfig',
              tsConfigPath: 'tsconfig.paths.json',
            },
          },
        ],
      }
    • Tsconfig์— tsconfig.paths.json extends ์‹œํ‚ค๊ธฐ
      // tsconfig.json
      {
        "extends": "./tsconfig.paths.json",
        "compilerOptions": {
          "target": "es5",
          "lib": ["dom", "dom.iterable", "esnext"],
          "allowJs": true,
          "skipLibCheck": true,
          "esModuleInterop": true,
          "allowSyntheticDefaultImports": true,
          "strict": true,
          "forceConsistentCasingInFileNames": true,
          "noFallthroughCasesInSwitch": true,
          "module": "esnext",
          "moduleResolution": "node",
          "resolveJsonModule": true,
          "isolatedModules": true,
          "noEmit": true,
          "jsx": "react-jsx"
        },
        "include": ["src", "tsconfig.paths.json"]
      }
      
      โ€‹

๐Ÿ€ scss ์„ค์ •

yarn add classnames sass
  • global.scss ์ƒ์„ฑ
  • App.module.scss ์ƒ์„ฑ
  • src\App.tsx
import classNames from 'classnames/bind'
import styles from './App.module.scss'

const cx = classNames.bind(styles)

//    <div className={cx('container')}> ์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉ!
๋ฐ˜์‘ํ˜•

+ Recent posts