TIL 2018-09-19
오늘 배운 것
memorybook
styled-component v4 사용
테마 관련 타이핑(typescript 형식) 작업.
내가 원하는 것은 기본 테마 컬러들이 테마 모드에 따라 바뀌고, 그에 따라 다시 전체 색상이 자동 계산되는 것인데, 이것을 따로 lightTheme, darkTheme으로 나누면 색상이 하나 늘 때마다 lightTheme, darkTheme 및 타이핑까지 작업해 줘야 한다. 그래서 생각한 것이, theme: IThemeInterface
를 인수로 받아 그 인수에 있는 기본 색상 등을 이용해서 계산을 한 뒤에, 다시 계산 완료된 calculatedtheme: IThemeInterface
을 반환하는 함수 getDynamicThemeProperties
를 작성했다. 멋지긴 하지만 몇 가지 문제가 있다.
- 몇 군데에 lightTheme, currentTheme, darkTheme의 계산된 프로퍼티여야 하는 속성을 직접 접근한다. 이럴 경우 그런 모든 지점에
getDynamicThemeProperties
로 계산을 한 후에 접근하게 해야 한다. - 의미적으로 일치하지 않는다는 점.
- 나는 좀 더 멋지게 설계하고 싶기 때문에, 이 부분을 무시할 수 없다. 지금 구조라면
theme: IThemeInterface
가base
로서의 역할도 하고, 계산된 테마로서의 역할도 하는데, 그 두가지 역할은 분명히 다르다.
따라서 IBaseTheme
을 만든 뒤, ITheme
을 이를 이용해서 계산하는 방식으로 해야겠다. 사실 위의 1번 문제는 이렇게 바꾸어도 결국 계산을해야 하는 부분이니 괜찮다. 그렇다면 lightTheme
이나 darkTheme
의 경우 정적 오브젝트로 작성할 게 아니라, 다음과 같은 함수를 작성하여 redux store, themeSelect 컴포넌트 등에 사용해야겠다.
func getTheme(mode:string, baseTheme:IBaseTheme)=>(calculatedTheme:ITheme)
baseTheme
이 굳이 필요하지 않다. 생각해보면 난 theme 이름을 이용해서 계산을 하되, 그것으로부터 순차적으로 계산해 나가면 되고, color같은 경우 colors 팔레트를 쓰면 되니 baseTheme
보다는 그냥 mode(themeName)
만 있으면 된다. 문제는 그러면 redux store의 state가 (current theme's)name
과 (current theme's)theme
으로 나뉘어야 할 것이다. 또 shared theme attributes, shared theme effects 및 그 타이핑 때문에 골치 아팠으나 생각해 보면 이걸 다 따로 할 게 아니라, 합친 함수의 리턴만 검사하면 되니 shared에 대해서는 굳이 타이핑이 필요하지 않을 것이다.
근데 생각해보면 색상이 하나 늘 때마다 타이핑 등을 작업해 줘야 하는 건 마찬가지일까.. 아니 어쩌면 오브젝트로 작성하고 타입을 얻어오면 타이핑을 안 써도 될수도 있다! 아니다. 타이핑은 컴파일 타임이기도 하고, 어쨌든 오브젝트로부터 타입을 만들거나 할 수는 없다.
이렇게 바꾸려고 할 때 문제는, 나는 theme
유틸 함수를 이용하여 styled-components
에서 편하게 테마 속성에 접근하려 하는데, themeName
의 경우, type themeNames = 'light' | 'dark'
이렇게 더 자세한 제약이 걸려있는데, themeAttributeKey
에 themeNames
키를 추가하게 되면 type themeAttributes = Record<themeAttributeKey, string>;
이렇게 계산되어 정확한 형식이 아니게 된다. themeName
키에 대해서만 다른 형식을 넣는다고 해도 이번엔 theme
함수의 매개변수인 themeAttributeKey
에 themeName
가 없게 되어 부정확하다. 둘 다 따로 넣자니.. 귀찮다. 어쩌지?
아무래도 key
로부터 Record<T,K>
를 이용해 타입을 만드는 것 보다, 정확한 인터페이스 또는 타입으로부터 Keyify
? 등을 이용해 필요한 부분만 잘라내는 것이 훨씬 더 쉬울 것 같다.
현재는 themeAttributeKey
, themeEffectKey
로부터 themeEffects
, themeAttributes
를 만들고 다시 export interface ITheme extends themeEffects, themeAttributes {}
이렇게 하고 있는데, 무조건 큰것부터 나누는 것도, 무조건 작은 것부터 쌓아 올리는것도 좋지 않을 수 있다. 지금 보니 어차피 ITheme
은 이펙트와 속성의 합이므로, 이펙트와 속성 각각에 대해서만 상세한 형식을 기술하면 될 것이다.
다음 라이브러리는 타입스크립트 형식을 만드는 데 도움을 준다고 한다. https://github.com/Microsoft/dts-gen
spread operator?에 대해 의문이 있는데, 아래 코드처럼 그냥 따로 할당할 때는 const themeName
이 type themeName
으로 추론이 되지만, spread operator를 쓸 경우 type이 widen되어 정확한 형식을 잃어버린다. 하지만 그냥 할당으로는 되는 게 spread operator를 쓰면 안 되는 건 어색하다.
type themeName = 'dark' | 'light';
interface ITheme {
themeName: themeName,
fontColor: string,
}
function getTheme(name: themeName): ITheme{
const ret = {
themeName: 'dark',
fontColor: 'black',
}
return {
...ret
};
}
function getTheme2(name: themeName): ITheme{
const themeName = 'dark';
const ret = {
fontColor: 'black',
}
return {
...ret,
themeName,
};
}
function aFunction<T extends object>(base: T) {
const anObject = { test:"value" }
if (typeof base !== 'object') { return }
// the following line causes a TSC error, saying that spread types can only be
// created from object types and highlighting base as the problem... wut?
const merged = { ...base, anObject }
return merged
}
function aFunction2<T extends object>(base: T) {
const anObject = { test:"value" }
if (typeof base !== 'object') { return }
// the following line causes a TSC error, saying that spread types can only be
// created from object types and highlighting base as the problem... wut?
const merged = { ...base, anObject }
return merged
}
Comments