๐ก 20. ๋คํ์ฑ(Polymorphism)๊ณผ ์ ๊ทผ์ฑ(a11y)
๐ ๊ฐ์
๋๋ก๋ <button>์ผ๋ก, ๋๋ก๋ <a> ํ๊ทธ๋ <Link>๋ก ์์ ์์ฌ๋ก ๋ณ์ ํ๋ ๋คํ์ฑ ๋ ๋๋ง(as prop, Slot) ์ค๊ณ์, ์๊ฐ์ฅ์ ์ธ๋ ์๋ฒฝํ ์ฌ์ฉํ ์ ์๋ ๋๊ธฐ์ ๊ธ ์ปดํฌ๋ํธ์ ๋น๋ฐ์ ํํค์นฉ๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ฌ๋ด ๊ณตํต
Button์ปดํฌ๋ํธ๊ฐ ์ ์ด๋จ ๋๋ ๋งํฌ(<a>)๋ก ๋ณ์ ํด์ผ ํ๋์ง ์ ์ฐ์ฑ์ ๋ณธ์ง์ ๊นจ๋ซ๋๋ค.asprop๊ณผ Radix UI์Slotํจํด์ ์ฌ์ฉํ์ฌ ๋ง๋ฒ์ฒ๋ผ ํ๊ทธ ๊ป๋ฐ๊ธฐ๋ฅผ ๊ฐ์ ๋ผ์ฐ๋ '๋คํ์ฑ' ์ปดํฌ๋ํธ๋ฅผ ์ค๊ณํ ์ ์๋ค.- ํค๋ณด๋ ๋ค๋น๊ฒ์ด์ ๊ณผ ์คํฌ๋ฆฐ ๋ฆฌ๋(VoiceOver)๋ฅผ ์ง์ํ๋
aria-์์ฑ์ ๊ธฐ๋ณธ๊ธฐ๋ฅผ ๊ฐ์ถ ๋ฐฐ๋ฆฌ์ดํ๋ฆฌ(Barrier-Free) ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ก ๊ฑฐ๋ญ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ: '๊ป๋ฐ๊ธฐ'์ ์ ์ฃผ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ ๊ทน์ ์ฒด๋: ์ ์ฒด์ดํ ๋คํ์ฑ ์ค๊ณํ๊ธฐ
- ๐ ๋น๊ณผ ์๋ฆฌ: ์ ๊ทผ์ฑ (Accessibility, a11y) ์ฑ๊ธฐ๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 10๋ถ / ํต์ฌ ํํธ: 6๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์(๋์์ด๋): "์์ฒ ๋! ์ด๋ฒ์ ๋ง๋ [ํ์๊ฐ์ ์๋ฃ] ๋ฒํผ ๋์์ธ ๋๋ฌด ์๋ป์. ์ ๋์์ธ ๊ทธ๋๋ก ๋ณต์ฌํด์, ๋ฉ์ธ ํ์ด์ง ํ๋จ์ [์ด๋ฒคํธ ํ์ด์ง๋ก ์ด๋] ๋ฒํผ์ผ๋ก๋ ๋๊ฐ์ด ๋ ๋๋งํด์ฃผ์ค ์ ์์ฃ ?"
- ์์ฒ (์ ์
): "๋ค! ๊ทธ์ผ ๋น์ฐํ์ฃ (์์ ๋ง๋ง). ์ด? ๊ทผ๋ฐ [ํ์๊ฐ์
์๋ฃ]๋ ๋๋ ์ ๋ ํผ์ ์ ์ถํ๋
<button>์ธ๋ฐ... [์ด๋] ๋ฒํผ์ ๋ค๋ฅธ ํ์ด์ง๋ก ์์ ํ ๋์ด๊ฐ๋<a>ํ๊ทธ(Next.js Link)์ฌ์ผ ํ์์์? ๊ทธ๋ผ ๋์์ธ์ ๋๊ฐ์๋ฐ ์ปดํฌ๋ํธ๋ฅผ<Button>์ฉ,<LinkButton>์ฉ 2๊ฐ๋ก ๋ฐ๋ก ์ชผ๊ฐ์ผ ํ๋์? CSS ์ค๋ณต์ด 500์ค์ด ๋์ด๊ฐ๋๋ฐ..." - ์ํธ(๋ฆฌ๋): "์์ฒ ๋, ์ธ์ ๊น์ง ๊ป๋ฐ๊ธฐ(ํ๊ทธ ์ข ๋ฅ)์ ์ฝ๋งค์ฌ์ ๋๊ฐ์ ๋์์ธ์ ์ฝ๋๋ฅผ ๋ณต๋ถํ์ค ๊ฑด๊ฐ์. ๋ฒํผ ์ปดํฌ๋ํธ์๊ฒ ์นด๋ฉ๋ ์จ์ฒ๋ผ ๋ณ์ ํ ์ ์๋ ๋ณ์ ๋ฌผ์ฝ (๋คํ์ฑ)์ ๋จน์ ์๋ค! ์, ๊ทธ๋ฆฌ๊ณ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ธฐ๋ก ์ ์ํ์๋ ์๊ฐ์ฅ์ ์ธ ์ ์ ๋ค์ ์ํด ์ ๊ทผ์ฑ (a11y)ํ๊ทธ๋ ์ข ๋ฐ์๋์์๋ค."
๐ค ์ ์์์ผ ํ๋๊ฐ: '๊ป๋ฐ๊ธฐ'์ ์ ์ฃผ
๋ชจ์๊ณผ ํฐํธ ํฌ๊ธฐ, ํธ๋ฒ(Hover) ์ ํ๋์์ผ๋ก ๋ณํ๋ ์๋กฑํ ์ ๋๋ฉ์ด์
๊น์ง ๋ฒฝ๋ ํ๋ ํ๋ฆฌ์ง ์๊ณ ๋๊ฐ์ 100% ๋์ผํ ๋ฒํผ ๋์์ธ์
๋๋ค.
๊ทธ๋ฐ๋ฐ ์ด๋ค ๋์ 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์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
- ๊ณผ๊ฑฐ์ ๋ปฃ๋ปฃํ ๊ฐ์ท (ํ๋์ฝ๋ฉ): "์ด ์๋ฆ๋ค์ด ํฉ๊ธ ํ๋์ ๊ฐ์ท์ ๋ฌด์กฐ๊ฑด '๊ฒ์ฌ(Button)' ์ ์ฉ์ ๋๋ค!" ๋ง๋ฒ์ฌ(Link)๊ฐ ์ ์ผ๋ ค๊ณ ํ๋ฉด ์ต์ง๋ก ๋ ๋ฒ์งธ ๊ฐ์ท์ ์์ ์ฒ์๋ถํฐ ๋ค์ ์ฒ ์ ๋๋ค๊ฒจ ์๋ก ๋ง๋ค์ด ์ค์ผ ํฉ๋๋ค.
- ๋คํ์ฑ์ ๋ง๋ฒ ๊ฐ์ท (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, Shadcn UI ๋ฑ)์ด ์ด ํ๊ณ๋ฅผ ๋ซ๊ณ ์ ํญ๋ฐ์ ์ผ๋ก ๋ฐ๊ณ ์๋ ํจํด์ด ๋ฐ๋ก ์์์๊ฒ ๋์์ธ์ ๊ถ๋ ฅ์ ์์ํ๋ Slot (asChild) ํจํด์
๋๋ค.
// ๐ฏ Slot (asChild) ํจํด์ ์ฒ ํ
import { Slot } from '@radix-ui/react-slot'; // (์๋ฆฌ๋ฅผ ๊ตฌํํ ์ ๋ช
ํด)
// ๋์์ธ ๊ป๋ฐ๊ธฐ๋ง ์ฅ๊ณ ์๋ ๋ฐ๋ณด ์ปดํฌ๋ํธ
export function MasterButton({ asChild, children, ...props }) {
// asChild๊ฐ ์ผ์ ธ์์ผ๋ฉด? "์๋
, ๋ ์ํญํ ๊ฒ. ๋ด ํ๋์ ์ธํฌ๋ ์ด๋ฒคํธ(props)๋ค์ ์ฃ๋ค
// ๋ด ๋ฐ์ ๋ค์ด์จ ๋ด ์์(children) ๋ํํ
๋ชฝ๋
ํ ์คํด์ ๋ฎ์ด์์์ค!"
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์ ๋์์ธ CSS ๋ฐ๋ฅด๋ ๊ฒ๋ง ์ฑ
์์ง๋๋ค. ํ๋กญ์ค ๊ตฌ์กฐ๊ฐ ์ฝํ์ง ์๊ณ ๊น๋ํ๊ฒ ์์๋๋ ๊ธฐ์ ์ ๋ ๋๋ง์ด ์ผ์ด๋ฉ๋๋ค.
๐ ๋น๊ณผ ์๋ฆฌ: ์ ๊ทผ์ฑ (Accessibility, a11y) ์ฑ๊ธฐ๊ธฐ
์์ ๋คํ์ฑ ๋ฒํผ์ ๋น์๋ค๊ณ ์๋์ด๊ธ์ด ๋๋ ๊ฒ ์๋๋๋ค. ์ด ๋ฒํผ์ด ๋๋ฆด ์ ์๋ ์กฐ๊ฑด์ ๋น์ ์ ์ฑ์ ์ ์ํ๋ ๋ง์ฐ์ค๊ฐ ๊ณ ์ฅ ๋ ์ ์ , ํน์ ์๋ ฅ์ด ๋์ ํ ์ ๋ณด์ฌ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ธฐ(TTS)๋ก ๋ฒํผ์ ๋ฃ๋ ์๊ฐ์ฅ์ ์ธ๋ค์๊ฒ๋ ์ ํจํด์ผ ํฉ๋๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ๋ **a11y (์ ๊ทผ์ฑ)**์ด๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ํ์ ๋์์ธ ์์คํ ์์ ๊ฐ์ฅ ์ ๋ช ๋์ ๋์ด๋๋ฅผ ์๋ํฉ๋๋ค.
aria-์์ฑ์ ์ง์ฅ: ์ ๋ฒํผ์ด ๋จ์ํ '์ ์ก' ๊ธ์๋ง ์๋ ๊ฒ ์๋๋ผ๋ฉด?
// ์๊ฐ์ฅ์ ์ธ ๋ฆฌ๋๊ธฐ๋ "์๋ด๋ฌธ ์ด๊ธฐ" ๋ผ๊ณ ์น์ ํ๊ฒ ์ฝ์ด์ค๋ค!
<MagicButton aria-label="์๋ด๋ฌธ ์ด๊ธฐ" aria-expanded={isOpen} onClick={toggle}>
๐ {/* ๋์ ๋๋ ํ
์คํธ๊ฐ ์๊ณ ์ด๋ชจ์ง๋ ์์ด์ฝ ์ชผ๊ฐ๋ฆฌ๋ง ๋ฐํ ๋ฒํผ์ผ ๋ ํ์ */}
</MagicButton>- Tab ํฌ์ปค์ค ๋ฐฉ์ด๋ง: ๋ง์ฐ์ค๋ก ๋ชป ๋๋ฅด๋ ์ฌ๋์ ํค๋ณด๋์
Tab๊ณผEnterํค๋ง์ผ๋ก ํ๋ฉด ์ด๊ณณ์ ๊ณณ์ ์ค๊ฐ๋๋ค. ์ฌ๊ธฐ์ ๋ง์ฝ ์ ๋คํ์ฑ ๋ฒํผ์ดdiv๋spanํ๊ทธ๋ก ๋ ๋๋ง ๋์๋ค๋ฉด? (๊ณผ๊ฑฐ์ ํํ ์ด๋ฌ์) ์์ ํค๋ณด๋ ์ด์ ์ด ์กํ์ง ์๊ณ ๋ง์ฐ์ค๋ก๋ง ํด๋ฆญ์ด ๊ฐ๋ฅํ ์ฐ๋ ๊ธฐ ๋ฒํผ์ด ๋ฉ๋๋ค.
๊ทธ๋ ๊ธฐ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ Tab ํค ์กฐ์์ 100% ์ง์ํด ์ฃผ๋ ๋ค์ดํฐ๋ธ <button> ์์๋ <a> ํ๊ทธ๋ก ๋ณํ์์ผ์ผ๋ง ํ๋ ์ "๋คํ์ฑ ํจํด"์ด, ๋จ์ํ ๋ณํ ๊ฐ์ ๊ฐ๋ฐ ๋ง์กฑ๋๋ฅผ ๋์ด ์๋ฐฑ๋ง ์ ์ ์ ์ค์ ์ ์ ์์กด์ ์ฑ
์์ง๋ ์ฝ์ด๊ฐ ๋ ๊ฒ์
๋๋ค.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ด์ | ๋จ์ผ ํ๊ทธ ๋ฒํผ <button> | ๋คํ์ฑ(Polymorphism) ๋ฒํผ <Button as="a"> |
|---|---|---|
| ์ฅ์ | ๋ง๋ค๊ธฐ๊ฐ ๊ฐ์ฅ ๋น ๋ฅด๋ค. | ์๋ฒฝํ ๋์์ธ CSS์ ๋จ์ผ ์์ค ์์น. ํ ๋ฒ ๋ฐ๊ฟ๋ 1๋ง ๊ณณ์ ๋งํฌ ๋ฒํผ๊ณผ ์ ์ถ ๋ฒํผ์ด ๋๊ฐ์ด ์ด๋ป์ง๋ค. |
| ๋จ์ / ์ ์ฝ | href๋ฅผ ์ธ ์ ์๊ณ ์ต์ง๋ก window.location.href= ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ๊ฐ์ ํด SEO ๊ตฌ๊ธ ๋ด(๊ฒ์ ๋
ธ์ถ)์ด ๋ฐ์ด ๋๋ค. | ์์ฑํ ๋ TypeScript ํ์ ํ์ดํ ๋์ด๋๊ฐ ์ฐ์ฃผ๋ก ๊ฐ๋ค. (์ต๊ทผ์ Slot ํจํด์ด ์ด ๋จ์ ์ ์์ ์ค) |
| ์ ๊ทผ์ฑ(a11y)์ ์ค์์ฑ | ์คํฌ๋ฆฐ ๋ฆฌ๋ ๋ด(Bot)๋ ์ด๋์ด ๋ญ ์ง์ ํ๋ ๋ฒํผ์ธ์ง(๋ฒํผ์ ์์ฑ) ๋ช
์พํ๊ฒ ์ ๊ถ๋ฆฌ๊ฐ ์๋ค! ์์ด์ฝ ํฌ์ฑ์ด๋ฉด ๊ผญ aria-label์ ๋ฐ์! |
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
"๋ชจ์(๋์์ธ)์ ์นด๋ฉ๋ ์จ์ฒ๋ผ ๊ฐ๊ณ ๋ผ๋(ํ๊ทธ ๊ธฐ๋ฅ)๋ ํธ๋์คํฌ๋จธ์ฒ๋ผ ์กฐ๋ฆฝํด์ผ ํ๋ ์๊ฐ, ๋น์ ์ด ์ฅ ์ ์๋ ๊ถ๊ทน์ ๊ฒ์ ๋ฐ๋ก 'Slot'๊ณผ 'as'์ด๋ค."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
๋์์ธ์ด ๋๊ฐ์๋ฐ ์์ฑ์ด ๋ค๋ฅด๋ค๊ณ ๋ฒํผ ์ปดํฌ๋ํธ๋ฅผ ๋์ธ ๊ฐ์ฉ ๋ณต์ฌ๋ณธ์ผ๋ก ๋ง๋ค๋ ๋ด ํ์ญ์ฌ๊ฐ ์ค์ณ ์ง๋๊ฐ๋ค. as๋ Slot ํจํด์ผ๋ก ์ท๋ง ์์๊ฒ ์
ํ๊ณ ์๋งน์ด๋ ์์ ์์ฌ๋ก ๋ฐ๊พธ๋ ๋คํ์ฑ ๊ธฐ๋ฒ์ ์ง์ง '์ ์ฒด์ดํ' ๊ทธ ์์ฒด๋ค.
๐ก "์๊ฐ ์ฅ์ ์ธ๋, ๊ตฌ๊ธ ๊ฒ์ ๋ด๋ ์์ด์ง ์๋ ์ง์ง ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์. ๊ป๋ฐ๊ธฐ์ ์ง์ฐฉํ์ง ๋ง๊ณ ๊ธฐ๋ฅ์ ๋ง๋ ํ๊ทธ๋ก ๋ง์๊ป ๋ณ์ (
asChild)์์ผ๋ผ!"
๋จ์ํ ์์ ๊ฒ ์ฅ๋ก์ด ์๋๋ผ, ์ ๊ทผ์ฑ(a11y)๊น์ง ์ฑ๊ฒจ์ผ ๋๊ธฐ์
์์ค์ ํ๋ฆฌํฐ๊ฐ ๋์จ๋ค๋ ์ํธ ์ ๋ฐฐ์ ํฉํธ ํญํ์ด ์ ์ผ ์ํ๊ณ ์์ํ๋ค. ์ด์ div์ onClick ๋ฐ๋ฅด๋ ๋์ฐํ ์ง์ ๋ด ๊นํ๋ธ์์ ์์ํ ํด์ถ์ด๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ๋น์ ์ด 5๋
๋ง์ ํ ์ค, ๋ฐฐ๋ฏผ๊ณผ ๊ฐ์ ์ด๋ํ ๊ธฐ์
์ ํ๋ก ํธ์๋ ๊ณตํต ์ปดํฌ๋ํธ(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์ชผ๊ฐ๋ฆฌ๋ฅผ ๋ฆฌ์กํธ๊ฐ ๋ฑ์ด๋ด๋ ์๊ฐ ์ปดํ์ผ ํ์์ ๋ธ๋ผ์ฐ์ ๊ฐ VDOM ์ฃผ์ ์ ๊ฑฐ๋ถํ๋ฉฐ ๋นจ๊ฐ ์๋ฌ ์ฐฝ์ ๋์ด๋ค. - B) ์ด ๋ฒํผ์ ๊ตฌ๊ธ ๊ฒ์ ์์ง ๋ก๋ด(Bot)์ด ํ์ฑํ ๋ ๊ทธ์ "์๋ฌด๋ฐ ์ด๋ ๊ธฐ๋ฅ์ด ์๋ ๊ธ์ ํ ์ค(
div๋ธ๋ก)"์ผ๋ก ์ทจ๊ธํ๋ฏ๋ก ์ฌ์ดํธ ๊ฐ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ(SEO)๊ฐ ํ๊ดด๋๋ค. ๋ํ, ํค๋ณด๋ Tab ํค๋ฅผ ๋๋ฅด๋ ์๊ฐ/์ง์ฒด ์ฅ์ ์ธ ์ ์ ๋ค์ด ์ด ๋ฒํผ์ ๋๋ฌ์กฐ์ฐจ ํ ์ ์๊ณ , ์ํฐ ํค๋ฅผ ์ณ๋ ์๋ฌด ์ผ๋ ๋ฒ์ด์ง์ง ์๋๋ค. - C) ์ด ๋ฒํผ์
divํ๊ทธ๋ก ์ฝ๋งค์ฌ ์๊ธฐ์ ์คํ์ผ(className="btn")์ด ๋ฐ์ผ๋ก ์์ด ๋๊ฐ ๊ธ๋ก๋ฒ ์ค์ผ์ ์ผ์ผํค๋ ๋ฒ๊ทธ๋ฅผ ์ ๋ฐํ๋ค.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: ๋ง์ ์ ๊ฐ๊น์ด ํด์ค์
๋๋ค. div์ ์ด๊ฑฐ์ง๋ก onClick์ ๋ฐ๋ฅด๊ณ ์ต์ปค ์ด๋ ํ๋ด(์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ ๋ผ์ฐํ
)๋ฅผ ๋ด๋ ํ์๋ ํ๋ก ํธ์๋ ์ด๋ณด์๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ ์ง๋ฅด๋ ์ต์
์ ์ํฐ ํจํด(์ ๊ทผ์ฑ ํ๊ดด)์
๋๋ค. ์ด๋ฐ ๋์ฐํ ์ฌํ๋ฅผ ๋ง์ผ๋ ค๋ฉด ๊ฒ์ ๋ด๊ณผ ์๊ฐ์ฅ์ ์ธ ๋ชจ๋๊ฐ ํฉ๋ฒ์ ์ผ๋ก ์ธ์ํ ์ ์๋ ์ค์ <a> ํ๊ทธ(๋คํ์ฑ)๋ก ์์ ํ ํธ๋์คํผํ์ฌ ๋ผ๋ ์์ฒด๋ฅผ ๊ฐ์์ฃผ์ด์ผ๋ง ํฉ๋๋ค.
Q2. ์ต๊ทผ์ ํ์คํฐ๊ธ ๋ชจ๋ UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Radix UI ๋ฑ)๊ฐ ์ ๊ณตํ๋ asChild (ํน์ Slot ํจํด)๊ฐ, ๊ณผ๊ฑฐ 1์ธ๋ as="a" ๋ฐฉ์์ ์ ์น๊ณ ๋๊ธฐ์
๋ฉ์ธ์คํธ๋ฆผ(์ฃผ๋ฅ)์ผ๋ก ๋ฑ๊ทนํ๊ฒ ๋ ํต์ฌ์ ์ธ ๊ฐ๋ฐ ๊ฒฝํ(DX)์ ์ด์ ๋ฅผ ์ฃผ๊ด์ ํ๋๋ก ๋น์ ํ๊ฑฐ๋ ๋ฌ์ฌํด๋ณด์ธ์.
โ
์ ๋ต ๋ฐ ์ฃผ๊ด์ ํด์ค:
"๊ณผ๊ฑฐ as="a" ๋ฐฉ์์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ href๋ target="_blank" ๊ฐ์ ์์์ด ํ์ํ ์์ฑ๋ค์ ์ด๋ป๊ฒ๋ ๋ค ์ ๋ฌ๋ฐ์์ ๋์ ์งฌ์ฒ๋ฆฌ ํด๊ฐ๋ฉฐ ๋ณต์กํ ์ ๋ค๋ฆญ ํ์
์๋ฌ์ ์ธ์ฐ๋ '์ค์ง๋์ ํ์ ' ๊ตฌ์กฐ์์ต๋๋ค. ๋ฐ๋ฉด asChild(Slot) ํจํด์, ๋ถ๋ชจ๋ ๊ทธ์ ๋ฉ์ง ํฉ๊ธ์ ๊ฐ์ท ๊ป๋ฐ๊ธฐ(๋์์ธ CSS์ ๊ณตํต ์ด๋ฒคํธ)๋ง ๋ธ๋ ๋์ ธ๋๊ณ ํญ์ฌํด๋ฒ๋ฆฌ๋ฉด, ๊ทธ ๊ฐ์ท์ด ๋ฐ์ ์ค์ ํ๊ทธ ์์์๊ฒ ๊ธฐ์์์ฒ๋ผ ์์์ ์ฐฐ์น ์ค๋ฉฐ๋ค์ด ๋ณํฉ ๋ ๋๋ง๋๋ ๊ธฐ์ ์ ๋ฐํํ๋ฏ๋ก Props ์ถฉ๋ ๊ณ ๋ฏผ์ด ์์ฒ ์๋ฉธํ๋ ๊ถ๊ทน์ ํ์ฅ์ฑ์ ์ง๋๊ธฐ ๋๋ฌธ์
๋๋ค."