-
Notifications
You must be signed in to change notification settings - Fork 0
DOCS. Woowahan Components
styled-components
의 카피 버전입니다. 1주차때 부터 마지막 프로젝트에서 직접 만들어서 해야겠다 생각하고 조금씩 만들어 오고 있다가 이번 프로젝트에 와서야 완성이 된 녀석입니다.
CSS-in-JS가 적용된 컴포넌트를 만들어주는 기능입니다.
import woowahan from 'lib/woowahan-components';
const Styled = woowahan.div`
color: ${props => props.theme?.color};
`;
이 기능을 구현하기 위해서 Tagged Templates
기능을 사용했는데요, JS에서 제공하는 Template Literals
를 함수로 파싱할 수 있게 해주는 기능입니다.
기본적인 사용 방법은 아래와 같습니다.
const parseString = (strings: string[], ...exps: (((props: any) => string) | string | number)[]): string => {
return strings
.map((string, i) => {
// exps의 타입에 따라 처리를 합니다.
// 아래에는 함수라고 치고 이야기 하겠습니다.
return `${string}${exps[i]()}`;
})
.join('');
};
템플릿 리터럴에 있는 ${}
를 만나게 되면 그 이전까지의 문자열을 하나의 조각으로 나누고, ${}
내부의 표현식(함수, 변수 등)을 하나의 조각으로 생각합니다. 이런 조각들이 순서대로 있는데, 결국 ${}
내부의 조각들을 처리해주고 ${}
가 아닌 나머지 문자열들과 순서대로 조합을 해서 새로운 문자열을 꺼내는 방식입니다.
그리고 woowahan.div
와 같이 사용하기 위해서 HTML에 있는 거의 모든 태그를 다 불러와서 넣어줬는데요, 엄청 많았습니다.(물론 대부분 사용하지는 않겠지만요)
const tags = [
'a',
'abbr',
'address',
// ...
'var',
'video',
'wbr',
];
woowahan.div
를 불러오면, 먼저 템플릿 리터럴을 사용해서 스타일 문자열을 만들고 스타일을 적용해줍니다.
여기서 스타일을 적용하기 위한 방법이 두가지가 있었습니다.
- 만들어서 반환해줄 리액트 엘리면트의
style
속성에 다 때려넣는 방법 - HTML의
CSSStyleSheet
를 사용하는 방법
첫번째는 개발자도구에서 확인할 때 스타일 때문에 요소의 길이가 너무 길어지는 것 같아 두번째 방법을 선택했습니다. 두번째 방법은 스타일 시트가 있어야 했는데요, css
파일이나 html
에 <style>
태그를 하나 넣어주는 방법이 있었습니다.
파일을 하나 만드는 것 보다 HTML 파일에 태그를 하나 추가해주는게 더 사용하기 편할 것 같아서 그렇게 적용을 했습니다.
const parsedString = parseString(styleString, exps, { theme, ...props });
let className = '';
const isAlreadyInserted = classMap.has(parsedString);
if (isAlreadyInserted) {
className = classMap.get(parsedString) as string;
} else {
className = `wc-${generateId()}`;
classMap.set(parsedString, className);
}
const preprocessedStyle = serialize(compile(`.${className} {${parsedString}}`), stringify);
const styleList = splitStyleString(preprocessedStyle);
const styleSheet = document.styleSheets[0];
if (!isAlreadyInserted) {
styleList.forEach(style => {
styleSheet.insertRule(style, styleSheet.cssRules.length);
});
}
코드입니다.
여기에서 두가지 라이브러리를 사용해줬습니다. nanoid
와 stylis
입니다.
-
nanoid
는 만들어지는 컴포넌트의 클래스 명을 설정해주기 위해서 사용했는데요,styled-components
로 컴포넌트를 만들어내면,sc-i2Y92q
처럼 클래스가 적용되는데 이렇게 무작위로 생성되는 클래스 이름을 넣기 위해서 사용했습니다. -
stylis
는 nested된 css 문자열을 파싱해주기 위해서 사용했습니다. 역할자체는sass
나stylus
와 같은데, 용량이 가장 적어서 채택했습니다.
nanoid
로 만든 클래스 이름과 stylis
가 파싱한 CSS 문자열을 하나로 합쳐서 스타일시트에 넣어줍니다. 이때 스타일 시트의 문자열을 분리해서 넣어줘야 하는데, CSSStyleSheet
는 스타일을 배열처럼 가지고 있습니다.
CSSStyleSheet {
cssRules: CSSRuleList[
0: CSSStyleRule {selectorText: string, style: CSSStyleDeclaration, …}
1: CSSStyleRule {selectorText: string, style: CSSStyleDeclaration, …}
2: CSSStyleRule {selectorText: string, style: CSSStyleDeclaration, …}
]
}
위는 축소시킨 예시입니다. selectorText
와 style
을 가지고 있죠? 그래서 스타일을 하나씩 분리해서 넣어줘야 합니다.
CSSStyleSheet.insertRule
이라는 함수는 선택자가 있어야 합니다. 무슨 말이냐 하면, 아래 코드를 보면 알 수 있습니다.
// Error😵
styleSheet.insertRule('font-size: 10px', styleSheet.cssRules.length);
// Success😊
styleSheet.insertTule('div { font-size: 10px }', styleSheet.cssRules.length);
// styleSheet.cssRules.length는 적용되는 스타일의 순서를 맞추기 위해 넣어줬습니다.
이렇게하면 CSSStyleSheet
가 원하는대로 한번에 하나의 스타일을 넣을 수 있겠죠?
이렇게 스타일까지 적용을 해줬으면, 만들어진 클래스 이름과 div
태그 문자열, React.createElement
메소드를 사용해서 div
인 리액트 엘리먼트를 만들어내고 반환을 해줍니다.
사용자체는 styled-componets
의 createGlobalStyle
과 동일합니다.
구현 방법도 위와 크게 다르지 않습니다. 다만 클래스 이름 없이 바로 전역에 스타일을 적용해준다는 점이 다르죠.
테마 기능입니다.
테마는 Context API
를 사용했습니다. 그래서 ThemeProvider
를 woowahan-componets
를 사용하는 최상단에 먼저 넣어줘야 합니다.
그리고 useTheme
이라는 내부의 간단한 훅을 통해서 Tagged Template
으로 받아온 함수에 넣어주는 방법으로 실행됩니다. useTheme
은 개발자가 직접 사용할 일은 없습니다.
-
Tagged Template
을 사용해서 스타일 문자열을 만들어 낸다. -
CSSStyleSheet.insertRule()
을 사용해서 스타일 문자열을 적용해준다. (한번에 하나의 스타일만 넣어준다) - 똑같은 클래스와 태그를 가진 리액트 컴포넌트를 반환한다.
-
createGlobalStyle
은 하나의 엘리먼트가 아니라 그냥 스타일시트에만 스타일을 적용한다. -
Theme
은Context API
를 사용해줘서props.theme
으로 사용할 수 있다.
컴포넌트가 만들어지고 반환할때 타입이 React.ReactElement
로 반환하지만, React의 기본적인 props(onClick, key 등..)가 자동완성이 되지 않습니다.(Typescript임에도..!) Typescript를 사용할때 제네릭으로 넘겨주게 만들어야 싶다가도 실제 React에서는 그렇지 않은데 왜 나오지 않을까? 라는 의문이 들었습니다. 하지만 이 고민만 계속하면 프로젝트가 진행이 되지 않기 때문에 왜 그렇게 되는지는 잘 모르겠습니다. JSX로 props를 넘겨준것이나 React.createElement로 만든것이나 같은데도 아직은 잘 모르겠습니다..