์ด ๊ธ€์€ Tanstack Query์™€ SSE๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์ƒํƒœ ๋™๊ธฐํ™” ๋ฌธ์ œ๋ฅผ ๊ฒฝํ—˜ํ•œ ๋ถ„๋“ค์„ ์œ„ํ•œ ์‚ฌ๋ก€ ๊ณต์œ ์ž…๋‹ˆ๋‹ค.

 

์ปค๋ฆฌ์–ด๋น„์—์„œ ์•Œ๋ฆผ ๋„๋ฉ”์ธ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ •๋ง ๋งŽ์€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…์ด ์žˆ์—ˆ๋‹ค.

์˜ค๋Š˜์€ ๊ทธ ์ค‘ ํ•˜๋‚˜์ธ “์•Œ๋ฆผ ์•„์ด์ฝ˜ ์ฝ์Œ ์ฒ˜๋ฆฌ ์—…๋ฐ์ดํŠธ” ์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•ด๋ณด๊ฒ ๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

๋จผ์ € ์ปค๋ฆฌ์–ด๋น„์—์„œ๋Š” SSE๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์•Œ๋ฆผ์ด ์ƒ๊ธฐ๋ฉด ๊ทธ ์—ฌ๋ถ€๋ฅผ boolean์œผ๋กœ ๋ฐ›์•„์˜จ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด๋ฅผ header์˜ ์•Œ๋ฆผ ์•„์ด์ฝ˜์— ์—…๋ฐ์ดํŠธ ํ•ด์ค€๋‹ค.

 

์ด์Šˆ๋Š” ์—ฌ๊ธฐ์—์„œ ๋ฐœ์ƒํ•œ๋‹ค.

์‚ฌ์šฉ์ž๋Š” ์•Œ๋ฆผ์„ ๋ณด๊ธฐ ์œ„ํ•ด ์•Œ๋ฆผ ์•„์ด์ฝ˜์„ ํด๋ฆญํ•œ๋‹ค.

ํด๋ฆญ ์‹œ /notification ์œผ๋กœ ์ด๋™ํ•œ๋‹ค.

์ด๋™ ํ›„, header์˜ ์•Œ๋ฆผ ์•„์ด์ฝ˜์ด ์ผ๋ฐ˜ ์•„์ด์ฝ˜์œผ๋กœ ๋˜๋Œ์•„๊ฐ€์•ผ ํ•œ๋‹ค.

ํฌ์ธํŠธ๋Š” ๋ฌด์‹œํ•˜์ž…

ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์˜ ๊ด€์  ์ฐจ์ด

์—ฌ๊ธฐ์„œ ๋ฐฑ์—”๋“œ์™€์˜ ๋กœ์ง์ด ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด ์ด์Šˆ์˜ ์‹œ๋ฐœ์ ์ด์—ˆ๋‹ค.

์šฐ์„  ์ปค๋ฆฌ์–ด๋น„์—์„œ๋Š” “์•Œ๋ฆผ ํŽ˜์ด์ง€์— ์ง„์ž… ํ•จ” = “์•Œ๋ฆผ์„ ์ฝ์Œ” ์œผ๋กœ ๊ฐ„์ฃผํ•˜์˜€๋‹ค.

๐ŸŽจ ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ

  • ์•Œ๋ฆผ ํŽ˜์ด์ง€์— ์ง„์ž… ์‹œ ๋ฐ”๋กœ header์˜ ์•Œ๋ฆผ ์•„์ด์ฝ˜์ด ์ •์ƒ์œผ๋กœ ๋Œ์•„์™€์•ผ ํ•œ๋‹ค.

๐Ÿ—„๏ธ ์„œ๋ฒ„ ์ž…์žฅ

  • ์ƒˆ ์•Œ๋ฆผ๋“ค์˜ ID๋ฅผ PATCH ์š”์ฒญ์„ ํ†ตํ•ด ๋ฐ›๊ณ , ํ•ด๋‹น ์•Œ๋ฆผ๋“ค์˜ ์ฝ์Œ ์—ฌ๋ถ€๋ฅผ '์•ˆ์ฝ์Œ' -> '์ฝ์Œ' ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
  • ์•Œ๋ฆผํ…Œ์ด๋ธ”์—์„œ ๊ฐ ์œ ์ €๋“ค์˜ ์•Œ๋ฆผ๊ณผ ์ฝ์Œ ์—ฌ๋ถ€๋ฅผ ํ•จ๊ป˜ ์ €์žฅํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค

hasNewAlarm์˜ T/F ๋ณ€ํ™˜ ์‹œ์ ์— ๋Œ€ํ•ด์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์‚ฌ์ด์˜ ์ž…์žฅ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๊ฒƒ ๋•Œ๋ฌธ์— ๋ฐฑ์—”๋“œ์—๊ฒŒ ๋ฐ์ดํ„ฐํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ผ๊ณ  ํ•  ์ˆ˜๋Š” ์—†๋Š” ๋…ธ๋ฆ‡์ด๊ธฐ์— ๋‚˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด๋ƒˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ ํ›„๋ณด

[๋ฐฉ๋ฒ• 1] useEffect์—์„œ setQueryData๋กœ ์ฆ‰์‹œ false ์ฒ˜๋ฆฌ

useEffect(() => {
  queryClient.setQueryData(['userinfo'], (oldData: any) => {
    if (!oldData) return oldData;
    if (oldData.hasNewAlarm === false) return oldData;
    return {
      ...oldData,
      hasNewAlarm: false,
    };
  });
}, []);

์ด๋Ÿฐ ์‹์œผ๋กœ ๊ฐ•์ œ๋กœ ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

โŒ ๋‹จ์ 

  • PATCH ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „ userInfo ์ฟผ๋ฆฌ๊ฐ€ stale๋˜์–ด refetch๋˜๋ฉด:
    • ์„œ๋ฒ„์—์„œ๋Š” ์•„์ง hasNewAlarm: true ์ƒํƒœ๋ผ ๋‹ค์‹œ true๋กœ ๋Œ์•„์˜ด
    • ์•„์ด์ฝ˜์ด ๊นœ๋นก์˜€๋‹ค ๋‹ค์‹œ ์ผœ์งˆ ์ˆ˜ ์žˆ์Œ
  • ์ผ๊ด€์„ฑ์ด ๊นจ์งˆ ๊ฐ€๋Šฅ์„ฑ

[๋ฐฉ๋ฒ• 2] PATCH ์™„๋ฃŒ ํ›„ refetchQueries

await PATCH(...);
queryClient.refetchQueries(['userInfo'])

PATCH๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ๋ณ€๊ฒฝ๋œ userInfo ๋ฐ์ดํ„ฐ๋ฅผ refetch ํ•œ๋‹ค.

โŒ ๋‹จ์ 

  • POST ์‘๋‹ต์ด ๋А๋ฆฐ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๋ฆผ ํŽ˜์ด์ง€ ๋“ค์–ด๊ฐ”๋Š”๋ฐ ํ—ค๋” ์•Œ๋ฆผ ์•„์ด์ฝ˜์ด ํ•œ์ฐธ ํ›„์— ์‚ฌ๋ผ์ง
    • ์ฆ‰์‹œ ํ”ผ๋“œ๋ฐฑ์ด ์—†์Œ
    • ์ฒด๊ฐ์ƒ UX๊ฐ€ ๋”œ๋ ˆ์ด๋œ๋‹ค๊ณ  ๋А๋‚„ ์ˆ˜ ์žˆ์Œ
  • ๋„คํŠธ์›Œํฌ ๋ ˆ์ดํ„ด์‹œ ์˜์กด

UI ๊นœ๋นก์ž„์ด ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. ๋„คํŠธ์›Œํฌ ์ƒํƒœ๊ฐ€ ๋А๋ฆฐ ํ™˜๊ฒฝ (์˜ˆ: ๋ชจ๋ฐ”์ผ 3G)
  2. ๋ฐฑ์—”๋“œ ์‘๋‹ต ์ง€์—ฐ (์˜ˆ: 1~2์ดˆ ์ด์ƒ)
  3. ์•Œ๋ฆผ์„ ์ฝ์Œ ์ฒ˜๋ฆฌํ•˜๋Š” POST ์š”์ฒญ๊ณผ, userInfo ์ฟผ๋ฆฌ๊ฐ€ ๋‘˜ ๋‹ค ํ™œ์„ฑํ™” ์ƒํƒœ๋ผ refetch ํƒ€์ด๋ฐ์ด ๊ฒน์นจ

ํ•˜์ง€๋งŒ ์ตœ๋Œ€ํ•œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ํŒจํ„ด์„ ์ ์šฉํ•˜๋Š”๊ฒŒ UX ์ธก๋ฉด์—์„œ ์ข‹์„ ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์•„๋ž˜์˜ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ–ˆ๋‹ค.

[๋ฐฉ๋ฒ• 3] ๋ฎคํ…Œ์ด์…˜ ์‚ฌ์šฉ

// PATCH ์š”์ฒญ ์ค‘์—๋Š” refetch ํ•˜์ง€ ์•Š๊ฒŒ
const mutation = useMutation(markNotificationsAsRead, {
  onMutate: async () => {
    // ์•Œ๋ฆผ ์ฆ‰์‹œ ๊บผ์ง€๊ธฐ
    queryClient.setQueryData(['userInfo'], (old) => ({ ...old, hasNewAlarm: false }));
    // ๊ธฐ์กด ์ฟผ๋ฆฌ ์ทจ์†Œ (์ค‘๊ฐ„ ๊ฐฑ์‹  ๋ฐฉ์ง€)
    await queryClient.cancelQueries(['userInfo']);
  },
  onSuccess: () => {
    queryClient.invalidateQueries(['userInfo']);
  },
});

100% ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋งž๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์‚ฌ์šฉ์—์„œ ์•Œ๋ฆผ ์ƒํƒœ ๊นœ๋นก์ž„์ด 500ms ์ดํ•˜์— ๊ทธ์ณค๊ณ , ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์—์„œ ํ˜ผ๋ž€์„ ๋А๋‚€ ์‚ฌ๋ก€๊ฐ€ ์—†์—ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋ฎคํ…Œ์ด์…˜์€ ์˜ค๋ฒ„์ŠคํŽ™์ด๋ผ๋Š” ๊ฒฐ๋ก ์„ ์ง€์—ˆ๋‹ค.

[๋ฐฉ๋ฒ• 4] setQueryData์™€ refetchQueries ๋ชจ๋‘ ์‚ฌ์šฉ + ๋กค๋ฐฑ

๋ง ๊ทธ๋Œ€๋กœ ์œ„์˜ ๋ฐฉ๋ฒ•์„ ๋ชจ๋‘ ์ ์šฉํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ๋„ ์ถฉ๋ถ„ํžˆ ๋ณด์žฅ๋  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ์™„๋ฒฝํ•œ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ํŒจํ„ด์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋กค๋ฐฑ ๋กœ์ง๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

๋กค๋ฐฑ ๋กœ์ง์€ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

const previousUserInfo = queryClient.getQueryData(['userInfo']);
try {
  await PATCH(...);
  queryClient.refetchQueries({ queryKey: ['userinfo'] });
} catch (error) {
  // ๋กค๋ฐฑ
  queryClient.setQueryData(['userInfo'], previousUserInfo);
}

 

ํšŒ๊ณ 

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”๋Š” ๋‹จ์ˆœํžˆ UI์— ํ‘œ์‹œ๋˜๋А๋ƒ ์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ๊ณผ ์‚ฌ์šฉ์ž ์ฒด๊ฐ ์†๋„, ๊ทธ๋ฆฌ๊ณ  ๊ฐœ๋ฐœ ๋น„์šฉ๊นŒ์ง€ ํ•จ๊ป˜ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋Š” ์˜์—ญ์ด๋ผ๋Š” ๊ฑธ ๋‹ค์‹œ ๋А๊ผˆ๋‹ค.

 

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

 

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

 

์ˆ˜๋งŽ์€ ๊ธฐ์ˆ ๋“ค์ด ์žˆ์ง€๋งŒ ์–ด๋””์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋А๋ƒ๋Š” ๋‚˜์˜ ์„ ํƒํ•˜๊ณ , ๊ตฌํ˜„ ๋ฐฉ์•ˆ๋„ ์ˆ˜๋‘๋ฃฉํ•˜์ง€๋งŒ ๊ทธ์ค‘ ๋ฌด์—‡์„ ์„ ํƒํ•˜๋А๋ƒ๋Š” ๋‚˜์˜ ํŒ๋‹จ์— ์˜ํ•œ๋‹ค. ๊ทธ ๊ทผ๊ฑฐ๋“ค๋„ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

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

 


'๐Ÿ–Œ๏ธFrontend' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Figma] ํ”ผ๊ทธ๋งˆ  (0) 2025.02.20

์ปค๋ฎค๋‹ˆํ‹ฐ ํ”„๋กœ์ ํŠธ์—์„œ ๋ชจ๋‹ฌ, ํ—ค๋”, ๋“œ๋กญ๋‹ค์šด ๊ฐ™์€ ์š”์†Œ๋“ค์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค...

uncaught SyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "my-profileImg" is not a valid custom element name

์ฐพ์•„๋ณด๋‹ˆ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ์˜ ๋„ค์ž„ ๊ทœ์น™์— ๊ด€ํ•œ ๋‚ด์šฉ์ด์—ˆ๋‹ค. ํ—ˆ๊ฑฐ๋ฉ๊ฑฐ๋ฉ์Šค 

๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋˜ ๊ฒŒ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ์˜€๊ตฌ๋‚˜! ์ •ํ™•ํžˆ๋Š” ์›น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์—ˆ๋‹ค... 

์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ์•Œ์•„๊ฐ€๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.....


์›น ์ปดํฌ๋„ŒํŠธ

๊ธฐ๋Šฅ์„ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ์บก์Аํ™”ํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์›น ์•ฑ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ ๋“ค์˜ ๋ชจ์Œ

์ฆ‰, ์ž์ฃผ ์“ฐ์ด๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ๊ธด ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง„ ๊ธฐ๋Šฅ์„ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌํ•˜๊ฒ ๋‹ค! ๋ผ๋Š” ์ทจ์ง€์ด๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์•„์ง€๊ณ  ์ฝ”๋“œ ์ถฉ๋Œ์— ๊ฑฑ์ •์ด ์—†๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜ ๊ฐ™์ด ์ •๋ง ๊ธด ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•˜์ž... ํ—ค๋”๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ๋  ํ…Œ๋‹ˆ๊นŒ ๊ณ„์†ํ•ด์„œ ์ € ๊ธด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค...

...
<body>
    <header>
        <div class="header-contents">    
            <div class="nav-btn left">
                 <img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋’ค๋กœ๊ฐ€๊ธฐ">
            </div>
            <div class="project-name">์„œ๋น„์Šค ํƒ€์ดํ‹€</div>
            <div class="nav-btn right">
                <img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์•ž์œผ๋กœ๊ฐ€๊ธฐ">
            </div>
        </div>
    </header>
    <main>
        ...
    </main>
</body>
...

 

์ด๊ฑธ custom element๋กœ ์ž‘์„ฑํ•ด์„œ ๋ฐ”๊พธ๋ฉด? 

...
<body>
    <my-header></my-header>
    <main>
        ...
    </main>
    <script type="module" src="../components/header.js"></script>
</body>
...

์ด๋ ‡๊ฒŒ๋‚˜ ์งง์•„์ง„๋‹ค!!! ๊ฐ€๋…์„ฑ๋„ ์ข‹์•„์ง€๊ณ  ๊ตฟ๊ตฟ

 

< ๊ตฌ์„ฑ ์š”์†Œ >

1. Custom elements:  ์‚ฌ์šฉ์ž ๋งž์ถคํ˜• HTML element๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” JS API ์„ธํŠธ

2. Shadow DOM:  ๋ฉ”์ธ DOM๊ณผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” DOM ํŠธ๋ฆฌ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ JS API ์„ธํŠธ

3. HTML ํ…œํ”Œ๋ฆฟ:  <template>, <slot>๊ณผ ๊ฐ™์ด ๋ Œ๋”๋ง ์‹œ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ํ…œํ”Œ๋ฆฟ

 

element๊ฐ€ ๋ญ”์ง€ ๋ชจ๋ฅธ๋‹ค๋ฉด...

๋”๋ณด๊ธฐ

HTML element(์š”์†Œ)

์‹œ์ž‘ ํƒœ๊ทธ๋กœ ์‹œ์ž‘ํ•ด์„œ ์ข…๋ฃŒ ํƒœ๊ทธ๋กœ ์ด๋ฃจ์–ด์ง„ ๋ชจ๋“  ๋ช…๋ น์–ด๋“ค

ํƒœ๊ทธ ์ด๋ฆ„

  - element์˜ ์—ญํ•  ์ง€์ • & ๊ธฐ๋Šฅ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Œ.

์†์„ฑ๋ช…

  - element์— ์ ์šฉ๋  ์Šคํƒ€์ผ ๋“ฑ๋“ฑ...

์†์„ฑ ๊ฐ’

  - ์†Œ๋ฌธ์ž๋กœ!    โ‡จ HTML5 ํ‘œ์ค€์—์„œ๋Š” ์†์„ฑ๋ช…์— ๋Œ€๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ X

  - ""('') ์‚ฌ์šฉํ•˜๊ธฐ

๋‚ด์šฉ

  - ํ™”๋ฉด์— ํ‘œ์‹œ๋  ๋‚ด์šฉ

์‹œ์ž‘ ํƒœ๊ทธ์™€ ์ข…๋ฃŒ ํƒœ๊ทธ

  - element์˜ ์‹œ์ž‘๊ณผ ๋ ์ง€์ •

  - ์ข…๋ฃŒ ํƒœ๊ทธ๊ฐ€ ์•ˆ ์“ฐ์ผ ๋•Œ๋„ ์žˆ์Œ   ex. <img>, <br> ...

์ด์ œ ํ•˜๋‚˜์”ฉ ์•Œ์•„๊ฐ€๋ณด์ž.

1๏ธโƒฃ Custom Elements

์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ HTML element๋ฅผ ๋งŒ๋“ค๊ฒŒ ํ•ด์ค€๋‹ค. ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ณ  ๋ฐ˜๋ณต๋˜๋Š” ์š”์†Œ๋“ค์„ ๋ชจ์•„๋†“์•„์„œ ํ‚ค์›Œ๋“œ๋กœ ๋Œ€์ฒดํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

class MyComponent extends HTMLElement {...}
customElements.define("my-component", MyComponent)

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์€ ์œ„์™€ ๊ฐ™๋‹ค. 

HTMLElement๋ฅผ extendsํ•œ ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. HTMLElement๋Š” window ๋ธŒ๋ผ์šฐ์ €์˜ ๋‚ด์žฅ ๊ฐ์ฒด์ด๋‹ค.

์œ„์—์„œ customElements.define()์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

customElement ๋˜ํ•œ window ๋ธŒ๋ผ์šฐ์ €์˜ ๋‚ด์žฅ ๊ฐ์ฒด์ด๋ฉฐ, ํ˜ธ์ถœ ์‹œ CustomElementRegistry ๊ฐ์ฒด์˜ ์ฐธ์กฐ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์ด ์ปค์Šคํ…€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ดํ”„ ์‚ฌ์ดํด ์ฝœ๋ฐฑ์ด ์กด์žฌํ•œ๋‹ค.

  • connectedCallback:  component๊ฐ€ DOM์— ์ถ”๊ฐ€ ๋ฌ์„ ๋•Œ ํ˜ธ์ถœ
  • disconnectedCallback:  component ๋‚ด์— element๊ฐ€ ์ œ๊ฑฐ๋  ๋•Œ ๋งˆ๋‹ค ํ˜ธ์ถœ
  • observedAttributes:  ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•  ์†์„ฑ๋“ค์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ
  • attributeChangedCallback:  observedAttributes์—์„œ ๋ชจ๋‹ˆํ„ฐ๋ง ์ค‘์ธ ์†์„ฑ๋“ค์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ
  • adoptedCallback:  element๊ฐ€ ์ƒˆ๋กœ์šด document๋กœ ์ด๋™๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
class MyComponent extends HTMLElement {
  // element ์ƒ์„ฑ์‹œ ๋ฌด์กฐ๊ฑด ์ž‘์„ฑ!!
  constructor() {
    super();
    ...
  }
  connectedCallback() {...}
  disconnectedCallback() {...}
  static get observedAttributes() {return [...];}
  attributeChangedCallback(name, oldValue, newValue) {...}
  adoptedCallback() {...}
}

 

2๏ธโƒฃ Shadow DOM

๋…๋ฆฝ์ ์ธ DOM ํŠธ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ์ˆ . ์บก์Аํ™”๋ฅผ ํ†ตํ•ด ๋งˆํฌ์—… ๊ตฌ์กฐ, ์Šคํƒ€์ผ, ๋กœ์ง์„ ์ˆจ๊ธฐ๊ณ  ํŽ˜์ด์ง€ ๋‚ด ๋‹ค๋ฅธ ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌ๋˜์–ด ์ฝ”๋“œ ์ถฉ๋Œ์ด ์—†๊ฒŒ ๋งŒ๋“ค์–ด ์ค€๋‹ค. ์ฆ‰, ๋…๋ฆฝ์ ์ธ ์Šค์ฝ”ํ”„๋ฅผ ๊ฐ€์ง„๋‹ค.

๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ shadow DOM์€ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์™ธ๋ถ€ JS์—์„œ์˜ shadowRoot ์ ‘๊ทผ ๊ฐ€๋Šฅ์„ฑ์— ๋”ฐ๋ผ ๋ชจ๋“œ๊ฐ€ ๋‚˜๋‰œ๋‹ค.

  • ์—ด๋ฆฐ ๋ชจ๋“œ:  ์™ธ๋ถ€ ์ ‘๊ทผ ๊ฐ€๋Šฅ     (mode: "open")
  • ๋‹ซํžŒ ๋ชจ๋“œ:  ์™ธ๋ถ€ ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅ ⇒ ํฌ๋กค๋ง ๋ฐฉ์ง€ ๊ฐ€๋Šฅ.    (mode: "closed")

< ๊ตฌ์กฐ >

  • Shadow Host:  Shadow DOM์ด ๋ถ€์ฐฉ๋˜๋Š” ์ผ๋ฐ˜ DOM์˜ ์š”์†Œ
  • Shadow Root:  Shadow ํŠธ๋ฆฌ์˜ ๋ฃจํŠธ ๋…ธ๋“œ
  • Shadow Tree:  Shadow DOM ๋‚ด๋ถ€์˜ DOM ํŠธ๋ฆฌ
  • Shadow boundary:  ๊ฒฝ๊ณ„

โ‡จ ์›๋ž˜ document tree์™€ shadow tree๋ฅผ ํ•ฉ์ณ์„œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด Flattened Tree๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

 

 

 

class MyComponent extends HTMLElement {
  constructor() {
    super();
    // Shadow DOM ์‚ฌ์šฉ
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
   connectedCallback() {
      const navBtns = this.shadowRoot.querySelector('.nav-btn.left');
      navBtns.classList.add('hidden');
   }
}

์œ„์—์„œ ์„ค๋ช…ํ•œ custom element ๊ตฌ์„ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

this.shadowRoot.appendChild(template.content.cloneNode(true)) ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜์ž๋ฉด...

shadowRoot์— ์ž์‹ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฒ ๋‹ค๋Š” ๊ฑฐ๋‹ค. template.content.cloneNode(true)  ๋Š” ์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.

 

์ฝœ๋ฐฑ์—์„œ ํŠน์ • element๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” this.shadowRoot ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

3๏ธโƒฃ HTML ํ…œํ”Œ๋ฆฟ

<template>

๋‹คํ๋จผํŠธ๊ฐ€ ์ฒ˜์Œ ๋กœ๋“œ๋  ๋•Œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์ง€๋งŒ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Ÿฐํƒ€์ž„์— ๋‚˜ํƒ€๋‚˜๋Š” HTML ์กฐ๊ฐ์„ ํฌํ•จํ•œ๋‹ค.

๋‚ด๋ถ€ ์š”์†Œ๋กœ content๋ฅผ ๊ฐ€์ง„๋‹ค.

 

์œ„์—์„œ shadowRoot์— template.content.cloneNode(true) ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

์—ฌ๊ธฐ์„œ template๋Š” template element๊ฐ€ ์ง€์ •๋œ const ๋ณ€์ˆ˜์ด๋‹ค. ๋”ฐ๋ผ์„œ ์œ„ ์ฝ”๋“œ๋Š” template element ์•ˆ์— ์„ ์–ธ๋œ ๋‚ด์šฉ์„ ๋ณต๋ถ™ํ•˜๊ฒ ๋‹ค๋Š” ๋œป์ด๋‹ค.

const template = document.createElement('template');
template.innerHTML = `
    <link rel="stylesheet" type="text/css" href="../assets/style/header.css">
    <header>
        <div class="header-contents">    
            <div class="nav-btn left">
                 <img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋’ค๋กœ๊ฐ€๊ธฐ">
            </div>
            <div class="project-name">์„œ๋น„์Šค ํƒ€์ดํ‹€</div>
            <div class="nav-btn right">
                <img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์•ž์œผ๋กœ๊ฐ€๊ธฐ">
            </div>
        </div>
    </header>
`;

 

๋งจ ์œ„์˜ ์˜ˆ์‹œ์—์„œ header๋ฅผ template ์š”์†Œ๋กœ ๋”ฐ๋กœ ๋นผ๋ฉด ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.

์ง€๊ธˆ์€ style์„ stylesheet๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์ง€๋งŒ, style element๋ฅผ ๋งŒ๋“ค์–ด์„œ template์— ๋ถ™์—ฌ๋„ ๋œ๋‹ค.

const styleEl = document.createElement('style');
styleEl.textContent = `
  header {
    background-color: #f8f8f8;
  }
  .header-contents {
    padding: 10px;
  }
`;

// template์˜ content์— style ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ (๋ณดํ†ต ๋งจ ์œ„์— ์ถ”๊ฐ€)
template.content.prepend(styleEl);

 

์ด์ œ custom element, shadow DOM, HTML ํ…œํ”Œ๋ฆฟ์„ ์ ์ ˆํ•˜๊ฒŒ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋‚˜๋งŒ์˜ ์ปค์Šคํ…€ ์›น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค!!

๊ทธ๋Ÿผ ์ด์ œ 3๊ฐœ์˜ ํŒŒ์ผ ๋ง๊ณ ! 1๊ฐœ์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋งŒ ํ•œ ๊ฐ€์ง€ ์•„์‰ฌ์šด ์ ์€ HTML๊ณผ CSS ์ฝ”๋“œ๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์•ˆ์— ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ „๋ถ€ ์ฃผํ™ฉ์ƒ‰์œผ๋กœ ๋‚˜์˜จ๋‹ค..... ๊ฐ€๋…์„ฑ์ด ์ข€ ๋–จ์–ด์ง€๊ธด ํ•˜๋‹ค...

์ด ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•œ ๊ฒƒ์ด jsx ์ธ ๋“ฏํ•˜๋‹ค!

 

์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฐœ๋ฐœ ์ค‘ ๊ฒŒ์‹œ๊ธ€์— ์ด๋ฏธ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€ GPT๋กœ ๋ถ€ํ„ฐ ๋‹ต๋ณ€์„ ๋“ค์—ˆ๋‹ค.

์šฐ์„  ๋‚ด ๊ณ ๋ฏผ์€ ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋ฅผ ์“ธ ๊ฒƒ์ด๋ƒ ์•„๋‹ˆ๋ฉด background-img ์†์„ฑ์„ ์“ธ ๊ฒƒ์ด๋ƒ์˜€๋‹ค. GPT๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๋ฅผ ๊ณ ๋ คํ•ด์„œ ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ์ถ”์ฒœํ•ด ์คฌ๋‹ค.

์—ฌ๊ธฐ์„œ ์žฌ๋ฏธ์žˆ๋˜ ์ ์€ ์ถ”์ฒœ ์ด์œ  ์ค‘ ํ•˜๋‚˜๊ฐ€ alt ์†์„ฑ์ด๋ผ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ alt๋Š” ์ด๋ฏธ์ง€ ์ฃผ์„์ฒ˜๋Ÿผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ.... 

์•ž์œผ๋กœ alt ์™€ title ์†์„ฑ๋„ ์ž˜ ๊ณ ๋ คํ•ด์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ์•ผ๊ฒ ๋‹ค. 


alt ์†์„ฑ

  ์ด๋ฏธ์ง€์˜ ๋Œ€์•ˆ, ์ด๋ฏธ์ง€์— ๋Œ€ํ•œ ์„ค๋ช…์ด๋‹ค. ์ด๋ฏธ์ง€ ํƒœ๊ทธ์—๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

<img src="../src.png" alt="์ด๋ฏธ์ง€์— ๋Œ€ํ•œ ์„ค๋ช…"/>
  • ์ธํ„ฐ๋„ท ์†๋„๊ฐ€ ๋А๋ฆฌ๊ฑฐ๋‚˜ ์ปดํ“จํ„ฐ ์‚ฌ์–‘ ๋“ฑ์˜ ๋ฌธ์ œ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•ด์˜ค์ง€ ๋ชปํ–ˆ์„ ๋•Œ, alt ํ…์ŠคํŠธ๋กœ ๋Œ€์ฒด๋œ๋‹ค.
  • ์Šคํฌ๋ฆฐ ๋ฆฌ๋”(์ฃผ๋กœ ์‹œ๊ฐ ์žฅ์• ์ธ๋“ค์ด ์‚ฌ์šฉ)๊ฐ€ ์ฝ๋Š” ๊ฐ’์ด๋‹ค.
  • SEO ๐Ÿ‘  (๊ฒ€์ƒ‰ ์—”์ง„์ด img๋Š” ๋ชป์ฝ์œผ๋ฏ€๋กœ alt๋ฅผ ์ฝ๋Š”๋‹ค.)

title ์†์„ฑ

  ์ด๋ฏธ์ง€ ์™ธ์˜ ํƒœ๊ทธ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ์†Œ์Šค hover ์‹œ ํ’์„  ๋„์›€๋ง์„ ๋œจ๊ฒŒ ํ•œ๋‹ค.

๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ค๋ด์š”
  • ์›นํ‘œ์ค€์—์„œ๋Š” aํƒœ๊ทธ์— title ์†์„ฑ์€ ํ•„์ˆ˜ํ•ญ๋ชฉ์ž„
  • ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ์—์„œ๋Š” ํ’์„  ๋„์›€๋ง์ด ๋œจ์ง€ ์•Š๋Š”๋‹ค. ์ •ํ™•ํžˆ๋Š” ๋งˆ์šฐ์Šค hover ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ทธ๋Ÿฌํ•˜๋‹ค. ์ด๋Š” ์ „์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์— ์˜์กดํ•œ๋‹ค. ์ผ๋ถ€ ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฒฝ์šฐ, ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๋ฉด title ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

tailwindCSS๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๋ ค๊ณ  ํ•˜๋‹ค๊ฐ€ ๋ฌธ๋“ ์ด๊ฒŒ ๋ญ์ง€? ํ•˜๋Š” ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค.... ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด์ž.


Bootstrap

https://getbootstrap.kr/

 

Bootstrap

๊ฐ•๋ ฅํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๋ฉฐ ๊ธฐ๋Šฅ์ด ํ’๋ถ€ํ•œ ํ”„๋ก ํŠธ์—”๋“œ ํˆดํ‚ท. Sass๋กœ ๋นŒ๋“œ ๋ฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๊ณ , ์‚ฌ์ „ ๋นŒ๋“œ๋œ ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ ๋ฐ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ™œ์šฉํ•˜๊ณ , ๊ฐ•๋ ฅํ•œ JavaScript ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ํ”„๋กœ์ ํŠธ์— ์ƒ๊ธฐ

getbootstrap.kr

๋ฌด๋ฃŒ or ์œ ๋ฃŒ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํŒŒ๋Š” ๋งˆ์ผ“.

๋กœ์ปฌ์— ์„ค์น˜ ํ›„ ๋งˆ์ผ“์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” ํด๋ž˜์Šค ๋ช…์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜: ๋ฏธ๋ฆฌ ๋””์ž์ธ๋œ UI ์ปดํฌ๋„ŒํŠธ(๋ฒ„ํŠผ, ๋„ค๋น„๊ฒŒ์ด์…˜, ๋ชจ๋‹ฌ ๋“ฑ)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘: ํ‘œ์ค€ํ™”๋œ ๋””์ž์ธ๊ณผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ ์›น์‚ฌ์ดํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ผ๊ด€๋œ ๋””์ž์ธ: ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ์Šคํƒ€์ผ ๋•๋ถ„์— ์ „์ฒด์ ์ธ ๋””์ž์ธ์˜ ์ผ๊ด€์„ฑ์„ ์‰ฝ๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•œ๊ณ„: ๊ธฐ๋ณธ ์Šคํƒ€์ผ์„ ์žฌ์ •์˜ํ•˜๊ฑฐ๋‚˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๋ ค๋ฉด CSS๋‚˜ Sass ๋ณ€์ˆ˜ ๋“ฑ์„ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํด๋ž˜์Šค ์ด๋ฆ„: ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„ ๊ธฐ๋ฐ˜์˜ ํด๋ž˜์Šค(์˜ˆ: btn, navbar)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Tailwind CSS

https://tailwindcss.com/

์ž‘์„ฑ์ด ์‰ฝ๋„๋ก ์ •ํ•ด๋†“์€ css ๊ทœ์น™์„ ์ •์˜

๋กœ์ปฌ์— ์„ค์น˜ ํ›„ ์‚ฌ์šฉ

  • ์œ ํ‹ธ๋ฆฌํ‹ฐ ์šฐ์„ (Utility-First): ๋ฏธ๋ฆฌ ์ •์˜๋œ ์ž‘์€ ๋‹จ์œ„์˜ ํด๋ž˜์Šค๋“ค(์˜ˆ: p-4, text-center, bg-blue-500)์„ ์กฐํ•ฉํ•˜์—ฌ ๋””์ž์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋†’์€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•: ๊ธฐ๋ณธ ๋””์ž์ธ์ด ๊ฑฐ์˜ ์—†์œผ๋ฉฐ, ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์กฐํ•ฉํ•ด ์ž์œ ๋กญ๊ฒŒ ๋””์ž์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์žฌ์‚ฌ์šฉ์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜: ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์ˆ˜์ •์ด ์šฉ์ดํ•˜์ง€๋งŒ, ํด๋ž˜์Šค๊ฐ€ HTML ์š”์†Œ์— ๋งŽ์ด ๋ถ™์–ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํผํฌ๋จผ์Šค ์ตœ์ ํ™”: PurgeCSS ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” CSS๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ์ตœ์ข… ๋นŒ๋“œ ํฌ๊ธฐ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜: ๋””์ž์ธ์˜ ์œ ์—ฐ์„ฑ๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ์ค‘์‹œํ•˜๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ธ๊ธฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

 

  Bootstrap Tailwind CSS
  ๋ฏธ๋ฆฌ ๋””์ž์ธ๋œ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜
์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์šฉ์ด X(๋ณต์žกํ•จ) O
์ปดํฌ๋„ŒํŠธ ๋‹ค์–‘ํ•œ UI ์ปดํฌ๋„ŒํŠธ(๋ฒ„ํŠผ, ๋ชจ๋‹ฌ ๋“ฑ) ์ œ๊ณต ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ ์—†์Œ, ์ง์ ‘ ์กฐํ•ฉํ•˜์—ฌ ๋””์ž์ธ
๊ฐœ๋ฐœ ์†๋„ FAST FAST
HTML ํด๋ž˜์Šค ์˜๋ฏธ ์ค‘์‹ฌ ํด๋ž˜์Šค (์˜ˆ: btn, navbar) ๊ธฐ๋Šฅ ์ค‘์‹ฌ ํด๋ž˜์Šค (์˜ˆ: p-4, bg-blue-500)
ํŒŒ์ผ ํฌ๊ธฐ ๋ฐ ์ตœ์ ํ™” ๊ธฐ๋ณธ ์Šคํƒ€์ผ์ด ๋งŽ์•„ ๋น„๊ต์  ๋ฌด๊ฑฐ์šธ ์ˆ˜ ์žˆ์Œ PurgeCSS๋กœ ์ตœ์ ํ™”ํ•˜์—ฌ ํŒŒ์ผ ํฌ๊ธฐ ์ค„์ผ ์ˆ˜ ์žˆ์Œ
์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฐ ์ง€์› ์˜ค๋žœ ๊ธฐ๊ฐ„ ์‚ฌ์šฉ๋˜์–ด ์•ˆ์ •์ ์ธ ์ƒํƒœ๊ณ„์™€ ๋ฌธ์„œ ์ œ๊ณต ์ตœ์‹  ํŠธ๋ Œ๋“œ์— ๋งž๋Š” ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ์™€ ํ™œ๋ฐœํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ

 

+ Recent posts