> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/ui-1/migration.md).

# 마이그레이션 가이드

이 가이드는 UI1 (`decentraland-ui`)에서 UI2 (`decentraland-ui2`).

## 마이그레이션 시점

마이그레이션은 다음 세 가지 유효한 이유로 시작할 수 있습니다:

### 1. 기술적 개선

다음과 같은 이유로 컴포넌트를 마이그레이션하려는 경우:

* 더 나은 테마 지원
* 개선된 TypeScript 타입
* 다른 UI2 컴포넌트와의 일관성
* 성능 최적화
* 접근성 개선

### 2. 컴포넌트 업데이트

* UI1 컴포넌트를 업데이트해야 합니다
* 아직 UI2에 해당하는 것이 없습니다
* **요구사항**: 먼저 UI2에 생성한 다음 사용하세요

### 3. 프로젝트 요구사항

* 이 컴포넌트는 새 프로젝트 또는 기존 프로젝트에서 사용될 것입니다
* 올바르게 마이그레이션할 시간이 있습니다
* 프로젝트 리소스로 충분히 마이그레이션할 수 있습니다

{% hint style="warning" %}
**하지 마세요** 컴포넌트를 "그냥" 마이그레이션하지 마세요. 각 마이그레이션은 명확한 비즈니스 또는 기술적 근거가 있어야 합니다.
{% endhint %}

***

## 마이그레이션 과정

### 1단계: 계획

마이그레이션을 시작하기 전에:

1. **의존성 파악**
   * 어떤 다른 컴포넌트를 사용하나요?
   * 현재 어떤 프로젝트에서 사용하나요?
   * 예정된 breaking change가 있나요?
2. **현재 사용 현황 검토**
   * 이 컴포넌트를 사용하는 프로젝트는 몇 개인가요?
   * 가장 자주 사용되는 props는 무엇인가요?
   * 알려진 문제가 있나요?
3. **범위 정의**
   * 이것은 1:1 마이그레이션인가요?
   * 예정된 개선 사항이 있나요?
   * 일정은 어떻게 되나요?

### 2단계: UI2 컴포넌트 생성

를 따르세요 [커스텀 컴포넌트](/contributor/contributor-ko/contributor-guides/ui-1/custom-components.md) UI2 후보 컴포넌트 가이드.

**요구사항:**

* styled-components에 object syntax 사용
* 테마 값만 사용(임의의 값 사용 금지)
* 포괄적인 Storybook 스토리 추가
* 완전한 테스트 작성
* 모든 props와 동작 문서화

**예시 구조:**

```
ui2/src/components/Button/
├── Button.tsx
├── Button.styles.ts
├── Button.stories.tsx
├── Button.test.tsx
├── types.ts
├── index.ts
└── README.md
```

### 3단계: 호환성 유지

UI2 컴포넌트는 **반드시** UI1 버전과 동일한 props와 동작을 제공해야 합니다.

#### 동일한 Props

```tsx
// UI1 Button
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  onClick?: () => void;
  disabled?: boolean;
  children: React.ReactNode;
}

// UI2 Button - 동일한 props를 반드시 지원해야 함
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  onClick?: () => void;
  disabled?: boolean;
  children: React.ReactNode;
  // 새 optional props를 추가할 수 있음
  variant?: 'text' | 'outlined' | 'contained';
}
```

#### 하위 호환 변경

props를 변경하거나 추가해야 한다면:

1. **먼저**, UI1에 새 props를 optional로 추가하세요
2. **그다음**, UI1 사용자를 새 props를 사용하도록 마이그레이션하세요
3. **마지막으로**, 새 API로 UI2 컴포넌트를 만드세요

```tsx
// 1단계: UI1에 optional prop 추가
interface ButtonProps {
  primary?: boolean;
  // 새 optional prop
  variant?: 'primary' | 'secondary';
}

// 2단계: UI1 구현 업데이트
export function Button({ primary, variant = primary ? 'primary' : 'secondary' }: ButtonProps) {
  // 내부적으로 primary 대신 variant 사용
}

// 3단계: 새 API로 UI2 생성
interface ButtonProps {
  // 이제 variant가 주요 prop입니다
  variant?: 'primary' | 'secondary';
  // 호환성을 위해 primary를 유지하고 deprecated로 표시
  /** @deprecated 대신 variant를 사용하세요 */
  primary?: boolean;
}
```

### 4단계: UI1 컴포넌트 deprecate

UI1 컴포넌트에 deprecation 안내를 추가하세요:

````tsx
/**
 * @deprecated 이 컴포넌트는 UI2로 마이그레이션되었습니다.
 * 대신 'decentraland-ui2'에서 import하세요:
 * 
 * ```tsx
 * import { Button } from 'decentraland-ui2';
 * ```
 * 
 * 마이그레이션 가이드 보기: https://docs.decentraland.org/contributor-guides/web-ui-standards/migration
 */
export function Button(props: ButtonProps) {
  // ... 기존 구현
}
````

### 5단계: 점진적 도입

즉시 마이그레이션을 강요하지 마세요. 점진적 도입을 허용하세요:

1. **배포** UI2 컴포넌트
2. **문서화** 마이그레이션 경로
3. **업데이트** 새 프로젝트는 UI2를 사용하도록
4. **마이그레이션** 기존 프로젝트는 필요할 때 점진적으로
5. **계획** 최종적으로 UI1 제거(공지 포함)

***

## 마이그레이션 예시

### 예시 1: 단순 컴포넌트

기본 `Card` 컴포넌트 마이그레이션:

#### UI1 버전

```tsx
// decentraland-ui/src/components/Card/Card.tsx
import React from 'react';
import './Card.css';

export interface CardProps {
  className?: string;
  children: React.ReactNode;
}

export function Card({ className, children }: CardProps) {
  return (
    <div className={`dcl-card ${className || ''}`}>
      {children}
    </div>
  );
}
```

#### UI2 버전

```tsx
// decentraland-ui2/src/components/Card/Card.tsx
import { styled } from '@mui/material/styles';

export interface CardProps {
  className?: string;
  children: React.ReactNode;
}

const StyledCard = styled('div')(({ theme }) => ({
  backgroundColor: theme.palette.background.paper,
  borderRadius: theme.shape.borderRadius,
  padding: theme.spacing(2),
  boxShadow: theme.shadows[1],
  
  [theme.breakpoints.down('sm')]: {
    padding: theme.spacing(1),
  },
}));

export function Card({ className, children }: CardProps) {
  return (
    <StyledCard className={className}>
      {children}
    </StyledCard>
  );
}
```

### 예시 2: 변형이 있는 컴포넌트

다음의 `Button` 변형과 함께 마이그레이션:

#### UI1 버전

```tsx
// UI1
import './Button.css';

interface ButtonProps {
  primary?: boolean;
  secondary?: boolean;
  size?: 'small' | 'medium' | 'large';
}

export function Button({ primary, secondary, size = 'medium', ...props }: ButtonProps) {
  const classes = [
    'dcl-button',
    primary && 'primary',
    secondary && 'secondary',
    `size-${size}`,
  ];.filter(Boolean).join(' ');
  
  return <button className={classes} {...props} />;
}
```

#### UI2 버전

```tsx
// UI2
import { styled } from '@mui/material/styles';

interface ButtonProps {
  /** @deprecated 대신 variant="contained"를 사용하세요 */
  primary?: boolean;
  /** @deprecated 대신 variant="outlined"를 사용하세요 */
  secondary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
  size?: 'small' | 'medium' | 'large';
}

const StyledButton = styled('button')<ButtonProps>(({ theme, variant = 'contained', size = 'medium' }) => {
  const sizes = {
    small: theme.spacing(0.5, 1),
    medium: theme.spacing(1, 2),
    large: theme.spacing(1.5, 3),
  };
  
  const variants = {
    text: {
      backgroundColor: 'transparent',
      color: theme.palette.primary.main,
    },
    outlined: {
      backgroundColor: 'transparent',
      color: theme.palette.primary.main,
      border: `1px solid ${theme.palette.primary.main}`,
    },
    contained: {
      backgroundColor: theme.palette.primary.main,
      color: theme.palette.primary.contrastText,
    },
  };
  
  return {
    padding: sizes[size],
    borderRadius: theme.shape.borderRadius,
    border: 'none',
    cursor: 'pointer',
    ...variants[variant],
    
    '&:hover': {
      opacity: 0.9,
    },
    
    '&:disabled': {
      opacity: 0.5,
      cursor: 'not-allowed',
    },
  };
});

export function Button({ 
  primary, 
  secondary, 
  variant, 
  ...props 
}: ButtonProps) {
  // deprecated props 처리
  const actualVariant = variant || 
    (primary ? 'contained' : secondary ? 'outlined' : 'text');
  
  return <StyledButton variant={actualVariant} {...props} />;
}
```

***

## 마이그레이션 체크리스트

각 컴포넌트 마이그레이션에 이 체크리스트를 사용하세요:

### 계획 단계

* [ ] 컴포넌트를 사용하는 모든 프로젝트 식별
* [ ] 현재 props와 동작 문서화
* [ ] 마이그레이션 범위와 일정 정의
* [ ] 이해관계자 승인 받기

### 구현 단계

* [ ] 표준을 따라 UI2 컴포넌트 생성
* [ ] props 호환성 유지
* [ ] 스타일링에 object syntax 사용
* [ ] 테마 값만 사용
* [ ] 모든 상태 구현(대기, 호버, 포커스, 비활성, 오류)
* [ ] 포괄적인 Storybook 스토리 추가
* [ ] 단위 테스트 작성
* [ ] 접근성 기능 문서화

### deprecation 단계

* [ ] UI1 컴포넌트에 deprecation 안내 추가
* [ ] UI1 문서 업데이트
* [ ] 사용자를 위한 마이그레이션 가이드 생성
* [ ] UI2 컴포넌트 배포

### 도입 단계

* [ ] 새 프로젝트는 UI2를 사용하도록 업데이트
* [ ] 기존 프로젝트를 위한 마이그레이션 PR 생성
* [ ] 문제 모니터링
* [ ] 피드백 수집
* [ ] UI1 제거 일정 계획

***

## 일반적인 마이그레이션 패턴

### CSS에서 Styled Components로

```tsx
// UI1: CSS 파일
.dcl-card {
  background: #fff;
  padding: 16px;
  border-radius: 8px;
}

// UI2: Styled component
const Card = styled('div')(({ theme }) => ({
  backgroundColor: theme.palette.background.paper,
  padding: theme.spacing(2),
  borderRadius: theme.shape.borderRadius,
}));
```

### 클래스 이름에서 Props로

```tsx
// UI1: 클래스 기반 변형
<Button className={primary ? 'primary' : 'secondary'} />

// UI2: prop 기반 변형
<Button variant={primary ? 'contained' : 'outlined'} />
```

### 고정값에서 테마로

```tsx
// UI1: 고정값
const styles = {
  color: '#333',
  fontSize: '14px',
  padding: '8px 16px',
};

// UI2: 테마 값
const Component = styled('div')(({ theme }) => ({
  color: theme.palette.text.primary,
  fontSize: theme.typography.body2.fontSize,
  padding: theme.spacing(1, 2),
}));
```

***

## Breaking Change

때로는 breaking change가 필요합니다. 신중하게 처리하세요:

### Breaking Change가 허용되는 경우

* 보안 수정
* 치명적인 버그
* 주요 버전 업데이트
* 더 이상 사용되지 않는 기능 제거(공지 포함)

### Breaking Change 처리 방법

1. **미리 공지** - 변경 사항을 충분히 미리 전달
2. **마이그레이션 경로 제공** - 업데이트 방법 문서화
3. **버전 업** - 시맨틱 버저닝 준수
4. **deprecation 기간** - 마이그레이션할 시간을 제공
5. **Codemod** - 가능하면 자동 마이그레이션 도구 제공

### 예시: 더 이상 사용되지 않는 props 제거

```tsx
// 버전 1.0: 새 API 도입, 기존 API는 deprecated
interface ButtonProps {
  /** @deprecated 대신 variant="contained"를 사용하세요 */
  primary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
}

// 버전 1.5: 제거 예정 경고
interface ButtonProps {
  /** @deprecated 2.0에서 제거됩니다. 대신 variant를 사용하세요 */
  primary?: boolean;
  variant?: 'text' | 'outlined' | 'contained';
}

// 버전 2.0: deprecated prop 제거
interface ButtonProps {
  variant?: 'text' | 'outlined' | 'contained';
}
```

***

## 마이그레이션 테스트

마이그레이션된 컴포넌트가 올바르게 동작하는지 확인하세요:

### 시각적 회귀 테스트

UI1과 UI2 컴포넌트를 시각적으로 비교:

```tsx
// 비교를 위한 Storybook 스토리
export const ComparisonStory: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: '2rem' }}>
      <div>
        <h3>UI1</h3>
        <UI1Button primary>Click Me</UI1Button>
      </div>
      <div>
        <h3>UI2</h3>
        <UI2Button variant="contained">Click Me</UI2Button>
      </div>
    </div>
  ),
};
```

### 동작 테스트

props가 동일하게 동작하는지 확인하세요:

```tsx
describe('Button migration', () => {
  it('primary prop(더 이상 사용되지 않음)을 variant="contained"와 동일하게 처리해야 합니다', () => {
    const { container: ui1 } = render(<UI1Button primary>Test</UI1Button>);
    const { container: ui2 } = render(<UI2Button primary>Test</UI2Button>);
    
    // 렌더링 결과 비교
    expect(ui1.textContent).toBe(ui2.textContent);
  });
});
```

***

## 문서 업데이트

마이그레이션 후 문서를 업데이트하세요:

### UI1 컴포넌트 문서 업데이트

```markdown
# Button (UI1 - Deprecated)

> ⚠️ **이 컴포넌트는 UI2로 마이그레이션되었습니다.**
> 새 버전은 [UI2 Button 문서](../ui2/button)를 참조하세요.

이 컴포넌트는 deprecated이며 향후 버전에서 제거될 예정입니다.
UI2로 마이그레이션하세요.

## 마이그레이션 가이드

자세한 내용은 [마이그레이션 가이드](./migration)를 참조하세요.
```

### UI2 컴포넌트 문서 생성

```markdown
# Button (UI2)

완전한 테마 지원을 갖춘 현대적인 버튼 컴포넌트.

## UI1에서 마이그레이션

UI1에서 마이그레이션 중이라면:

- `primary` prop → `variant="contained"`
- `secondary` prop → `variant="outlined"`
- CSS classes → styled-components

자세한 내용은 전체 [마이그레이션 가이드](./migration)를 참조하세요.
```

***

## 다음 단계

* 검토 [커스텀 컴포넌트](/contributor/contributor-ko/contributor-guides/ui-1/custom-components.md) UI2 컴포넌트 생성용
* 참고 [스타일링 및 테마](/contributor/contributor-ko/contributor-guides/ui-1/styling-and-theming.md) 스타일링 표준용
* 확인 [프로세스 개요](/contributor/contributor-ko/contributor-guides/ui-1/process-overview.md) 전체 워크플로용


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/ui-1/migration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
