v3에서 v4로 마이그레이션

v3에서 v4로 마이그레이션 (Migration from v3 to v4)

드디어 v4가 출시됐어요! 이 문서는 Material UI v3에서 v4로 사이트를 업그레이드하기 위한 참고 자료랍니다. 여기서 다루는 내용이 많지만, 여러분의 사이트에 모든 것을 적용할 필요는 없을 거예요. 따라가기 쉽고, 가능한 한 순차적으로 정리되어 있으니 v4에서 빠르게 작업을 시작할 수 있을 거예요.

출처: 문서

본문

v3 문서를 찾고 있나요? 최신 버전은 여기에서 확인할 수 있어요.

:::info 이 문서는 작업 진행 중(work in progress)이에요. 사이트를 업그레이드하면서 여기에 다루지 않은 문제를 만났나요? GitHub에서 여러분의 변경 사항을 추가해 주세요. :::

소개 (Introduction)

이 문서는 Material UI v3에서 v4로 사이트를 업그레이드하는 데 도움이 되는 참고 자료예요. 여기서 다루는 내용이 많지만, 여러분의 사이트에 모든 것을 적용할 필요는 없을 거예요. 따라가기 쉽고 가능한 한 순차적으로 정리되어 있으니 v4에서 빠르게 작업을 시작할 수 있게 도와드릴게요!

왜 마이그레이션해야 하나요 (Why you should migrate)

이 문서 페이지는 v3에서 v4로 마이그레이션하는 방법(how) 을 다룹니다. 이유(why) 는 Medium의 릴리스 블로그 포스트에서 확인할 수 있어요.

의존성 업데이트 (Updating your dependencies)

가장 먼저 해야 할 일은 의존성을 업데이트하는 거예요.

Material UI 버전 업데이트

package.json을 업데이트해서 최신 버전의 Material UI를 사용해야 해요.

"dependencies": {
  "@material-ui/core": "^4.0.0"
}

또는 다음을 실행하세요

npm install @material-ui/core

or

yarn add @material-ui/core

React 버전 업데이트

React의 최소 요구 버전이 react@^16.3.0에서 react@^16.8.0으로 올라갔어요. 이제 Hooks에 의존할 수 있게 되었기 때문이에요 (더 이상 class API를 사용하지 않아요).

Material UI Styles 버전 업데이트

v3에서 @material-ui/styles를 사용하고 있었다면, package.json을 업데이트해서 최신 버전의 Material UI Styles를 사용해야 해요.

"dependencies": {
  "@material-ui/styles": "^4.0.0"
}

또는 다음을 실행하세요

npm install @material-ui/styles

or

yarn add @material-ui/styles

브레이킹 체인지 처리 (Handling breaking changes)

Core (핵심)

  • 모든 컴포넌트가 자신의 ref를 전달해요. 이는 React.forwardRef()를 사용해서 구현됩니다. 이는 내부 컴포넌트 트리와 display name에 영향을 주므로 shallow 또는 snapshot 테스트가 깨질 수 있어요. innerRef는 더 이상 인스턴스에 대한 ref를 반환하지 않고(내부 컴포넌트가 함수 컴포넌트라면 아무것도 반환하지 않음), 대신 루트 컴포넌트에 대한 ref를 반환해요. 해당 API 문서에 루트 컴포넌트가 명시되어 있습니다.

Styles (스타일)

  • ⚠️ Material UI는 JSS v10에 의존해요. JSS v10은 v9와 하위 호환되지 않습니다. 환경에 JSS v9가 설치되어 있지 않은지 확인하세요. (package.json에서 react-jss를 제거하는 것이 도움이 될 수 있어요.) StylesProvider 컴포넌트가 JssProvider를 대체합니다.

  • withTheme()의 첫 번째 옵션 인자를 제거하세요. (첫 번째 인자는 결코 실현되지 않았던 잠재적 미래 옵션을 위한 자리표시자였어요.)

    이는 emotion API와 styled-components API와 일치합니다.

    -const DeepChild = withTheme()(DeepChildRaw);
    +const DeepChild = withTheme(DeepChildRaw);
    
  • convertHexToRGB를 hexToRgb로 이름을 바꾸세요.

    -import { convertHexToRgb } from '@material-ui/core/styles/colorManipulator';
    +import { hexToRgb } from '@material-ui/core/styles';
    
  • keyframes API를 스코프하세요. 코드베이스에서 다음 변경 사항을 적용해야 해요. 이는 애니메이션 로직을 격리하는 데 도움이 됩니다:

      rippleVisible: {
        opacity: 0.3,
    -   animation: 'mui-ripple-enter 100ms cubic-bezier(0.4, 0, 0.2, 1)',
    +   animation: '$mui-ripple-enter 100ms cubic-bezier(0.4, 0, 0.2, 1)',
      },
      '@keyframes mui-ripple-enter': {
        '0%': {
          opacity: 0.1,
        },
        '100%': {
          opacity: 0.3,
        },
      },
    

Theme (테마)

  • theme.palette.augmentColor() 메서드는 더 이상 입력 색상에 부작용(side effect)을 수행하지 않아요. 올바르게 사용하려면 반환된 값을 사용해야 합니다.

    -const background = { main: color };
    -theme.palette.augmentColor(background);
    +const background = theme.palette.augmentColor({ main: color });
    
     console.log({ background });
    
  • 테마 생성에서 다음 변형을 안전하게 제거할 수 있어요:

     typography: {
    -  useNextVariants: true,
     },
    
  • theme.spacing.unit 사용은 더 이상 사용되지 않아요(deprecated). 새 API를 사용하면 됩니다:

     label: {
       [theme.breakpoints.up('sm')]: {
    -    paddingTop: theme.spacing.unit * 12,
    +    paddingTop: theme.spacing(12),
       },
     }
    

    팁: 인자를 1개 이상 제공할 수 있어요: theme.spacing(1, 2) // = '8px 16px'.

    프로젝트에서 마이그레이션 헬퍼를 사용하면 더 수월하게 진행할 수 있어요.

Layout (레이아웃)

  • [Grid] 임의의 spacing 값을 지원하고 8 단위로 머릿속 계산할 필요를 없애기 위해 spacing API를 변경해요:

      /**
       * Defines the space between the type `item` component.
       * It can only be used on a type `container` component.
       */
    -  spacing: PropTypes.oneOf([0, 8, 16, 24, 32, 40]),
    +  spacing: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
    

    앞으로 테마를 사용해 커스텀 Grid spacing 변환 함수를 구현할 수 있어요.

  • [Container] @material-ui/lab에서 @material-ui/core로 이동했어요.

    -import Container from '@material-ui/lab/Container';
    +import Container from '@material-ui/core/Container';
    

TypeScript

value 타입

입력 컴포넌트의 value prop 타입을 unknown으로 정규화했어요. 이는 InputBase, NativeSelect, OutlinedInput, Radio, RadioGroup, Select, SelectInput, Switch, TextArea, TextField에 영향을 줍니다.

 function MySelect({ children }) {
-  const handleChange = (event: any, value: string) => {
+  const handleChange = (event: any, value: unknown) => {
     // handle value
   };

   return <Select onChange={handleChange}>{children}</Select>
 }

이 변경 사항은 TypeScript 가이드에서 더 자세히 설명하고 있어요.

Button (버튼)

  • [Button] 더 이상 사용되지 않는 버튼 변형(flat, raised, fab)을 제거하세요:

    -<Button variant="raised" />
    +<Button variant="contained" />
    
    -<Button variant="flat" />
    +<Button variant="text" />
    
    -import Button from '@material-ui/core/Button';
    -<Button variant="fab" />
    +import Fab from '@material-ui/core/Fab';
    +<Fab />
    
    -import Button from '@material-ui/core/Button';
    -<Button variant="extendedFab" />
    +import Fab from '@material-ui/core/Fab';
    +<Fab variant="extended" />
    
  • [ButtonBase] component prop에 전달되는 컴포넌트는 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.

    이는 BottomNavigationAction, Button, CardActionArea, Checkbox, ExpansionPanelSummary, Fab, IconButton, MenuItem, Radio, StepButton, Tab, TableSortLabel 그리고 button prop이 true일 때의 ListItem에도 적용돼요.

Card (카드)

  • [CardActions] disableActionSpacing prop을 disableSpacing으로 이름을 바꾸세요.
  • [CardActions] disableActionSpacing CSS 클래스를 제거하세요.
  • [CardActions] action CSS 클래스를 spacing으로 이름을 바꾸세요.

ClickAwayListener

  • [ClickAwayListener] react-event-listener props를 숨기세요.

Dialog (다이얼로그)

  • [DialogActions] disableActionSpacing prop을 disableSpacing으로 이름을 바꾸세요.
  • [DialogActions] action CSS 클래스를 spacing으로 이름을 바꾸세요.
  • [DialogContentText] subtitle1 대신 typography 변형 body1을 사용하세요.
  • [Dialog] 자식은 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.

Divider (구분선)

  • [Divider] 더 이상 사용되지 않는 inset prop을 제거하세요.

    -<Divider inset />
    +<Divider variant="inset" />
    

ExpansionPanel (확장 패널)

  • [ExpansionPanelActions] action CSS 클래스를 spacing으로 이름을 바꾸세요.
  • [ExpansionPanel] disabled와 expanded 스타일 규칙의 CSS 특이성을 높이세요.
  • [ExpansionPanel] CollapseProps prop을 TransitionProps로 이름을 바꾸세요.

List (목록)

  • [List] 스펙에 맞게 목록 컴포넌트를 재작업했어요:

    • 아바타를 사용할 때는 ListItemAvatar 컴포넌트가 필요해요.
    • 왼쪽 체크박스를 사용할 때는 ListItemIcon 컴포넌트가 필요해요.
    • 아이콘 버튼에 edge 속성을 설정해야 해요.
  • [List] dense는 더 이상 List 요소의 위아래 padding을 줄이지 않아요.

  • [ListItem] disabled와 focusVisible 스타일 규칙의 CSS 특이성을 높이세요.

  • [MenuItem] MenuItem의 고정 높이를 제거하세요. 이제 padding과 line-height를 브라우저가 높이를 계산하는 데 사용해요.
  • [Modal] 자식은 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.

    이는 Dialog와 Popover에도 적용돼요.

  • [Modal] Modal 컴포넌트의 classes 커스터마이제이션 API를 제거하세요 (별도로 사용할 때 번들 크기가 -74% 줄어들어요).

  • [Modal] event.defaultPrevented는 이제 무시돼요. 새 로직은 키 다운 이스케이프 이벤트에서 event.preventDefault()가 호출되어도 Modal을 닫아요. event.preventDefault()는 체크박스를 클릭해 체크하는 것, 버튼을 눌러 폼을 제출하는 것, 왼쪽 화살표를 눌러 텍스트 입력에서 커서를 움직이는 것 같은 기본 동작을 막기 위한 것이에요. 이런 기본 동작을 가진 것은 특수 HTML 요소뿐입니다. 모달에서 onClose 이벤트를 트리거하고 싶지 않다면 event.stopPropagation()을 사용해야 해요.

Paper

  • [Paper] 기본 elevation(고도)을 줄이세요. 기본 Paper elevation을 Card와 Expansion Panel과 일치하도록 변경하세요:

    -<Paper />
    +<Paper elevation={2} />
    

    이는 ExpansionPanel에도 영향을 줍니다.

Portal

  • [Portal] disablePortal을 사용할 때 자식은 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.

Slide

  • [Slide] 자식은 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.

Slider (슬라이더)

  • [Slider] @material-ui/lab에서 @material-ui/core로 이동했어요.

    -import Slider from '@material-ui/lab/Slider'
    +import Slider from '@material-ui/core/Slider'
    

Switch (스위치)

  • [Switch] 스타일을 오버라이드하기 더 쉽도록 구현을 리팩터링했어요. 클래스 이름을 스펙 용어와 일치하도록 바꾸세요:

    -icon
    -bar
    +thumb
    +track
    

Snackbar

  • [Snackbar] 새 스펙과 일치하도록 변경했어요.
    • 치수(dimensions)를 변경
    • 기본 전환(transition)을 Slide에서 Grow로 변경

SvgIcon

  • [SvgIcon] nativeColor -> htmlColor로 이름을 바꾸세요. React는 for HTML 속성과 같은 문제를 해결하면서 prop을 htmlFor라고 부르기로 결정했어요. 이 변경은 같은 논리를 따릅니다.

    -<AddIcon nativeColor="#fff" />
    +<AddIcon htmlColor="#fff" />
    

Tabs (탭)

  • [Tab] 단순화를 위해 labelContainer, label, labelWrapped 클래스 키를 제거하세요. 이를 통해 중간 DOM 요소 2개를 제거할 수 있었어요. 커스텀 스타일을 root 클래스 키로 옮길 수 있을 거예요.

    A simpler tab item DOM structure

  • [Tabs] 더 이상 사용되지 않는 fullWidth와 scrollable props를 제거하세요:

    -<Tabs fullWidth scrollable />
    +<Tabs variant="scrollable" />
    

Table (테이블)

  • [TableCell] 더 이상 사용되지 않는 numeric 속성을 제거하세요:

    -<TableCell numeric>{row.calories}</TableCell>
    +<TableCell align="right">{row.calories}</TableCell>
    
  • [TableRow] 고정 높이 CSS 속성을 제거하세요. 이제 셀 높이는 브라우저가 padding과 line-height를 사용해 계산해요.

  • [TableCell] dense 모드를 다른 속성으로 이동하세요:

    -<TableCell padding="dense" />
    +<TableCell size="small" />
    
  • [TablePagination] 컴포넌트는 더 이상 유효하지 않은 (page, count, rowsPerPage) 속성 조합을 수정하려고 시도하지 않아요. 대신 경고를 발생시킵니다.

TextField (텍스트 필드)

  • [InputLabel] InputLabel 컴포넌트의 CSS API를 사용해 FormLabel 컴포넌트의 모든 스타일을 오버라이드할 수 있어야 해요. FormLabelClasses 속성이 제거되었어요.

     <InputLabel
    -  FormLabelClasses={{ asterisk: 'bar' }}
    +  classes={{ asterisk: 'bar' }}
     >
       Foo
     </InputLabel>
    
  • [InputBase] 기본 box sizing 모델을 변경하세요. 이제 다음 CSS를 사용해요:

    box-sizing: border-box;
    

    이는 fullWidth prop과 관련된 문제를 해결합니다.

  • [InputBase] InputBase에서 inputType 클래스를 제거하세요.

Tooltip (툴팁)

  • [Tooltip] 자식은 ref를 보유할 수 있어야 해요. 컴포지션 가이드가 마이그레이션 전략을 설명합니다.
  • [Tooltip] 모든 focus 대신 focus-visible focus 후에만 나타나요.

Typography (타이포그래피)

  • [Typography] 더 이상 사용되지 않는 typography 변형을 제거하세요. 다음 교체를 수행해 업그레이드할 수 있어요:

    • display4 => h1
    • display3 => h2
    • display2 => h3
    • display1 => h4
    • headline => h5
    • title => h6
    • subheading => subtitle1
    • body2 => body1
    • body1 (default) => body2 (default)
  • [Typography] 독단적인 display: block 기본 typography 스타일을 제거하세요. 새 display?: 'initial' | 'inline' | 'block'; 속성을 사용할 수 있어요.

  • [Typography] headlineMapping 속성을 variantMapping으로 이름을 바꿔 목적에 더 잘 맞게 하세요.

    -<Typography headlineMapping={headlineMapping}>
    +<Typography variantMapping={variantMapping}>
    
  • [Typography] 기본 변형을 body2에서 body1로 변경하세요. 14px보다 16px 폰트 크기가 더 나은 기본값이에요. Bootstrap, material.io, 그리고 심지어 문서도 기본 폰트 크기로 16px을 사용해요. Ant Design이 사용하는 14px은 이해할 만한데, 중국 사용자는 다른 알파벳을 사용하기 때문이에요. 일본어의 기본 폰트 크기로는 12px이 권장됩니다.

  • [Typography] typography 변형에서 기본 색상을 제거하세요. 색상은 대부분의 경우 상속되어야 해요. 그것이 웹의 기본 동작이에요.

  • [Typography] 이 스레드의 논리에 따라 color="default"를 color="initial"로 이름을 바꾸세요. default 사용은 피해야 해요. 시맨틱이 부족하거든요.

Node

UMD

  • 이 변경으로 CDN에서 Material UI를 더 쉽게 사용할 수 있어요:

     const {
       Button,
       TextField,
    -} = window['material-ui'];
    +} = MaterialUI;
    

    이는 다른 React 프로젝트와 일관됩니다:

    • material-ui => MaterialUI
    • react-dom => ReactDOM
    • prop-types => PropTypes

더 알아보기 (Learn more)