Ver2.0 SVG 조작을 위한 TypeScript 타입 정의 완벽 가이드: 시각적 웹 개발의 새로운 차원 🚀

SVG 조작을 위한 TypeScript 타입 정의 완벽 가이드: 시각적 웹 개발의 새로운 차원 🚀
안녕하세요, 개발자 여러분! 오늘은 SVG와 TypeScript의 찰떡궁합 🍯 조합에 대해 얘기해볼게요. SVG(Scalable Vector Graphics)는 웹에서 벡터 그래픽을 표현하는 강력한 도구인데, 이걸 TypeScript로 타입 안전하게 다루면 진짜 개발이 너무 즐거워진다니까요? 😆
요즘 프론트엔드 개발에서 SVG 활용이 대세인 거 아시죠? 데이터 시각화부터 인터랙티브 애니메이션까지, SVG는 웹 디자인의 게임체인저가 되었어요. 근데 이 SVG를 TypeScript로 제대로 타입 정의해서 쓰면 개발 경험이 레벨업된다는 사실! ✨
이 글에서는 SVG 요소를 TypeScript로 어떻게 타입 정의하고, 조작하고, 최적화할 수 있는지 재능넷에서만 볼 수 있는 특별한 팁들과 함께 알려드릴게요. 코드 안정성은 물론이고 개발 생산성까지 확 올라가는 마법을 경험해보세요! 🧙♂️
📚 목차
- SVG와 TypeScript: 기본 개념 이해하기
- SVG DOM 요소를 위한 TypeScript 인터페이스
- SVG 속성과 메서드의 타입 정의
- 실전 예제: 인터랙티브 SVG 컴포넌트 만들기
- SVG 애니메이션과 타입 안전한 조작
- SVG 최적화 기법과 TypeScript 활용
- 고급 테크닉: SVG와 TypeScript 제네릭 활용
- 실무 팁: SVG 타입 정의의 모범 사례
- 마무리 및 추가 리소스
1. SVG와 TypeScript: 기본 개념 이해하기 🌱
SVG와 TypeScript를 함께 사용하기 전에 각각이 뭔지 간단히 짚고 넘어갈게요. 기초부터 차근차근 알아봅시다!
1.1 SVG란 무엇인가요?
SVG는 Scalable Vector Graphics의 약자로, XML 기반의 벡터 이미지 포맷이에요. 픽셀 기반 이미지(JPG, PNG)와 달리 수학적 공식으로 이미지를 정의하기 때문에 크기를 조절해도 품질 손실이 없어요. 완전 신기하지 않나요? 🤩
SVG의 주요 특징:
- 확장성: 어떤 크기로 조절해도 선명함 유지
- XML 기반: 텍스트 에디터로 직접 편집 가능
- 스타일링: CSS로 스타일 적용 가능
- 스크립팅: JavaScript로 동적 조작 가능
- 접근성: 텍스트 기반이라 스크린 리더에 친화적
간단한 SVG 예제를 볼까요?
<svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>
이렇게 하면 아래와 같은 빨간 원이 그려져요:
1.2 TypeScript와 SVG의 만남
TypeScript는 JavaScript에 타입 시스템을 추가한 언어예요. 코드 작성 단계에서 오류를 잡아주고, 자동 완성 기능을 제공해서 개발 생산성을 크게 높여줘요. 특히 SVG처럼 복잡한 DOM 조작을 할 때 진가를 발휘한답니다! 👍
TypeScript로 SVG를 다루면 얻는 이점:
- 타입 안전성: 속성 이름 오타나 잘못된 값 타입을 컴파일 단계에서 발견
- 코드 자동 완성: SVG 요소와 속성에 대한 인텔리센스 지원
- 리팩토링 용이성: 타입 시스템 덕분에 안전한 코드 변경 가능
- 문서화: 코드 자체가 문서 역할을 함
- 팀 협업 향상: 명확한 인터페이스 정의로 협업 효율 증가
SVG와 TypeScript의 조합은 마치 치즈와 와인 같은 환상의 페어링이랄까요? ㅋㅋㅋ 이제 본격적으로 SVG 요소들을 TypeScript로 어떻게 타입 정의하는지 알아볼게요! 🧀🍷
2. SVG DOM 요소를 위한 TypeScript 인터페이스 🧩
SVG 요소들을 TypeScript로 다루려면 먼저 각 요소에 맞는 인터페이스를 알아야 해요. 브라우저에는 이미 SVG 관련 타입 정의가 있지만, 더 구체적인 타입을 정의하면 개발이 훨씬 편해진답니다!
2.1 기본 SVG 요소 타입
TypeScript에서 SVG 요소는 기본적으로 SVGElement 인터페이스를 상속받아요. 그리고 각 요소별로 더 구체적인 인터페이스가 있죠.
// SVG 기본 요소 타입들
interface SVGElement extends Element {
// SVG 요소의 공통 속성과 메서드
}
interface SVGSVGElement extends SVGElement {
// <svg> 요소의 속성과 메서드
width: SVGAnimatedLength;
height: SVGAnimatedLength;
viewBox: SVGAnimatedRect;
// 기타 속성들...
}
interface SVGCircleElement extends SVGElement {
// <circle> 요소의 속성과 메서드
cx: SVGAnimatedLength;
cy: SVGAnimatedLength;
r: SVGAnimatedLength;
// 기타 속성들...
}
// 기타 SVG 요소들에 대한 인터페이스...
이런 인터페이스들 덕분에 TypeScript는 각 SVG 요소의 속성과 메서드를 정확히 알고 있어서 코드 작성 시 자동 완성과 타입 체크를 제공해요. 완전 꿀이죠? 🍯
2.2 커스텀 SVG 타입 정의하기
기본 인터페이스만으로는 부족할 때가 있어요. 특히 프로젝트에 특화된 SVG 컴포넌트를 만들 때는 커스텀 타입을 정의하면 좋답니다.
// 커스텀 SVG 차트 컴포넌트를 위한 타입 정의
interface ChartDataPoint {
x: number;
y: number;
label: string;
color?: string;
}
interface SVGChartProps {
data: ChartDataPoint[];
width: number;
height: number;
margin?: {
top: number;
right: number;
bottom: number;
left: number;
};
animated?: boolean;
onPointClick?: (point: ChartDataPoint) => void;
}
// 차트 컴포넌트 사용 예시
function createChart(props: SVGChartProps): SVGSVGElement {
// 차트 생성 로직...
}
이렇게 타입을 명확하게 정의하면 코드 작성 시 실수를 줄이고, 다른 개발자들도 API를 쉽게 이해할 수 있어요. 재능넷에서 SVG 관련 프로젝트를 의뢰할 때도 이런 타입 정의가 잘 되어 있으면 협업이 훨씬 수월해진답니다! 😉
2.3 타입 단언(Type Assertion) 활용하기
SVG 요소를 DOM에서 가져올 때 종종 타입 단언이 필요해요. querySelector나 getElementById는 기본적으로 구체적인 SVG 타입을 반환하지 않기 때문이죠.
// 타입 단언 없이 사용하면
const circle = document.querySelector('circle');
// circle의 타입은 Element | null
// 타입 단언 사용
const circle = document.querySelector('circle') as SVGCircleElement;
// 이제 circle의 타입은 SVGCircleElement
circle.cx.baseVal.value = 100; // 타입 안전하게 속성 접근 가능
// 또는 타입 가드 사용
const element = document.querySelector('circle');
if (element instanceof SVGCircleElement) {
// 이 블록 안에서 element는 SVGCircleElement 타입
element.cx.baseVal.value = 100;
}
타입 단언은 강력하지만, 남용하면 타입 안전성을 해칠 수 있어요. 가능하면 타입 가드를 사용하는 것이 더 안전하답니다! 💪
3. SVG 속성과 메서드의 타입 정의 🔍
SVG 요소들은 다양한 속성과 메서드를 가지고 있어요. 이것들을 TypeScript로 타입 정의하면 훨씬 안전하게 코딩할 수 있답니다!
3.1 SVG 기하학적 속성의 타입
SVG 요소들은 위치, 크기 등을 정의하는 기하학적 속성을 많이 가지고 있어요. 이런 속성들은 대부분 SVGAnimatedLength 타입이에요.
// SVG 원의 속성 타입
interface SVGCircleElement extends SVGGeometryElement {
cx: SVGAnimatedLength; // x 좌표
cy: SVGAnimatedLength; // y 좌표
r: SVGAnimatedLength; // 반지름
}
// 실제 사용 예시
function setCircleProperties(circle: SVGCircleElement, x: number, y: number, radius: number): void {
circle.cx.baseVal.value = x;
circle.cy.baseVal.value = y;
circle.r.baseVal.value = radius;
}
SVG 속성들은 대부분 직접 값을 설정하는 것이 아니라 baseVal이나 animVal을 통해 접근해야 해요. 이런 구조를 TypeScript가 강제해주니 실수할 일이 없죠! 👌
3.2 SVG 스타일 속성의 타입
SVG 요소의 스타일 속성은 style 객체나 직접 속성으로 접근할 수 있어요.
// SVG 스타일 속성 타입 정의
interface SVGStylable {
style: CSSStyleDeclaration;
className: SVGAnimatedString;
}
// 실제 사용 예시
function styleCircle(circle: SVGCircleElement & SVGStylable, color: string, strokeWidth: number): void {
circle.style.fill = color;
circle.style.strokeWidth = `${strokeWidth}px`;
// 또는 속성으로 직접 설정
circle.setAttribute('fill', color);
circle.setAttribute('stroke-width', strokeWidth.toString());
}
인터섹션 타입(&)을 사용하면 여러 인터페이스의 속성을 모두 가진 타입을 정의할 수 있어요. 완전 유용하죠? 😎
3.3 SVG 변환(Transform) 속성의 타입
SVG 요소의 변환(회전, 이동, 확대/축소 등)은 transform 속성을 통해 이루어져요.
// SVG 변환 메서드 타입 정의
interface SVGTransformable {
transform: SVGAnimatedTransformList;
// 기타 변환 관련 속성들...
}
// 실제 사용 예시
function rotateElement(element: SVGElement & SVGTransformable, angle: number): void {
const transform = element.ownerSVGElement?.createSVGTransform();
if (transform) {
transform.setRotate(angle, 0, 0);
element.transform.baseVal.appendItem(transform);
}
}
3.4 SVG 이벤트 핸들러 타입
SVG 요소에 이벤트 핸들러를 추가할 때도 TypeScript의 타입 시스템을 활용할 수 있어요.
// SVG 이벤트 핸들러 타입 정의
type SVGMouseEventHandler = (event: MouseEvent) => void;
interface SVGElementEventHandlers {
onClick: SVGMouseEventHandler;
onMouseMove: SVGMouseEventHandler;
onMouseEnter: SVGMouseEventHandler;
onMouseLeave: SVGMouseEventHandler;
// 기타 이벤트 핸들러들...
}
// 실제 사용 예시
function addEventListeners(element: SVGElement, handlers: Partial<SVGElementEventHandlers>): void {
if (handlers.onClick) {
element.addEventListener('click', handlers.onClick);
}
if (handlers.onMouseMove) {
element.addEventListener('mousemove', handlers.onMouseMove);
}
// 기타 이벤트 핸들러 추가...
}
Partial 유틸리티 타입을 사용하면 모든 속성을 선택적으로 만들 수 있어요. 이렇게 하면 필요한 핸들러만 전달할 수 있어 편리하죠! 💯
SVG 속성의 타입을 잘 정의해두면 코드 작성 시 자동 완성과 타입 체크의 혜택을 누릴 수 있어요. 재능넷에서 SVG 관련 프로젝트를 진행할 때도 이런 타입 정의가 있으면 개발 속도와 품질이 크게 향상된답니다! 🚀
4. 실전 예제: 인터랙티브 SVG 컴포넌트 만들기 🎮
이제 배운 내용을 바탕으로 실제 인터랙티브 SVG 컴포넌트를 TypeScript로 만들어볼게요! 이론만 알면 뭐해요, 실전에서 써먹어야죠? ㅋㅋㅋ
4.1 간단한 SVG 차트 컴포넌트
먼저 데이터를 시각화하는 간단한 막대 차트 컴포넌트를 만들어볼게요.
// 차트 데이터 타입 정의
interface BarChartData {
label: string;
value: number;
color?: string;
}
// 차트 속성 타입 정의
interface BarChartProps {
data: BarChartData[];
width: number;
height: number;
margin?: {
top: number;
right: number;
bottom: number;
left: number;
};
onBarClick?: (data: BarChartData) => void;
}
// 차트 생성 함수
function createBarChart(props: BarChartProps): SVGSVGElement {
const { data, width, height, margin = { top: 20, right: 20, bottom: 30, left: 40 }, onBarClick } = props;
// SVG 요소 생성
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', width.toString());
svg.setAttribute('height', height.toString());
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
// 차트 영역 계산
const chartWidth = width - margin.left - margin.right;
const chartHeight = height - margin.top - margin.bottom;
// 막대 너비 계산
const barWidth = chartWidth / data.length * 0.8;
const barGap = chartWidth / data.length * 0.2;
// 최대값 찾기
const maxValue = Math.max(...data.map(d => d.value));
// 막대 그리기
data.forEach((d, i) => {
const barHeight = (d.value / maxValue) * chartHeight;
const x = margin.left + i * (barWidth + barGap);
const y = height - margin.bottom - barHeight;
const bar = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
bar.setAttribute('x', x.toString());
bar.setAttribute('y', y.toString());
bar.setAttribute('width', barWidth.toString());
bar.setAttribute('height', barHeight.toString());
bar.setAttribute('fill', d.color || '#4ECDC4');
// 이벤트 핸들러 추가
if (onBarClick) {
bar.addEventListener('click', () => onBarClick(d));
}
svg.appendChild(bar);
// 레이블 추가
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', (x + barWidth / 2).toString());
text.setAttribute('y', (height - margin.bottom + 15).toString());
text.setAttribute('text-anchor', 'middle');
text.textContent = d.label;
svg.appendChild(text);
// 값 표시
const valueText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
valueText.setAttribute('x', (x + barWidth / 2).toString());
valueText.setAttribute('y', (y - 5).toString());
valueText.setAttribute('text-anchor', 'middle');
valueText.textContent = d.value.toString();
svg.appendChild(valueText);
});
return svg;
}
이 코드를 사용하면 이런 차트를 만들 수 있어요:
4.2 인터랙티브 요소 추가하기
이제 차트에 인터랙티브 요소를 추가해볼게요. 마우스 오버 효과와 클릭 이벤트를 구현해볼거예요.
// 인터랙티브 요소 타입 정의
interface InteractiveElementProps {
element: SVGElement;
hoverColor?: string;
hoverScale?: number;
onClick?: (event: MouseEvent) => void;
onMouseEnter?: (event: MouseEvent) => void;
onMouseLeave?: (event: MouseEvent) => void;
}
// 인터랙티브 기능 추가 함수
function makeInteractive(props: InteractiveElementProps): void {
const {
element,
hoverColor,
hoverScale = 1.1,
onClick,
onMouseEnter,
onMouseLeave
} = props;
// 원래 속성 저장
const originalFill = element.getAttribute('fill') || '';
const originalTransform = element.getAttribute('transform') || '';
// 마우스 진입 이벤트
element.addEventListener('mouseenter', (e) => {
if (hoverColor) {
element.setAttribute('fill', hoverColor);
}
if (hoverScale !== 1) {
// 요소의 중심점 찾기 (간단한 방법)
const bbox = (element as SVGGraphicsElement).getBBox();
const centerX = bbox.x + bbox.width / 2;
const centerY = bbox.y + bbox.height / 2;
// 중심점 기준으로 확대
element.setAttribute(
'transform',
`${originalTransform} translate(${centerX}, ${centerY}) scale(${hoverScale}) translate(${-centerX}, ${-centerY})`
);
}
if (onMouseEnter) {
onMouseEnter(e);
}
});
// 마우스 이탈 이벤트
element.addEventListener('mouseleave', (e) => {
if (hoverColor) {
element.setAttribute('fill', originalFill);
}
if (hoverScale !== 1) {
element.setAttribute('transform', originalTransform);
}
if (onMouseLeave) {
onMouseLeave(e);
}
});
// 클릭 이벤트
if (onClick) {
element.addEventListener('click', onClick);
}
}
이렇게 만든 함수를 사용하면 SVG 요소에 쉽게 인터랙티브 기능을 추가할 수 있어요. 마우스를 올리면 색상이 바뀌고 크기가 커지는 효과를 줄 수 있죠! 🎯
4.3 실제 사용 예시
이제 만든 컴포넌트와 인터랙티브 기능을 실제로 사용해볼게요.
// 차트 데이터
const data: BarChartData[] = [
{ label: '2020', value: 50, color: '#4ECDC4' },
{ label: '2021', value: 100, color: '#4ECDC4' },
{ label: '2022', value: 150, color: '#4ECDC4' },
{ label: '2023', value: 200, color: '#4ECDC4' },
{ label: '2024', value: 220, color: '#FF6B6B' },
{ label: '2025', value: 250, color: '#FF6B6B' }
];
// 차트 생성
const chart = createBarChart({
data,
width: 600,
height: 400,
onBarClick: (data) => {
console.log(`클릭한 연도: ${data.label}, 값: ${data.value}`);
alert(`${data.label}년 TypeScript 인기도: ${data.value}`);
}
});
// 차트를 DOM에 추가
document.getElementById('chart-container')?.appendChild(chart);
// 모든 막대에 인터랙티브 기능 추가
const bars = chart.querySelectorAll('rect');
bars.forEach((bar) => {
makeInteractive({
element: bar as SVGRectElement,
hoverColor: '#F08A5D',
hoverScale: 1.05,
onMouseEnter: () => {
// 툴팁 표시 등의 추가 기능
}
});
});
이런 식으로 TypeScript의 타입 시스템을 활용하면 SVG 요소를 안전하고 효율적으로 조작할 수 있어요. 코드가 길어 보이지만, 실제로는 타입 정의 덕분에 개발 과정에서 많은 실수를 방지하고 생산성을 높일 수 있답니다! 💪
재능넷에서도 이런 인터랙티브 SVG 컴포넌트를 활용한 프로젝트가 많이 진행되고 있어요. 특히 데이터 시각화나 인터랙티브 웹사이트 제작에 이런 기술이 많이 활용된답니다! 🌟
5. SVG 애니메이션과 타입 안전한 조작 🎬
SVG 애니메이션은 웹 페이지에 생동감을 불어넣는 강력한 도구예요. TypeScript로 이런 애니메이션을 타입 안전하게 다루는 방법을 알아볼게요!
5.1 SMIL 애니메이션 타입 정의
SVG는 SMIL(Synchronized Multimedia Integration Language)이라는 내장 애니메이션 시스템을 가지고 있어요. 이를 TypeScript로 타입 정의해볼게요.
// SMIL 애니메이션 요소 타입 정의
interface SVGAnimateElement extends SVGElement {
attributeName: string;
from: string;
to: string;
dur: string;
repeatCount: string;
fill: string;
begin: string;
// 기타 애니메이션 속성들...
}
// 애니메이션 생성 함수
function createAnimation(params: {
attributeName: string;
from: string | number;
to: string | number;
dur: string;
repeatCount?: string;
fill?: 'freeze' | 'remove';
begin?: string;
}): SVGAnimateElement {
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate') as SVGAnimateElement;
animate.setAttribute('attributeName', params.attributeName);
animate.setAttribute('from', params.from.toString());
animate.setAttribute('to', params.to.toString());
animate.setAttribute('dur', params.dur);
if (params.repeatCount) {
animate.setAttribute('repeatCount', params.repeatCount);
}
if (params.fill) {
animate.setAttribute('fill', params.fill);
}
if (params.begin) {
animate.setAttribute('begin', params.begin);
}
return animate;
}
이 함수를 사용하면 SVG 요소에 쉽게 애니메이션을 추가할 수 있어요. TypeScript의 타입 체크 덕분에 필수 속성을 빼먹을 일도 없죠! 👍
5.2 CSS 애니메이션과 TypeScript
SVG 요소는 CSS 애니메이션도 지원해요. 이를 TypeScript로 다루는 방법을 알아볼게요.
// CSS 애니메이션 속성 타입 정의
interface CSSAnimationProps {
element: SVGElement;
keyframes: Keyframe[] | PropertyIndexedKeyframes;
options?: KeyframeAnimationOptions;
}
// CSS 애니메이션 생성 함수
function animateWithCSS(props: CSSAnimationProps): Animation {
const { element, keyframes, options } = props;
return element.animate(keyframes, options);
}
// 사용 예시
const circle = document.querySelector('circle') as SVGCircleElement;
if (circle) {
const animation = animateWithCSS({
element: circle,
keyframes: [
{ r: '20', fill: 'red' },
{ r: '40', fill: 'blue' },
{ r: '20', fill: 'red' }
],
options: {
duration: 2000,
iterations: Infinity,
easing: 'ease-in-out'
}
});
// 애니메이션 제어
animation.pause();
animation.play();
animation.finish();
}
Web Animations API를 사용하면 JavaScript로 CSS 애니메이션을 제어할 수 있어요. TypeScript와 함께 사용하면 타입 안전성까지 확보할 수 있죠! 💯
5.3 애니메이션 시퀀스 만들기
여러 애니메이션을 순차적으로 실행하는 시퀀스를 만들어볼게요.
// 애니메이션 시퀀스 타입 정의
interface AnimationSequenceItem {
element: SVGElement;
keyframes: Keyframe[] | PropertyIndexedKeyframes;
options: KeyframeAnimationOptions;
}
// 애니메이션 시퀀스 생성 함수
async function playAnimationSequence(sequence: AnimationSequenceItem[]): Promise<void> {
for (const item of sequence) {
const animation = item.element.animate(item.keyframes, item.options);
await new Promise<void>((resolve) => {
animation.onfinish = () => resolve();
});
}
}
// 사용 예시
const circle = document.querySelector('circle') as SVGCircleElement;
const rect = document.querySelector('rect') as SVGRectElement;
if (circle && rect) {
playAnimationSequence([
{
element: circle,
keyframes: [
{ r: '20' },
{ r: '50' }
],
options: {
duration: 1000,
fill: 'forwards'
}
},
{
element: rect,
keyframes: [
{ width: '50', height: '50' },
{ width: '100', height: '100' }
],
options: {
duration: 1000,
fill: 'forwards'
}
},
{
element: circle,
keyframes: [
{ fill: 'red' },
{ fill: 'blue' }
],
options: {
duration: 1000,
fill: 'forwards'
}
}
]).then(() => {
console.log('모든 애니메이션 완료!');
});
}
async/await와 Promise를 활용하면 복잡한 애니메이션 시퀀스도 쉽게 관리할 수 있어요. TypeScript의 타입 시스템 덕분에 각 애니메이션 항목의 구조도 명확하게 정의할 수 있죠! 🎭
SVG 애니메이션은 웹사이트에 생동감을 불어넣는 훌륭한 도구예요. 재능넷에서도 이런 애니메이션 기술을 활용한 다양한 프로젝트가 진행되고 있어요. 특히 인터랙티브 인포그래픽이나 데이터 시각화에 많이 활용된답니다! 🌈
6. SVG 최적화 기법과 TypeScript 활용 🔧
SVG를 효율적으로 사용하려면 최적화가 필수예요. TypeScript를 활용해 SVG를 최적화하는 방법을 알아볼게요!
6.1 SVG 스프라이트 타입 정의
여러 SVG 아이콘을 하나의 파일로 관리하는 SVG 스프라이트 기법은 성능 최적화에 매우 효과적이에요. 이를 TypeScript로 타입 정의해볼게요.
// SVG 스프라이트 아이콘 타입 정의
interface SpriteIcon {
id: string;
viewBox: string;
content: string;
}
// SVG 스프라이트 타입 정의
interface SpriteSheet {
id: string;
icons: SpriteIcon[];
}
// SVG 스프라이트 생성 함수
function createSpriteSheet(spriteSheet: SpriteSheet): SVGSVGElement {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('id', spriteSheet.id);
svg.style.display = 'none';
spriteSheet.icons.forEach(icon => {
const symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
symbol.setAttribute('id', icon.id);
symbol.setAttribute('viewBox', icon.viewBox);
symbol.innerHTML = icon.content;
svg.appendChild(symbol);
});
return svg;
}
// SVG 아이콘 사용 함수
function useSpriteIcon(iconId: string, width: number, height: number): SVGSVGElement {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', width.toString());
svg.setAttribute('height', height.toString());
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#${iconId}`);
svg.appendChild(use);
return svg;
}
이렇게 스프라이트를 사용하면 HTTP 요청 수를 줄이고 캐싱 효율을 높일 수 있어요. TypeScript로 타입을 정의하면 스프라이트 관리도 더 쉬워지죠! 🚀
6.2 SVG 최적화 유틸리티 함수
SVG 파일을 최적화하는 유틸리티 함수를 TypeScript로 만들어볼게요.
// SVG 최적화 옵션 타입 정의
interface SVGOptimizationOptions {
removeComments?: boolean;
removeEmptyAttrs?: boolean;
removeEmptyElements?: boolean;
removeHiddenElements?: boolean;
removeMetadata?: boolean;
removeUnusedNS?: boolean;
cleanupIDs?: boolean;
convertColors?: boolean;
removeViewBox?: boolean;
}
// SVG 최적화 함수
function optimizeSVG(svgString: string, options: SVGOptimizationOptions = {}): string {
const {
removeComments = true,
removeEmptyAttrs = true,
removeEmptyElements = true,
removeHiddenElements = true,
removeMetadata = true,
removeUnusedNS = true,
cleanupIDs = true,
convertColors = true,
removeViewBox = false
} = options;
let result = svgString;
// 주석 제거
if (removeComments) {
result = result.replace(/<!--[\s\S]*?-->/g, '');
}
// 빈 속성 제거
if (removeEmptyAttrs) {
result = result.replace(/\s+(\w+)=""/g, '');
}
// 빈 요소 제거
if (removeEmptyElements) {
result = result.replace(/<(\w+)><\/\1>/g, '');
}
// 숨겨진 요소 제거
if (removeHiddenElements) {
result = result.replace(/<(\w+)[^>]*display\s*:\s*none[^>]*>[\s\S]*?<\/\1>/g, '');
}
// 메타데이터 제거
if (removeMetadata) {
result = result.replace(/<metadata>[\s\S]*?<\/metadata>/g, '');
}
// 사용되지 않는 네임스페이스 제거
if (removeUnusedNS) {
// 실제로는 더 복잡한 로직이 필요합니다
result = result.replace(/\s+xmlns:(\w+)="[^"]*"/g, (match, prefix) => {
return result.includes(`${prefix}:`) ? match : '';
});
}
// ID 정리
if (cleanupIDs) {
// 실제로는 더 복잡한 로직이 필요합니다
const ids: Record<string, string> = {};
let counter = 0;
result = result.replace(/\sid="([^"]+)"/g, (match, id) => {
if (!ids[id]) {
ids[id] = `id${counter++}`;
}
return ` id="${ids[id]}"`;
});
// ID 참조도 업데이트
Object.entries(ids).forEach(([oldId, newId]) => {
result = result.replace(new RegExp(`url\\(#${oldId}\\)`, 'g'), `url(#${newId})`);
result = result.replace(new RegExp(`href="#${oldId}"`, 'g'), `href="#${newId}"`);
});
}
// 색상 변환 (예: #ff0000 -> red)
if (convertColors) {
const colorMap: Record<string, string> = {
'#ff0000': 'red',
'#00ff00': 'green',
'#0000ff': 'blue',
'#ffff00': 'yellow',
'#ff00ff': 'magenta',
'#00ffff': 'cyan',
'#000000': 'black',
'#ffffff': 'white'
};
Object.entries(colorMap).forEach(([hex, name]) => {
result = result.replace(new RegExp(hex, 'gi'), name);
});
}
// viewBox 유지 (기본적으로 제거하지 않음)
if (removeViewBox) {
// 실제로는 width/height가 있을 때만 제거해야 합니다
result = result.replace(/\sviewBox="[^"]*"/g, '');
}
return result;
}
이런 최적화 함수를 사용하면 SVG 파일 크기를 크게 줄일 수 있어요. TypeScript의 타입 시스템 덕분에 옵션도 명확하게 정의할 수 있죠! 🔍
6.3 SVG 지연 로딩 구현
SVG를 필요할 때만 로드하는 지연 로딩 기법을 TypeScript로 구현해볼게요.
// SVG 지연 로딩 옵션 타입 정의
interface SVGLazyLoadOptions {
threshold?: number;
rootMargin?: string;
onLoad?: (element: HTMLElement) => void;
placeholder?: string | null;
}
// SVG 지연 로딩 함수
function lazyLoadSVG(selector: string, options: SVGLazyLoadOptions = {}): void {
const {
threshold = 0.1,
rootMargin = '0px',
onLoad,
placeholder = null
} = options;
// IntersectionObserver 설정
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const element = entry.target as HTMLElement;
const svgUrl = element.dataset.svgSrc;
if (svgUrl) {
// SVG 로드
fetch(svgUrl)
.then(response => response.text())
.then(svgContent => {
element.innerHTML = svgContent;
// 로드 콜백 호출
if (onLoad) {
onLoad(element);
}
// 관찰 중단
observer.unobserve(element);
})
.catch(error => {
console.error('SVG 로드 실패:', error);
});
}
}
});
},
{ threshold, rootMargin }
);
// 대상 요소들 관찰 시작
document.querySelectorAll<HTMLElement>(selector).forEach(element => {
// 플레이스홀더 설정
if (placeholder) {
element.innerHTML = placeholder;
}
// 관찰 시작
observer.observe(element);
});
}
이 함수를 사용하면 SVG를 필요할 때만 로드해서 초기 로딩 시간을 단축할 수 있어요. TypeScript의 타입 시스템 덕분에 옵션 구조도 명확하게 정의할 수 있죠! 🚄
SVG 최적화는 웹 성능 향상에 큰 도움이 돼요. 재능넷에서도 이런 최적화 기법을 적용해 사용자 경험을 개선하고 있답니다. 특히 모바일 환경에서는 이런 최적화가 더욱 중요하죠! 📱
7. 고급 테크닉: SVG와 TypeScript 제네릭 활용 🧠
이제 좀 더 고급 기술로 들어가볼게요! TypeScript의 제네릭을 활용해 SVG 조작을 더 유연하게 만들어봅시다.
7.1 제네릭 SVG 팩토리 함수
다양한 SVG 요소를 생성하는 제네릭 팩토리 함수를 만들어볼게요.
// SVG 요소 타입 맵 정의
interface SVGElementTypeMap {
'svg': SVGSVGElement;
'circle': SVGCircleElement;
'rect': SVGRectElement;
'path': SVGPathElement;
'line': SVGLineElement;
'text': SVGTextElement;
'g': SVGGElement;
// 기타 SVG 요소들...
}
// 제네릭 SVG 요소 생성 함수
function createSVGElement<K extends keyof SVGElementTypeMap>(
type: K,
attributes?: Record<string, string | number | boolean>,
children?: (SVGElement | string)[]
): SVGElementTypeMap[K] {
// SVG 요소 생성
const element = document.createElementNS('http://www.w3.org/2000/svg', type);
// 속성 설정
if (attributes) {
Object.entries(attributes).forEach(([key, value]) => {
// 카멜케이스를 대시 케이스로 변환 (예: strokeWidth -> stroke-width)
const attributeName = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
element.setAttribute(attributeName, value.toString());
});
}
// 자식 요소 추가
if (children) {
children.forEach(child => {
if (typeof child === 'string') {
element.textContent = child;
} else {
element.appendChild(child);
}
});
}
return element as SVGElementTypeMap[K];
}
이 함수를 사용하면 타입 안전하게 다양한 SVG 요소를 생성할 수 있어요. 제네릭 덕분에 반환 타입이 정확하게 추론되죠! 🧩
7.2 제네릭 SVG 속성 설정 함수
SVG 요소의 속성을 타입 안전하게 설정하는 제네릭 함수를 만들어볼게요.
// SVG 요소별 속성 타입 정의
type SVGCircleAttributes = {
cx?: number | string;
cy?: number | string;
r?: number | string;
fill?: string;
stroke?: string;
strokeWidth?: number | string;
// 기타 속성들...
};
type SVGRectAttributes = {
x?: number | string;
y?: number | string;
width?: number | string;
height?: number | string;
rx?: number | string;
ry?: number | string;
fill?: string;
stroke?: string;
strokeWidth?: number | string;
// 기타 속성들...
};
// 요소별 속성 맵 정의
interface SVGAttributeMap {
'circle': SVGCircleAttributes;
'rect': SVGRectAttributes;
// 기타 요소들...
}
// 제네릭 속성 설정 함수
function setSVGAttributes<K extends keyof SVGAttributeMap>(
element: SVGElementTypeMap[K],
attributes: SVGAttributeMap[K]
): SVGElementTypeMap[K] {
Object.entries(attributes).forEach(([key, value]) => {
if (value !== undefined) {
// 카멜케이스를 대시 케이스로 변환
const attributeName = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
element.setAttribute(attributeName, value.toString());
}
});
return element;
}
이 함수를 사용하면 각 SVG 요소에 맞는 속성만 설정할 수 있어요. 잘못된 속성을 설정하려고 하면 TypeScript가 컴파일 오류를 발생시키죠! 🛡️
7.3 제네릭 SVG 이벤트 핸들러
SVG 요소에 이벤트 핸들러를 타입 안전하게 추가하는 제네릭 함수를 만들어볼게요.
// SVG 이벤트 맵 타입 정의
interface SVGEventMap {
'click': MouseEvent;
'mousemove': MouseEvent;
'mouseenter': MouseEvent;
'mouseleave': MouseEvent;
'touchstart': TouchEvent;
'touchmove': TouchEvent;
'touchend': TouchEvent;
// 기타 이벤트들...
}
// 제네릭 이벤트 핸들러 추가 함수
function addSVGEventListener<K extends keyof SVGEventMap>(
element: SVGElement,
eventType: K,
handler: (event: SVGEventMap[K]) => void,
options?: boolean | AddEventListenerOptions
): void {
element.addEventListener(eventType, handler as EventListener, options);
}
// 제네릭 이벤트 핸들러 제거 함수
function removeSVGEventListener<K extends keyof SVGEventMap>(
element: SVGElement,
eventType: K,
handler: (event: SVGEventMap[K]) => void,
options?: boolean | EventListenerOptions
): void {
element.removeEventListener(eventType, handler as EventListener, options);
}
이 함수들을 사용하면 각 이벤트 타입에 맞는 이벤트 객체를 정확하게 처리할 수 있어요. 타입 안전한 이벤트 처리가 가능하죠! 🎯
7.4 제네릭 SVG 애니메이션 함수
SVG 요소에 애니메이션을 적용하는 제네릭 함수를 만들어볼게요.
// SVG 애니메이션 속성 타입 정의
type AnimatableProps<T> = {
[K in keyof T]?: {
from: T[K];
to: T[K];
duration?: number;
easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
delay?: number;
iterations?: number | 'infinite';
direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
fill?: 'none' | 'forwards' | 'backwards' | 'both';
};
};
// 제네릭 SVG 애니메이션 함수
function animateSVG<K extends keyof SVGAttributeMap>(
element: SVGElementTypeMap[K],
animations: AnimatableProps<SVGAttributeMap[K]>
): Animation[] {
return Object.entries(animations).map(([prop, config]) => {
if (!config) return null;
const { from, to, duration = 1000, easing = 'linear', delay = 0, iterations = 1, direction = 'normal', fill = 'none' } = config;
// 카멜케이스를 대시 케이스로 변환
const attributeName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
// 키프레임 생성
const keyframes = [
{ [attributeName]: from },
{ [attributeName]: to }
];
// 애니메이션 옵션 생성
const options: KeyframeAnimationOptions = {
duration,
easing,
delay,
iterations,
direction,
fill
};
// 애니메이션 실행
return element.animate(keyframes, options);
}).filter(Boolean) as Animation[];
}
이 함수를 사용하면 각 SVG 요소에 맞는 속성만 애니메이션화할 수 있어요. 타입 안전한 애니메이션 적용이 가능하죠! 🎬
TypeScript의 제네릭을 활용하면 SVG 조작을 더 유연하고 타입 안전하게 만들 수 있어요. 재능넷에서도 이런 고급 테크닉을 활용한 프로젝트가 많이 진행되고 있답니다. 특히 복잡한 데이터 시각화나 인터랙티브 애플리케이션 개발에 많이 활용되고 있어요! 🚀
8. 실무 팁: SVG 타입 정의의 모범 사례 💼
실제 프로젝트에서 SVG와 TypeScript를 함께 사용할 때 알아두면 좋은 모범 사례들을 알아볼게요!
8.1 SVG 타입 정의 모듈화
큰 프로젝트에서는 SVG 타입 정의를 모듈화하는 것이 좋아요.
// types/svg/elements.ts
export interface SVGElementTypeMap {
'svg': SVGSVGElement;
'circle': SVGCircleElement;
'rect': SVGRectElement;
'path': SVGPathElement;
// 기타 요소들...
}
// types/svg/attributes.ts
export interface SVGCircleAttributes {
cx?: number | string;
cy?: number | string;
r?: number | string;
// 기타 속성들...
}
export interface SVGRectAttributes {
x?: number | string;
y?: number | string;
width?: number | string;
height?: number | string;
// 기타 속성들...
}
export interface SVGAttributeMap {
'circle': SVGCircleAttributes;
'rect': SVGRectAttributes;
// 기타 요소들...
}
// types/svg/events.ts
export interface SVGEventMap {
'click': MouseEvent;
'mousemove': MouseEvent;
// 기타 이벤트들...
}
// types/svg/index.ts
export * from './elements';
export * from './attributes';
export * from './events';
이렇게 모듈화하면 코드 구조가 깔끔해지고 재사용성이 높아져요. 프로젝트가 커질수록 이런 구조화의 이점이 더 커지죠! 📚
8.2 타입 가드 활용하기
SVG 요소를 다룰 때 타입 가드를 활용하면 더 안전하게 코딩할 수 있어요.
// SVG 요소 타입 가드
function isSVGElement(element: Element): element is SVGElement {
return element instanceof SVGElement;
}
function isSVGCircleElement(element: Element): element is SVGCircleElement {
return element instanceof SVGCircleElement;
}
function isSVGRectElement(element: Element): element is SVGRectElement {
return element instanceof SVGRectElement;
}
// 사용 예시
function processSVGElement(element: Element): void {
if (isSVGElement(element)) {
// 여기서 element는 SVGElement 타입
console.log('SVG 요소입니다');
if (isSVGCircleElement(element)) {
// 여기서 element는 SVGCircleElement 타입
console.log(`원의 반지름: ${element.r.baseVal.value}`);
} else if (isSVGRectElement(element)) {
// 여기서 element는 SVGRectElement 타입
console.log(`사각형의 너비: ${element.width.baseVal.value}`);
}
}
}
타입 가드를 사용하면 타입 단언(as)보다 더 안전하게 타입을 좁힐 수 있어요. 런타임에 실제 타입을 확인하기 때문이죠! 🔍
8.3 유틸리티 타입 활용하기
TypeScript의 유틸리티 타입을 활용하면 SVG 타입 정의를 더 유연하게 만들 수 있어요.
// 공통 SVG 속성 타입 정의
interface SVGCommonAttributes {
fill?: string;
stroke?: string;
strokeWidth?: number | string;
opacity?: number | string;
transform?: string;
// 기타 공통 속성들...
}
// 특정 요소의 속성은 공통 속성을 확장
type SVGCircleSpecificAttributes = {
cx?: number | string;
cy?: number | string;
r?: number | string;
};
// 유틸리티 타입을 사용해 합치기
type SVGCircleAttributes = SVGCommonAttributes & SVGCircleSpecificAttributes;
// Partial 유틸리티 타입 활용
function updateCircle(circle: SVGCircleElement, attributes: Partial<SVGCircleAttributes>): void {
// 속성 업데이트 로직...
}
// Pick 유틸리티 타입 활용
function updateCirclePosition(circle: SVGCircleElement, position: Pick<SVGCircleAttributes, 'cx' | 'cy'>): void {
// 위치 업데이트 로직...
}
// Omit 유틸리티 타입 활용
function updateCircleStyle(circle: SVGCircleElement, style: Omit<SVGCircleAttributes, 'cx' | 'cy' | 'r'>): void {
// 스타일 업데이트 로직...
}
유틸리티 타입을 활용하면 기존 타입을 변형해 새로운 타입을 만들 수 있어요. 코드 중복을 줄이고 타입 시스템을 더 유연하게 활용할 수 있죠! 🧰
8.4 타입 안전한 SVG 문자열 처리
SVG를 문자열로 처리할 때도 타입 안전성을 유지하는 방법을 알아볼게요.
// SVG 문자열 생성을 위한 타입 정의
type SVGStringAttributes = Record<string, string | number | boolean>;
interface SVGStringElementOptions {
tagName: string;
attributes?: SVGStringAttributes;
content?: string;
children?: SVGStringElement[];
}
interface SVGStringElement {
toString(): string;
}
// SVG 문자열 요소 클래스
class SVGStringElementImpl implements SVGStringElement {
private options: SVGStringElementOptions;
constructor(options: SVGStringElementOptions) {
this.options = options;
}
toString(): string {
const { tagName, attributes = {}, content, children = [] } = this.options;
// 속성 문자열 생성
const attributesString = Object.entries(attributes)
.map(([key, value]) => {
// 카멜케이스를 대시 케이스로 변환
const attributeName = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
return `${attributeName}="${value}"`;
})
.join(' ');
// 자식 요소가 없고 내용도 없으면 자체 닫는 태그 사용
if (children.length === 0 && !content) {
return `<${tagName}${attributesString ? ' ' + attributesString : ''}/>`;
}
// 자식 요소나 내용이 있으면 여는 태그와 닫는 태그 사용
return `<${tagName}${attributesString ? ' ' + attributesString : ''}>${
content || ''
}${
children.map(child => child.toString()).join('')
}</${tagName}>`;
}
}
// SVG 문자열 생성 함수
function createSVGString(options: SVGStringElementOptions): SVGStringElement {
return new SVGStringElementImpl(options);
}
// 사용 예시
const svgString = createSVGString({
tagName: 'svg',
attributes: {
width: 100,
height: 100,
viewBox: '0 0 100 100'
},
children: [
createSVGString({
tagName: 'circle',
attributes: {
cx: 50,
cy: 50,
r: 40,
fill: 'red'
}
})
]
}).toString();
console.log(svgString);
// 출력: <svg width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="red"/></svg>
이런 방식으로 SVG 문자열을 생성할 때도 타입 안전성을 유지할 수 있어요. 문자열 템플릿을 직접 작성하는 것보다 훨씬 안전하고 유지보수하기 쉽죠! 📝
이런 모범 사례들을 적용하면 SVG와 TypeScript를 함께 사용할 때 더 안정적이고 유지보수하기 쉬운 코드를 작성할 수 있어요. 재능넷에서 진행되는 프로젝트에서도 이런 모범 사례를 적용하면 협업 효율성이 크게 향상된답니다! 🌟
9. 마무리 및 추가 리소스 📚
지금까지 SVG 조작을 위한 TypeScript 타입 정의에 대해 자세히 알아봤어요. 이제 마무리하면서 몇 가지 추가 리소스와 팁을 공유할게요!
9.1 배운 내용 정리
지금까지 우리가 배운 내용을 간략하게 정리해볼게요:
- SVG와 TypeScript의 기본 개념 이해
- SVG DOM 요소를 위한 TypeScript 인터페이스 정의
- SVG 속성과 메서드의 타입 정의 방법
- 인터랙티브 SVG 컴포넌트 만들기
- SVG 애니메이션과 타입 안전한 조작 방법
- SVG 최적화 기법과 TypeScript 활용
- SVG와 TypeScript 제네릭 활용 테크닉
- SVG 타입 정의의 모범 사례
TypeScript로 SVG를 다루면 코드의 안정성과 개발 생산성이 크게 향상돼요. 특히 복잡한 SVG 조작이 필요한 프로젝트에서는 더욱 그 가치가 빛나죠! 💎
9.2 추천 라이브러리
SVG와 TypeScript를 함께 사용할 때 도움이 되는 라이브러리들을 소개할게요:
- D3.js: 데이터 시각화를 위한 강력한 라이브러리로, TypeScript 타입 정의가 잘 되어 있어요.
- SVG.js: SVG 조작을 위한 경량 라이브러리로, TypeScript 지원이 우수해요.
- React-SVG: React에서 SVG를 쉽게 사용할 수 있게 해주는 라이브러리예요.
- GSAP: 강력한 애니메이션 라이브러리로, SVG 애니메이션에 특히 유용해요.
- Snap.svg: 모던 브라우저를 위한 SVG 라이브러리로, 다양한 기능을 제공해요.
이런 라이브러리들은 이미 TypeScript 타입 정의가 잘 되어 있어서 바로 사용할 수 있어요. 재능넷에서 프로젝트를 진행할 때도 이런 검증된 라이브러리를 활용하면 개발 시간을 크게 단축할 수 있답니다! 🚀
9.3 학습 리소스
SVG와 TypeScript에 대해 더 깊이 학습하고 싶다면 다음 리소스들을 참고해보세요:
- MDN Web Docs: SVG에 대한 가장 포괄적인 문서를 제공해요.
- TypeScript 공식 문서: TypeScript의 모든 기능을 자세히 설명해요.
- SVG Essentials: SVG의 기초부터 고급 기능까지 다루는 책이에요.
- Programming TypeScript: TypeScript의 깊이 있는 이해를 돕는 책이에요.
- CSS-Tricks SVG 가이드: SVG 사용에 대한 실용적인 팁을 제공해요.
이런 리소스들을 통해 SVG와 TypeScript에 대한 지식을 더욱 넓히고 깊게 할 수 있어요. 지식은 끝이 없으니 계속해서 학습하는 것이 중요하죠! 📖
9.4 마치며
SVG와 TypeScript의 조합은 웹 개발에서 강력한 시각적 경험을 타입 안전하게 구현할 수 있는 훌륭한 방법이에요. 이 글이 여러분의 SVG 개발 여정에 도움이 되었기를 바랍니다!
재능넷에서는 이런 최신 기술을 활용한 다양한 프로젝트가 진행되고 있어요. SVG와 TypeScript를 활용한 시각화 프로젝트나 인터랙티브 웹 개발에 관심이 있으시다면 재능넷을 통해 전문가들과 연결될 수 있답니다! 🌟
질문이나 의견이 있으시면 언제든지 댓글로 남겨주세요. 함께 성장하는 개발 커뮤니티를 만들어가요! 😊
행복한 코딩 되세요! 🚀
댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.
아직 댓글이 없습니다.