๐Ÿ’ก 20. ๋‹คํ˜•์„ฑ(Polymorphism)๊ณผ ์ ‘๊ทผ์„ฑ(a11y)

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

๋•Œ๋กœ๋Š” <button>์œผ๋กœ, ๋•Œ๋กœ๋Š” <a> ํƒœ๊ทธ๋‚˜ <Link>๋กœ ์ž์œ ์ž์žฌ๋กœ ๋ณ€์‹ ํ•˜๋Š” ๋‹คํ˜•์„ฑ ๋ Œ๋”๋ง(as prop, Slot) ์„ค๊ณ„์™€, ์‹œ๊ฐ์žฅ์• ์ธ๋„ ์™„๋ฒฝํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค๋ฌดํ˜• ์ปดํฌ๋„ŒํŠธ์˜ ๋น„๋ฐ€์„ ํŒŒํ—ค์นฉ๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์‚ฌ๋‚ด ๊ณตํ†ต Button ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์™œ ์–ด๋–จ ๋•Œ๋Š” ๋งํฌ(<a>)๋กœ ๋ณ€์‹ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์œ ์—ฐ์„ฑ์˜ ๋ณธ์งˆ์„ ๊นจ๋‹ซ๋Š”๋‹ค.
  • as prop๊ณผ Radix UI์˜ Slot ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ํƒœ๊ทธ ๊ป๋ฐ๊ธฐ๋ฅผ ๊ฐˆ์•„ ๋ผ์šฐ๋Š” '๋‹คํ˜•์„ฑ' ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜๊ณผ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”(VoiceOver)๋ฅผ ์ง€์›ํ•˜๋Š” aria- ์†์„ฑ์˜ ๊ธฐ๋ณธ๊ธฐ๋ฅผ ๊ฐ–์ถ˜ ๋ฐฐ๋ฆฌ์–ดํ”„๋ฆฌ(Barrier-Free) ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ๊ฑฐ๋“ญ๋‚œ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 10๋ถ„ / ํ•ต์‹ฌ ํŒŒํŠธ: 6๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ˆ™(๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜! ์ด๋ฒˆ์— ๋งŒ๋“  [ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ] ๋ฒ„ํŠผ ๋””์ž์ธ ๋„ˆ๋ฌด ์—๋ป์š”. ์ € ๋””์ž์ธ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ, ๋ฉ”์ธ ํŽ˜์ด์ง€ ํ•˜๋‹จ์˜ [์ด๋ฒคํŠธ ํŽ˜์ด์ง€๋กœ ์ด๋™] ๋ฒ„ํŠผ์œผ๋กœ๋„ ๋˜‘๊ฐ™์ด ๋ Œ๋”๋งํ•ด์ฃผ์‹ค ์ˆ˜ ์žˆ์ฃ ?"
  • ์˜์ฒ (์‹ ์ž…): "๋„ค! ๊ทธ์•ผ ๋‹น์—ฐํ•˜์ฃ  (์ž์‹ ๋งŒ๋งŒ). ์–ด? ๊ทผ๋ฐ [ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ]๋Š” ๋ˆŒ๋ €์„ ๋•Œ ํผ์„ ์ œ์ถœํ•˜๋Š” <button>์ธ๋ฐ... [์ด๋™] ๋ฒ„ํŠผ์€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์™„์ „ํžˆ ๋„˜์–ด๊ฐ€๋Š” <a> ํƒœ๊ทธ(Next.js Link)์—ฌ์•ผ ํ•˜์ž–์•„์š”? ๊ทธ๋Ÿผ ๋””์ž์ธ์€ ๋˜‘๊ฐ™์€๋ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ <Button>์šฉ, <LinkButton>์šฉ 2๊ฐœ๋กœ ๋”ฐ๋กœ ์ชผ๊ฐœ์•ผ ํ•˜๋‚˜์š”? CSS ์ค‘๋ณต์ด 500์ค„์ด ๋„˜์–ด๊ฐ€๋Š”๋ฐ..."
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜, ํƒœ๊ทธ ์ข…๋ฅ˜ ๋•Œ๋ฌธ์— ๊ฐ™์€ ๋””์ž์ธ์„ ๊ณ„์† ๋ณต์‚ฌํ•˜๋ฉด ๋””์ž์ธ ์‹œ์Šคํ…œ์ด ๊ธˆ๋ฐฉ ํ”๋“ค๋ฆฝ๋‹ˆ๋‹ค. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋Š” ์Šคํƒ€์ผ์€ ์œ ์ง€ํ•˜๋˜ ์‹ค์ œ ํƒœ๊ทธ๋งŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์š”. ๊ทธ๋ฆฌ๊ณ  ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์™€ ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๋„ ๊ฐ™์€ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ ‘๊ทผ์„ฑ(a11y) ์†์„ฑ๊นŒ์ง€ ๊ฐ™์ด ์„ค๊ณ„ํ•ฉ์‹œ๋‹ค."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ํƒœ๊ทธ์™€ ์Šคํƒ€์ผ์„ ๋ถ„๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ด์œ 

๋ชจ์–‘๊ณผ ํฐํŠธ ํฌ๊ธฐ, ํ˜ธ๋ฒ„(Hover) ์‹œ ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋ณ€ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜๊นŒ์ง€ ๋™์ผํ•œ ๋ฒ„ํŠผ ๋””์ž์ธ์ž…๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์–ด๋–ค ๊ฒฝ์šฐ๋Š” API๋ฅผ ์˜๋Š” type="submit" ๋ฒ„ํŠผ์ด๊ณ , ์–ด๋–ค ๊ฒฝ์šฐ๋Š” ๊ตฌ๊ธ€ ์ฐฝ์„ ์—ฌ๋Š” href="https://..." ํ˜•ํƒœ์˜ ์•ต์ปค(A) ํƒœ๊ทธ๋ผ๊ณ  ์นฉ์‹œ๋‹ค.

// โŒ ์˜์ฒ ์ด์˜ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ตฌํ˜„ (๋””์ž์ธ ์ค‘๋ณต์˜ ๋Šช)
 
// 1๋ฒˆ: ํผ ์ œ์ถœ์šฉ ๋ฒ„ํŠผ
export function DefaultButton({ children, onClick }) {
  return <button className="super-gorgeous-blue-btn" onClick={onClick}>{children}</button>;
}
 
// 2๋ฒˆ: ์™ธ๋ถ€ ๋งํฌ ์ด๋™์šฉ ๋ฒ„ํŠผ
export function LinkButton({ children, href }) {
  // CSS ํด๋ž˜์Šค ์˜คํƒ€๋ผ๋„ ๋‚˜๋ฉด ๋””์ž์ธ์ด ํ‹€์–ด์ง„๋‹ค!
  return <a className="super-gorgeous-blue-btn" href={href}>{children}</a>;
}

๋””์ž์ธ ์‹œ์Šคํ…œ(Design System)์„ ๊ตฌ์ถ•ํ•˜๋Š” ํŒ€์—์„œ ์ด๋Ÿฐ ๋ฐฉ์‹์ด ๋ฐ˜๋ณต๋˜๋ฉด ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์ด ๋น ๋ฅด๊ฒŒ ์ปค์ง‘๋‹ˆ๋‹ค. ๋””์ž์ธ(Style)๊ณผ ๊ธฐ๋Šฅ(HTML Tag)์€ ๋ถ„๋ฆฌํ•ด์„œ ์ƒ๊ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์Šคํƒ€์ผ์€ ๊ณต์œ ํ•˜๋˜ ์‹ค์ œ ๋ Œ๋”๋ง ํƒœ๊ทธ๋Š” ์ƒํ™ฉ์— ๋งž๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ๋‹คํ˜•์„ฑ(Polymorphism) ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

  1. ๊ณผ๊ฑฐ์˜ ๋ปฃ๋ปฃํ•œ ๊ฐ‘์˜ท (ํ•˜๋“œ์ฝ”๋”ฉ): "์ด ํŒŒ๋ž€์ƒ‰ ๊ฐ‘์˜ท์€ ๋ฌด์กฐ๊ฑด '๊ฒ€์‚ฌ(Button)' ์ „์šฉ์ž…๋‹ˆ๋‹ค!" ๋งˆ๋ฒ•์‚ฌ(Link)๊ฐ€ ์ž…์œผ๋ ค๊ณ  ํ•˜๋ฉด ๊ฐ™์€ ๊ฐ‘์˜ท์„ ํ•˜๋‚˜ ๋” ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋‹คํ˜•์„ฑ์˜ ๋งˆ๋ฒ• ๊ฐ‘์˜ท (Polymorphic): ์ด ํŒŒ๋ž€์ƒ‰ ๊ฐ‘์˜ท(CSS ์Šคํƒ€์ผ)์€ ์ž…๋Š” ์‚ฌ๋žŒ์˜ ์—ญํ• ์— ๋งž์ถฐ ํ˜•ํƒœ๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
    ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ์ž…์œผ๋ฉด ์ง€ํŒก์ด ๊ตฌ๋ฉ(href)์ด ๋‚˜ ์žˆ๋Š” ์˜ท์œผ๋กœ ์Šค๋ฅด๋ฅต ๋ณ€ํ•˜๊ณ , ๊ฒ€์‚ฌ๊ฐ€ ์ž…์œผ๋ฉด ์นผ๊ฝ‚์ด(type="submit")๊ฐ€ ๋‹ฌ๋ฆฐ ๋ฒ„ํŠผ์œผ๋กœ ๋ณ€์‹ ํ•˜์ฃ . ํ•˜์ง€๋งŒ ๊ฒ‰์—์„œ ๋ณผ ๋• ๋ˆˆ๋ถ€์‹  ํŒŒ๋ž€ ํ™ฉ๊ธˆ์ƒ‰ ์Šคํƒ€์ผ์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋˜‘๊ฐ™์ด ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿงฉ ๊ทน์˜ ์ฒด๋“: ์œ ์ฒด์ดํƒˆ ๋‹คํ˜•์„ฑ ์„ค๊ณ„ํ•˜๊ธฐ

๋ณดํ†ต ์‹ค๋ฌด์—์„œ๋Š” ์ด "๋ณ€์‹ " ์ง€์‹œ๋ฅผ ๋‚ด๋ฆฌ๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ํŒจํ„ด์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

โœ… ๋ฐฉ๋ฒ• 1: as Prop ํŒจํ„ด (์ „ํ†ต์˜ ๊ฐ•์ž)

๊ฐ€์žฅ ๋ฌด๋‚œํ•˜๊ณ  ๋„๋ฆฌ ์•Œ๋ ค์ง„ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. "as๋กœ ์ „๋‹ฌํ•œ ํƒœ๊ทธ๋‚˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ Œ๋”๋งํ•˜๋ผ"๋ผ๊ณ  ๋ช…์‹œํ•ด์ค๋‹ˆ๋‹ค.

// ๐ŸŽฏ as๋ฅผ ์‚ฌ์šฉํ•œ ๋‹คํ˜•์„ฑ ๋ฒ„ํŠผ ์„ค๊ณ„
export function MagicButton({ as: Component = "button", children, ...props }) {
 
  // Component์— "a"๊ฐ€ ๋“ค์–ด์˜ค๋ฉด <a> ํƒœ๊ทธ๋กœ, "button"์ด๋ฉด <button>์œผ๋กœ ๋ Œ๋”๋งํ•œ๋‹ค.
  // Next.js์˜ <Link> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„˜๊ฒจ๋„ ๊ฐ™์€ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  return (
    <Component className="super-gorgeous-blue-btn" {...props}>
      {children}
    </Component>
  );
}
 
// ------ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ (์†Œ๋น„์ž) ------
function App() {
  return (
    <div>
      {/* 1. ๊ธฐ๋ณธ์€ ์ œ์ถœ์šฉ <button>์œผ๋กœ ๋ Œ๋”๋ง ๋จ */}
      <MagicButton type="submit">๋กœ๊ทธ์ธ</MagicButton>
 
      {/* 2. ์™ธ๋ถ€ ๋งํฌ๋กœ ์ด๋™ํ•˜๋Š” <a> ํƒœ๊ทธ๋กœ ๋ Œ๋”๋ง๋œ๋‹ค. ๋””์ž์ธ์€ ๊ฐ™๋‹ค. */}
      <MagicButton as="a" href="https://google.com">
        ๊ตฌ๊ธ€์—์„œ ๋กœ๊ทธ์ธ
      </MagicButton>
 
      {/* 3. ๋ผ์šฐํŒ… ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด Next.js Link ์ปดํฌ๋„ŒํŠธ๋กœ ๋ Œ๋”๋งํ•œ๋‹ค. */}
      <MagicButton as={Link} href="/home">
        ์šฐ๋ฆฌ์ง‘์œผ๋กœ ๊ฐ€์ž
      </MagicButton>
    </div>
  )
}

์ด์ œ ํŒŒ๋ž€์ƒ‰ ๋ฒ„ํŠผ CSS๋Š” ํ•œ ๋ฒˆ(className="super-gorgeous...")๋งŒ ์„ ์–ธ๋˜๊ณ , ์—ฌ๋Ÿฌ ํƒœ๊ทธ ์—ญํ• ์— ์žฌ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

โœ… ๋ฐฉ๋ฒ• 2: Slot (asChild) ํŒจํ„ด (ํ˜„๋Œ€ Radix UI์˜ ๋Œ€์„ธ)

as ํŒจํ„ด์€ ์ง๊ด€์ ์ด์ง€๋งŒ ํ•œ๊ณ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ถ”๋ก (TypeScript Generic type infer)์„ ์ •๊ตํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋ ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๊ฝค ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. "as๊ฐ€ a๋ผ๋ฉด href๋ฅผ ํ—ˆ์šฉํ•˜๋ผ" ๊ฐ™์€ ์กฐ๊ฑด์„ ํƒ€์ž…์œผ๋กœ ํ‘œํ˜„ํ•˜๊ธฐ๊ฐ€ ๊นŒ๋‹ค๋กญ์ฃ .

๊ทธ๋ž˜์„œ Radix UI, shadcn/ui ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋งŽ์ด ์“ฐ๋Š” ๋ฐฉ์‹์ด ์ž์‹์—๊ฒŒ ๋””์ž์ธ ์†์„ฑ์„ ๋ณ‘ํ•ฉํ•˜๋Š” Slot (asChild) ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

// ๐ŸŽฏ Slot (asChild) ํŒจํ„ด์˜ ์ฒ ํ•™
import { Slot } from '@radix-ui/react-slot'; // (์›๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•œ ์œ ๋ช… ํˆด)
 
// ๋””์ž์ธ ๊ป๋ฐ๊ธฐ๋งŒ ์ฅ๊ณ  ์žˆ๋Š” ๋‹จ์ˆœ ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ
export function MasterButton({ asChild, children, ...props }) {
 
  // asChild๊ฐ€ ์ผœ์ ธ ์žˆ์œผ๋ฉด Slot์ด ์ž์‹ ์š”์†Œ๋ฅผ ์‹ค์ œ ํƒœ๊ทธ๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ
  // className, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๊ฐ™์€ props๋ฅผ ์ž์‹์—๊ฒŒ ๋ณ‘ํ•ฉํ•œ๋‹ค.
  const Comp = asChild ? Slot : "button";
 
  return (
    <Comp className="super-gorgeous-blue-btn" {...props}>
      {children}
    </Comp>
  )
}
 
// ------ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ (์†Œ๋น„์ž) ------
function App() {
  return (
    <MasterButton asChild>
      <a href="https://google.com">ํŒŒ๋ž€ ๊ตฌ๊ธ€๋ฒ„ํŠผ</a>
      {/* ๊ฒฐ๊ณผ: Slot์ด <a href="..." className="super-gorgeous..." /> ๋กœ ๋ณ‘ํ•ฉํ•ด ๋ Œ๋”๋ง! */}
    </MasterButton>
  )
}

์ด ๋ฐฉ๋ฒ•์˜ ์žฅ์ : <a> ํƒœ๊ทธ๋Š” ์ž์‹ ์ด ๊ฐ€์ง„ ๊ณ ์œ  ์†์„ฑ(href)๋งŒ ์ฑ…์ž„์ง€๊ณ , MasterButton์€ ๊ณตํ†ต ๋””์ž์ธ๊ณผ ์ด๋ฒคํŠธ๋งŒ ์ฑ…์ž„์ง‘๋‹ˆ๋‹ค. ํ”„๋กญ์Šค ๊ตฌ์กฐ๊ฐ€ ๋œ ์–ฝํžˆ๊ณ  ์‹ค์ œ ํƒœ๊ทธ์˜ ์˜๋ฏธ๋„ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.


๐Ÿ‘‚ ๋น›๊ณผ ์†Œ๋ฆฌ: ์ ‘๊ทผ์„ฑ (Accessibility, a11y) ์ฑ™๊ธฐ๊ธฐ

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

์ด๊ฒƒ์„ ์šฐ๋ฆฌ๋Š” **a11y (์ ‘๊ทผ์„ฑ)**์ด๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ, ํ˜„์—… ๋””์ž์ธ ์‹œ์Šคํ…œ์—์„œ ๋งค์šฐ ์ค‘์š”ํ•œ ํ’ˆ์งˆ ๊ธฐ์ค€์œผ๋กœ ๋ด…๋‹ˆ๋‹ค.

  1. aria- ์†์„ฑ์˜ ๋Šช: ์ € ๋ฒ„ํŠผ์ด ๋‹จ์ˆœํžˆ '์ „์†ก' ๊ธ€์ž๋งŒ ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ๋ฉด?
// ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋Š” ์ด ๋ฒ„ํŠผ์„ "์•ˆ๋‚ด๋ฌธ ์—ด๊ธฐ"๋ผ๊ณ  ์ฝ๋Š”๋‹ค.
<MagicButton aria-label="์•ˆ๋‚ด๋ฌธ ์—ด๊ธฐ" aria-expanded={isOpen} onClick={toggle}>
   ๐Ÿš€ {/* ๋ˆˆ์— ๋ณด์ด๋Š” ํ…์ŠคํŠธ ์—†์ด ์•„์ด์ฝ˜๋งŒ ์žˆ๋Š” ๋ฒ„ํŠผ์—๋Š” ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์ด ํ•„์š”ํ•˜๋‹ค. */}
</MagicButton>
  1. Tab ํฌ์ปค์Šค: ๋งˆ์šฐ์Šค๋ฅผ ์“ฐ์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž๋Š” ํ‚ค๋ณด๋“œ์˜ Tab๊ณผ Enter ํ‚ค๋งŒ์œผ๋กœ ํ™”๋ฉด์„ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋‹คํ˜•์„ฑ ๋ฒ„ํŠผ์ด div๋‚˜ span ํƒœ๊ทธ๋กœ ๋ Œ๋”๋ง ๋˜์—ˆ๋‹ค๋ฉด ํ‚ค๋ณด๋“œ ์ดˆ์ ์ด ์žกํžˆ์ง€ ์•Š์•„ ๋งˆ์šฐ์Šค๋กœ๋งŒ ํด๋ฆญ ๊ฐ€๋Šฅํ•œ ์š”์†Œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ธฐ๋ณธ ํ‚ค๋ณด๋“œ ์กฐ์ž‘์„ ์ง€์›ํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ <button> ์š”์†Œ๋‚˜ <a> ํƒœ๊ทธ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋‹คํ˜•์„ฑ ํŒจํ„ด์€ ๊ฐœ๋ฐœ์ž ์ทจํ–ฅ์„ ๋„˜์–ด ์‹ค์ œ ์‚ฌ์šฉ์„ฑ์„ ์ง€ํ‚ค๋Š” ํ•ต์‹ฌ ์„ค๊ณ„๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๊ด€์ ๋‹จ์ผ ํƒœ๊ทธ ๋ฒ„ํŠผ <button>๋‹คํ˜•์„ฑ(Polymorphism) ๋ฒ„ํŠผ <Button as="a">
์žฅ์ ๋งŒ๋“ค๊ธฐ๊ฐ€ ๊ฐ€์žฅ ๋น ๋ฅด๋‹ค.๋””์ž์ธ CSS์˜ ๋‹จ์ผ ์†Œ์Šค ์›์น™. ํ•œ ๋ฒˆ ๋ฐ”๊ฟ”๋„ ์—ฌ๋Ÿฌ ๋งํฌ ๋ฒ„ํŠผ๊ณผ ์ œ์ถœ ๋ฒ„ํŠผ์— ๊ฐ™์€ ์Šคํƒ€์ผ์ด ์ ์šฉ๋œ๋‹ค.
๋‹จ์  / ์ œ์•ฝhref๋ฅผ ์“ธ ์ˆ˜ ์—†๊ณ  ๋ฌด๋ฆฌํ•˜๊ฒŒ window.location.href= ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๊ฒ€์ƒ‰ ์—”์ง„(๊ฒ€์ƒ‰ ๋…ธ์ถœ)์ด ๊นจ์ง„๋‹ค.์ž‘์„ฑํ•  ๋•Œ TypeScript ํƒ€์ž… ์„ค๊ณ„๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค. (์ตœ๊ทผ์—” Slot ํŒจํ„ด์ด ์ด ๋‹จ์ ์„ ์ƒ์‡„ ์ค‘)
์ ‘๊ทผ์„ฑ(a11y)์˜ ์ค‘์š”์„ฑ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋„ ์ด ์š”์†Œ๊ฐ€ ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•œ๋‹ค. ์•„์ด์ฝ˜ ์ „์šฉ ๋ฒ„ํŠผ์ด๋ฉด ๊ผญ aria-label์„ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"๋ชจ์–‘์€ ๊ฐ™๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ํƒœ๊ทธ์˜ ์˜๋ฏธ๋Š” ์ƒํ™ฉ์— ๋งž์ถฐ ๋ฐ”๊ฟ”์•ผ ํ•  ๋•Œ, as์™€ asChild๋Š” ๊ฐ€์žฅ ์‹ค์šฉ์ ์ธ ์„ ํƒ์ง€๋‹ค."


๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๋‹น์‹ ์ด ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ(Design System) ๊ฐœ๋ฐœํŒ€์— ํ•ฉ๋ฅ˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒซ ์ž„๋ฌด๋กœ Button ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค๊ณ„ํ•˜๋Š”๋ฐ, ๊ธฐ์กด ํŒ€์› ์˜์ฒ ์ด๊ฐ€ ๋งŒ๋“ค์–ด๋‘” ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์‹œ๋‹ˆ์–ด์˜ ๊ด€์ ์—์„œ, ๋‹คํ˜•์„ฑ๊ณผ SEO, ์ ‘๊ทผ์„ฑ(a11y) ์ธก๋ฉด์˜ ๊ฐ€์žฅ ๋ฌธ์ œ๊ฐ€ ํฐ ๋‹จ์ ์„ ๊ณจ๋ผ๋ณด์„ธ์š”.

function SimpleButton({ title, url }) {
  // ์‚ฌ์šฉ์ž๊ฐ€ url์„ ๋„˜๊ธฐ๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๊ฐ•์ œ๋กœ ์ฐฝ์„ ์ด๋™์‹œํ‚จ๋‹ค!
  const pushLink = () => window.location.href = url;
 
  return <div className="btn" onClick={url ? pushLink : undefined}>{title}</div>
}
  • A) div๋ฅผ ๋ Œ๋”๋งํ•˜๋ฉด React๊ฐ€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ด๋ฅผ ๊ฑฐ๋ถ€ํ•œ๋‹ค.
  • B) ์ด ๋ฒ„ํŠผ์€ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ์—”์ง„ ๋กœ๋ด‡(Bot)์ด ํŒŒ์‹ฑํ•  ๋•Œ ๊ทธ์ € "์•„๋ฌด๋Ÿฐ ์ด๋™ ๊ธฐ๋Šฅ์ด ์—†๋Š” ๊ธ€์ž ํ•œ ์ค„(div๋ธ”๋ก)"์œผ๋กœ ์ทจ๊ธ‰ํ•˜๋ฏ€๋กœ ์‚ฌ์ดํŠธ ๊ฐ„ ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ(SEO)๊ฐ€ ๊นจ์ง„๋‹ค. ๋˜ํ•œ, ํ‚ค๋ณด๋“œ Tab ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋Š” ์‹œ๊ฐ/์ง€์ฒด ์žฅ์• ์ธ ์œ ์ €๋“ค์ด ์ด ๋ฒ„ํŠผ์— ๋„๋‹ฌ์กฐ์ฐจ ํ•  ์ˆ˜ ์—†๊ณ , ์—”ํ„ฐ ํ‚ค๋ฅผ ์ณ๋„ ์•„๋ฌด ์ผ๋„ ๋ฒŒ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค.
  • C) ์ด ๋ฒ„ํŠผ์€ div ํƒœ๊ทธ๋กœ ์–ฝ๋งค์—ฌ ์žˆ๊ธฐ์— ์Šคํƒ€์ผ(className="btn")์ด ๋ฐ–์œผ๋กœ ์ƒˆ์–ด ๋‚˜๊ฐ€ ๊ธ€๋กœ๋ฒŒ ์˜ค์—ผ์„ ์ผ์œผํ‚ค๋Š” ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•œ๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: div์— onClick์„ ๋ถ™์—ฌ ์•ต์ปค ์ด๋™์„ ํ‰๋‚ด ๋‚ด๋ฉด ๋งํฌ์˜ ์˜๋ฏธ, ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ, ๊ฒ€์ƒ‰ ์—”์ง„์ด ์ดํ•ดํ•˜๋Š” ์—ฐ๊ฒฐ ๊ตฌ์กฐ๊ฐ€ ๋ชจ๋‘ ์•ฝํ•ด์ง‘๋‹ˆ๋‹ค. ์ด๋™ ๋™์ž‘์€ ์‹ค์ œ <a>๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์˜ Link๋กœ ํ‘œํ˜„ํ•˜๊ณ , ๋ฒ„ํŠผ ๋™์ž‘์€ ๋„ค์ดํ‹ฐ๋ธŒ <button>์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค.

Q2. ์ตœ๊ทผ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Radix UI ๋“ฑ)๊ฐ€ ์ œ๊ณตํ•˜๋Š” asChild ํ˜น์€ Slot ํŒจํ„ด์ด, ๊ธฐ์กด as="a" ๋ฐฉ์‹๋ณด๋‹ค ์„ ํ˜ธ๋˜๋Š” ํ•ต์‹ฌ์ ์ธ ๊ฐœ๋ฐœ ๊ฒฝํ—˜(DX) ์ด์œ ๋ฅผ ์ฃผ๊ด€์‹์œผ๋กœ ์„ค๋ช…ํ•ด๋ณด์„ธ์š”.
โœ… ์ •๋‹ต ๋ฐ ์ฃผ๊ด€์‹ ํ•ด์„ค:
"๊ธฐ์กด as="a" ๋ฐฉ์‹์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ href๋‚˜ target="_blank" ๊ฐ™์€ ์ž์‹ ํƒœ๊ทธ์˜ ์†์„ฑ๊นŒ์ง€ ๋„“๊ฒŒ ๋ฐ›์•„์•ผ ํ•ด์„œ ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด ๋ณต์žกํ•ด์ง€๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด asChild(Slot) ํŒจํ„ด์€ ์‹ค์ œ ํƒœ๊ทธ๋ฅผ ์ž์‹์ด ์ฑ…์ž„์ง€๊ณ , ๋ถ€๋ชจ๋Š” ๊ณตํ†ต ์Šคํƒ€์ผ๊ณผ ์ด๋ฒคํŠธ๋งŒ ๋ณ‘ํ•ฉํ•˜๋ฏ€๋กœ props ์ถฉ๋Œ๊ณผ ํƒ€์ž… ๋ถ„๊ธฐ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์˜์ˆ™์ด ์•„์ด์ฝ˜๋งŒ ๋ณด์ด๋Š” "์•Œ๋ฆผ ์„ค์ • ์—ด๊ธฐ" ๋ฒ„ํŠผ์„ ๋””์ž์ธ ์‹œ์Šคํ…œ์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ์Šคํƒ€์ผ์€ <button>๊ณผ <a> ์–‘์ชฝ์—์„œ ์žฌ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•˜๊ณ , ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž์™€ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ํ˜ธ๊ฐ€ ์Šน์ธํ•  ๊ตฌํ˜„์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) <div onClick>์— CSS๋ฅผ ์ž…ํžˆ๊ณ , ์•„์ด์ฝ˜๋งŒ ๋„ฃ๋Š”๋‹ค. ๋งˆ์šฐ์Šค๋กœ ํด๋ฆญ๋˜๋ฏ€๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.
  • B) ๊ธฐ๋ณธ ๋™์ž‘์€ ๋„ค์ดํ‹ฐ๋ธŒ <button>์œผ๋กœ ๋‘๊ณ , ๋งํฌ๊ฐ€ ํ•„์š”ํ•  ๋•Œ๋Š” as๋‚˜ asChild๋กœ ์‹ค์ œ <a>/Link๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค. ์•„์ด์ฝ˜ ์ „์šฉ ๋ฒ„ํŠผ์—๋Š” aria-label ๊ฐ™์€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์„ ์ œ๊ณตํ•œ๋‹ค.
  • C) ๋ชจ๋“  ๋ฒ„ํŠผ์„ <a href="#">๋กœ ํ†ต์ผํ•˜๊ณ , ์ œ์ถœ ๋™์ž‘์€ preventDefault()๋กœ ํ‰๋‚ด ๋‚ธ๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ๋””์ž์ธ ์‹œ์Šคํ…œ์˜ ๋‹คํ˜•์„ฑ์€ "๋ชจ์–‘์€ ๊ฐ™๊ฒŒ, ์˜๋ฏธ๋Š” ์ •ํ™•ํ•˜๊ฒŒ" ๋งŒ๋“œ๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ํด๋ฆญ ์•ก์…˜์€ <button>, ์ด๋™์€ <a>/Link๊ฐ€ ๋งก์•„์•ผ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ํ‚ค๋ณด๋“œ ๋™์ž‘, SEO, ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์˜๋ฏธ๊ฐ€ ์‚ด์•„๋‚ฉ๋‹ˆ๋‹ค. ์•„์ด์ฝ˜๋งŒ ์žˆ๋Š” ์ปจํŠธ๋กค์€ ๋ณด์ด๋Š” ํ…์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฏ€๋กœ aria-label์ฒ˜๋Ÿผ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ํ˜ธ๋ผ๋ฉด CSS ์žฌ์‚ฌ์šฉ๋ณด๋‹ค ํƒœ๊ทธ ์˜๋ฏธ์™€ ์ ‘๊ทผ์„ฑ ๊ณ„์•ฝ์„ ๋จผ์ € ํ™•์ธํ•  ๊ฒ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

๋””์ž์ธ์ด ๊ฐ™๋‹ค๋Š” ์ด์œ ๋กœ ๋งํฌ๋„ ๋ฒ„ํŠผ๋„ ์ „๋ถ€ ํ•œ ์ปดํฌ๋„ŒํŠธ๋กœ ์šฐ๊ฒจ ๋„ฃ๋˜ ์ฝ”๋“œ๊ฐ€ ๋– ์˜ฌ๋ž๋‹ค. ์˜ค๋Š˜์€ ๋‹คํ˜•์„ฑ์ด ๋‹จ์ˆœํžˆ CSS ์ค‘๋ณต์„ ์ค„์ด๋Š” ํŒจํ„ด์ด ์•„๋‹ˆ๋ผ, ๊ฐ™์€ ์‹œ๊ฐ ๋””์ž์ธ ์•ˆ์—์„œ๋„ HTML ์˜๋ฏธ๋ฅผ ์ •ํ™•ํžˆ ๋ณด์กดํ•˜๋Š” ์„ค๊ณ„๋ผ๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค.

๐Ÿ’ก "๋””์ž์ธ์€ ๊ณต์œ ํ•˜๋˜ ์˜๋ฏธ๋Š” ์†์ด์ง€ ๋ง์ž. ์ด๋™์€ ๋งํฌ, ์‹คํ–‰์€ ๋ฒ„ํŠผ, ์•„์ด์ฝ˜ ์ „์šฉ ์ปจํŠธ๋กค์€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„๊นŒ์ง€๊ฐ€ ๊ณ„์•ฝ์ด๋‹ค."

์˜ํ˜ธ ์„ ๋ฐฐ๊ฐ€ "์ปดํฌ๋„ŒํŠธ API๊ฐ€ ์˜ˆ๋ป๋„ ํ‚ค๋ณด๋“œ๋กœ ๋ชป ์“ฐ๋ฉด ๋””์ž์ธ ์‹œ์Šคํ…œ์ด ์•„๋‹ˆ๋‹ค"๋ผ๊ณ  ํ•œ ๊ฒŒ ์˜ค๋ž˜ ๋‚จ๋Š”๋‹ค. ๋‚ด์ผ๋ถ€ํ„ฐ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ทฐํ•  ๋•Œ๋Š” as/asChild ํƒ€์ž…๋ณด๋‹ค ๋จผ์ € ์‹ค์ œ ๋ Œ๋”๋ง ํƒœ๊ทธ, ํฌ์ปค์Šค ๊ฐ€๋Šฅ ์—ฌ๋ถ€, ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„, ๋งํฌ์˜ SEO ์˜๋ฏธ๋ฅผ ์ฒดํฌํ•˜๊ฒ ๋‹ค. ์˜์ฒ ๋„ ์ด์ œ ์˜ˆ์œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค.