https://velog.io/@sharlotte_04/MUI-Theming-경험
에서 팔래트 색 이름을 속성값에 넣기만 해도 자동으로 색이 부여되는 것에 대해 언급했다. text, background 등 palette의 기본 속성들은 이미 팔레트 타입에 명시되어 있기 때문에 값을 집어넣기만 하면 된다.
createTheme 파라미터에선 이 모두가 optional로 되어서 모두 다 직접 입력할 필요는 없다. 생략한 값들은 MUI 공식 문서에 명시된 기본 테마를 따른다.
https://mui.com/material-ui/customization/default-theme/
이번에 겪고 해결한 과제다. 위 Palette
를 보다시피 Record<string, PaletteColor>
따위의 확장성을 지닌 타입이 없다. 즉, 일반적으로 임의의 색 속성을 생성하는건 자바스크립트에선 가능하나 타입스크립트에선 타입 오류(해당 속성이 Palette
에 없음)로 인해 불가능하다.
이 문제를 해결하기 위해 서드파티 타입해결 단골키워드인 declare
를 사용한다.
https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules
타입스크립트 핸드북에 따르면 declare
는 타입 뿐만이 아니라 module
키워드를 통해 해당 모듈이 export
할 타입조차도 declare
할 수 있다고 한다.
declare module '@mui/material/styles' {
interface Palette {
"색이름": Palette['primary']
}
interface PaletteOptions {
"색이름"?: PaletteOptions['primary']
}
}
이건 MUI 공식 문서의 adding new colors 문단에서 인용한 코드 예시다.
https://mui.com/material-ui/customization/palette/#adding-new-colors
직접 입력해보면 잘 작동하는걸 볼 수 있으나 여러가지 이유로 이 예제는 좋지 않다.
1. 타입 자체를 가져오는게 아니라 primary
속성을 거쳐 타입을 얻는다.
=> primary
에 의존적이다. 별 상관은 없으나 의존성을 내려 PaletteColor
와 PaletteColorOptions
를 사용한다.
declare module '@mui/material/styles' {
interface Palette extends CustomPalette {
color1: PaletteColor
}
interface PaletteOptions extends CustomPaletteOptions {
color1: PaletteColorOptions
}
}
declare module '@mui/material/styles' {
interface Palette extends CustomPalette {
color1: PaletteColor,
color2: PaletteColor,
color3: PaletteColor,
color4: PaletteColor,
color5: PaletteColor
}
interface PaletteOptions extends CustomPaletteOptions {
color1: PaletteColorOptions
color2: PaletteColorOptions,
color3: PaletteColorOptions,
color4: PaletteColorOptions,
color5: PaletteColorOptions
}
}
이처럼 색 수를 늘릴때마다 코드 덩치가 배로 늘어난다. 즉, 반복적인 코드다. 때문에 타입스크립트의 mapped type를 사용할 생각이다.
Mapped type는
type MappedType<T, U> = {
[Property in T]: U
}
와 같이 [문자열_타입 in 문자열_유니언_타입]: 대상_타입
의 문법을 따르며 문자열_유니언_타입
안 각 문자열 타입 이름의 속성이 대상_타입
타입을 가지도록 자동 생성한다. 이때 Property는 for(let elem of obj)의 elem의 타입 버전이라 여기면 된다. 즉 아무 이름으로 지어도 괜찮다. 사실 MapeedType는 이미 Record란 이름으로 내장되어있다.
https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
타입스크립트 핸드북을 보면 더 많은 예시와 설명을 알 수 있다.
type CustomPaletteColors = 'default' | 'nature';
type CustomPalette = {
[_ in CustomPaletteColors]: PaletteColor;
}
type CustomPaletteOptions = {
[_ in keyof CustomPalette]?: PaletteColorOptions;
}
default
와 nature
는 내가 새로 쓸 커스텀 색의 이름이다. 새로운 색을 만들때마다 유니언 타입에 추가하기만 하면 되도록 설계한다. 위 예시에선 Property
라 인수 속성의 이름을 지었지만 지금은 그걸 갖다쓸 일이 없으니 언더바로 대체한다.
declare module '@mui/material/styles' {
interface Palette extends CustomPalette { }
interface PaletteOptions extends CustomPaletteOptions { }
}
이제 mapped type들을 declare할 타입에 합쳐야 하는데, Palette
와 PaletteOptions
는 인터페이스이므로 그에 맞게 extends
키워드를 사용해 mapped type들을 확장시키도록 조작한다.
vsc 힌트에서도 잘 나오는걸 볼 수 있다.
가장 위 사진을 보면 모든 팔레트가 PaletteColor
로 이루어진게 아니다. 다양한 속성과 타입이 있는데 PaletteColor
만 확장시키는건 한계가 있다고 생각해서 팔레트 색을 떠나 팔레트 속성까지 확장시키고자 한다.
type Partialize<T> = {
[P in keyof T]?: T[P] extends Function
? T[P]
: T[P] extends PaletteColor
? PaletteColorOptions
: Partial<T[P]>
}
type CustomPalettes = Record<'accent' | 'health', PaletteColor>
type CustomSimeples = Record<'default' | 'nature', Color>
declare module '@mui/material/styles' {
interface Palette extends CustomPalettes, CustomSimeples { }
interface PaletteOptions extends Partialize<CustomPalettes>, Partialize<CustomSimeples> { }
}
Partialize에서 타입이 Function인 속성은 함수의 속성들을 모두 optional하는게 불필요하므로 넘기고 PaletteColor는 나머지들이 Partial를 가지는 것과 달리 PaletteColorOptions를 가지므로 그것에 대해 처리하고 나머지들에겐 Partial를 넣어 optional하게 만들었다.
inferface delcaring 부분에선 커스텀 속성이 늘어날수록 길이가 2배로 늘어나는 문제가 있었는데 인터페이스의 다중 상속은 인터섹션 타입을 상속하는 것과 같다는 것을 깨닫고 타입을 하나로 정리했다.
type CustomPalette =
Record<'default' | 'nature', PaletteColor> &
Record<'accent' | 'health', Color>
declare module '@mui/material/styles' {
interface Palette extends CustomPalette { }
interface PaletteOptions extends Partialize<CustomPalette> { }
}
어느정도 괜찮아졌다. 뭔가를 더 추가할 때 한 부분만 수정하면 되니 더이상 건들 필요는 없을 것 같다.
새로 추가한 속성 역시 vsc 힌트에 잘 뜨는걸 볼 수 있다.