원문 보기 :https://www.joshwcomeau.com/css/styled-components/
CSS Variables
function Backdrop({ opacity, color, children }) {
return (
<Wrapper>
{children}
</Wrapper>
)
}
const Wrapper = styled.div`
/* ?? */
`;
interpolation function을 사용
function Backdrop({ opacity, color, children }) {
return (
<Wrapper opacity={opacity} color={color}>
{children}
</Wrapper>
)
}
const Wrapper = styled.div`
opacity: ${p => p.opacity};
background-color: ${p => p.color};
`;
CSS 변수를 사용
잘 이해되지 않는다면 CSS Variables in React tutorial 를 참조하세요.
혹은 : https://itchallenger.tistory.com/592
function Backdrop({ opacity, color, children }) {
return (
<Wrapper
style={{
'--color': color,
'--opacity': opacity,
}}
>
{children}
</Wrapper>
)
}
// 기본값 지정
const Wrapper = styled.div`
opacity: var(--opacity, 0.75);
background-color: var(--color, var(--color-gray-900));
`;
단일 진실 원천(SSOT)
이 게시물에서 하나만 얻어갈 수 있다면, 해당 지식을 얻어가세요
여기저기서 쓸 수 있는 공통 TextLink 컴포넌트가 있습니다.
const TextLink = styled.a`
color: var(--color-primary);
font-weight: var(--font-weight-medium);
`;
Aside 컴포넌트 내부에 TextLink 컴포넌트가 존재하고, 스타일링 한다 가정해 보겠습니다.
우리는 Aside 컴포넌트 내부에서는 TextLink 컴포넌트가 다른 색상을 보여주길 원합니다.
이것을 contextual style이라 할 수 있습니다.
같은 컴포넌트가 문맥에 따라 스타일이 변합니다.
보통 아래와 같이 코딩합니다.
a 때문에 찾기가 좀 더 어렵습니다.
// Aside.js
const Aside = ({ children }) => {
return (
<Wrapper>
{children}
</Wrapper>
);
}
const Wrapper = styled.aside`
/* Base styles */
a {
color: var(--color-text);
font-weight: var(--font-weight-bold);
}
`;
export default Aside;
styled-components를 사용하면 컴포넌트를 다른 컴포넌트에 "임베딩"할 수 있습니다.
컴포넌트가 렌더링되면 TextLink 스타일드 컴포넌트와 일치하는 클래스의 적절한 셀렉터가 표시됩니다.
// Aside.js
import TextLink from '../TextLink'
const Aside = ({ children }) => { /* Omitted for brevity */ }
const Wrapper = styled.aside`
/* Base styles */
${TextLink} {
color: var(--color-text);
font-weight: var(--font-weight-bold);
}
`;
export default Aside;
당연한 예시인데 무엇이 문제일까요?
문맥이 없으면 TextLink의 스타일은 동일합니다만,
다른 색상이 있는 TextLink를 발견하면 해당 문맥을 찾으러 다녀야 합니다.
특히 aside라는 컴포넌트의 존재 자체도 모른다면, 문제는 어려워집니다.
대신 이렇게 하면 어떨까요?
캡슐화에 대해 이야기해 봅시다.
내가 React를 사랑하게 된 이유는 로직(상태, 효과) 및 UI(JSX)를 재사용 가능한 상자에 포장할 수 있는 방법을 제공한다는 것입니다.
많은 사람들이 "재사용 가능한" 측면에 중점을 두지만 제 생각에는 더 멋진 것은 상자라는 것입니다.
TextLink 솔루션을 다시 한 번 살펴보십시오. Aside는 TextLink의 스타일에 손을 대고 간섭합니다!
어떤 컴포넌트가 다른 컴포넌트의 스타일을 덮어쓸 수 있다면 캡슐화를 위반합니다.
주 : 캡슐화는 자기 정보에 대해선 자기 자신이 모든 책임을 지는것(crud)
TextLink 관련 스타일이 전부 TextLink에만 정의되어 있다면 얼마나 좋을까요?
// Aside.js
const Aside = ({ children }) => { /* Omitted for brevity */ }
// Export this wrapper
export const Wrapper = styled.aside`
/* styles */
`;
export default Aside;
// TextLink.js
import { Wrapper as AsideWrapper } from '../Aside'
const TextLink = styled.a`
color: var(--color-primary);
font-weight: var(--font-weight-medium);
${AsideWrapper} & {
color: var(--color-text);
font-weight: var(--font-weight-bold);
}
`;
&은 생성된 클래스 명의 플레이스 홀더입니다.
styled-components가 이 컴포넌트에 대해 TextLink-abc123 클래스를 만들 때
& 문자도 해당 선택자(.TextLink-abc123)로 바꿉니다.
.TextLink-abc123 {
color: var(--color-primary);
font-weight: var(--font-weight-medium);
}
.Aside-Wrapper-def789 .TextLink-abc123 {
color: var(--color-text);
font-weight: var(--font-weight-bold);
}
올바른 상황에 올바른 도구 쓰기 : 일반화와 특수화
할로윈 시즌에만 쓰는 텍스트 링크가 필요합니다. props와 css var를 이용해 variant를 추가할까요?
할로윈 텍스트 링크는 텍스트 링크처럼 일반적인 컴포넌트가 아닙니다.
이 떄는 Composition API가 있습니다.
// HalloweenPage.js
import TextLink from '../TextLink';
const HalloweenTextLink = styled(TextLink)`
font-family: 'Spooky Font', cursive;
`;
우리는 Aside안에서 TextLink를 사용하는데, AsideTextWrapper, AsideTextLink는 어떤지요?
> 별로입니다.
- Aside 컴포넌트는 모든 곳에서 사용합니다. 즉 애플리케이션의 핵심적인 부분이기에, TextLink로 작업할 때마다 Aside에 대해 알고 있으면 좋습니다.
- Aside가 필요할 때마다 AsideTextLink를 사용하는 것을 기억하도록 하고 싶지 않습니다.
<Aside>
개발자가 이 특수한 컴포넌트가 있다는 것을
<AsideTextLink href="">
알아야만 할까요?
</AsideTextLink>?
</Aside>
위 설명은 간단한 내용이 아니며, 완벽한 솔루션은 없습니다.
그러나 "핵심 변형 컴포넌트"과 "일회성 컴포넌트"을 명확하게 구분하여
가장 중요한 항목의 우선 순위를 지정하고 관리 가능한 상태로 유지합니다
(TextLink에 30개의 일회성 변형을 정의하는 것이 정말 도움이 될까요?).
Escape Hatch
위의 제어의 역전 패턴을 사용하려면, 항상 자식 Styled Component를 만들어야 하는데, 이는 지루할 수 있습니다.// Whatever.js
const Whatever = () => {
return (
<Wrapper>
This is an <em>OK</em> shortcut.
</Wrapper>
);
}
const Wrapper = styled.div`
& > em {
color: #F00;
}
`;
상속한 속성들
function App() {
return (
<div style={{ color: 'red' }}>
<Kid />
</div>
)
}
function Kid() {
return <p>Hello world</p>
}
- 상속한 속성은 해당 컴포넌트에서 무효화 할 수 있습니다. 강력한 권한은 여전히 컴포넌트에 있습니다.
- 몇 가지 CSS 속성만 상속할 수 있으며 거의 전적으로 타이포그래피와 관련이 있습니다.
- 패딩이나 테두리와 같은 레이아웃 속성은 상속되지 않습니다.
- 일반적으로 타이포그래피 스타일은 상속해야 합니다.
- 단락의 모든 <strong> 또는 <em>에 현재 글꼴 색상을 다시 적용해야 하는 것은 정말 성가신 일입니다.
- devtools에서 무슨 일이 일어나고 있는지는 명확합니다. 우리는 스타일이 어디에서 왔는지 이해하기 위해 애쓰지 않을 것입니다.
독립적인 CSS
Aside 컴포넌트 주변에 다른 컴포넌트가 달라붙지 않게 하고 싶습니다. 마진을 사용할까요?
// Aside.js
const Aside = ({ children }) => {
return (
<Wrapper>
{children}
</Wrapper>
);
}
const Wrapper = styled.aside`
margin-top: 32px;
margin-bottom: 48px;
`;
export default Aside;
일반적으로 마진은 재사용 가능한 컴포넌트에 도움이 안됩니다.
누군가는 다음과 같이 말했습니다.
마진을 안쓰는 방법들이 논의되고 있습니다.
- CSS 그리드 내부에 있는 경우 grid-gap을 사용하여 각 요소의 간격을 지정할 수 있습니다.
- Flex 컨테이너 내부에 있는 경우 새로운 gap 속성이 경이롭게 작동합니다.
- 논란의 여지가 있지만 놀랍도록 즐거운 옵션인 Spacer 구성 요소를 사용할 수 있습니다. (use a Spacer component)
- Stack과 같은 전용 레이아웃 구성 요소를 사용할 수 있습니다.
쌓임 맥락
// Flourish.js
const Flourish = styled.div`
position: relative;
z-index: 2;
/* Omitted decorative properties */
`;
export default Flourish;
문제가 보이시나요? 이전과 마찬가지로 컴포넌트에 z-index를 미리 지정했습니다.
앞으로의 모든 사용 사례에서 2가 올바른 계층이 되기를 바랍니다!
더 위험한 버전을 봅시다. Wapper는 z-index가 없으니 괜찮겠죠?
// Flourish.js
const Flourish = ({ children }) => {
return (
<Wrapper>
<DecorativeBit />
<DecorativeBackground />
</Wrapper>
);
}
const Wrapper = styled.div`
position: relative;
`;
const DecorativeBit = styled.div`
position: absolute;
z-index: 3;
`;
const DecorativeBackground = styled.div`
position: absolute;
z-index: 1;
`;
말콤 씨가 컴포넌트를 우리 컴포넌트 위에 뒀네요. 어떻게 될까요? > 중간에 끼입니다.
쌓임 맥락이 언제 생기는지를 고려하면, 같은 쌓임 맥락에서 z-index를 공유하기 때문입니다.
<div>
{/* Malcom will get stuck in the middle! */}
<Malcolm
style={{ position: 'relative', zIndex: 2}}
/>
<Flourish />
</div>
이 문제는 isolation 속성으로 해결할 수 있습니다.
const Flourish = ({ children }) => {
return (
<Wrapper>
<DecorativeBit />
<DecorativeBackground />
</Wrapper>
);
}
const Wrapper = styled.div`
position: relative;
isolation: isolate;
`;
// The rest unchanged
새로운 스태킹 컨텍스트에는 z-인덱스가 없으므로 순전히 DOM 순서에 의존할 수 있습니다.
(필요할 때 사용 가능)
다양한 팁과 기술들
as props
동적으로 h1 레벨을 정하고 싶을 떄
// `level` is a number from 1 to 6, mapping to h1-h6
function Heading({ level, children }) {
const tag = `h${level}`;
return (
<Wrapper as={tag}>
{children}
</Wrapper>
);
}
// The `h2` down here doesn't really matter,
// since it'll always get overwritten!
const Wrapper = styled.h2`
/* Stuff */
`;
버튼을 a 링크로 사용하고 싶을 떄
function LinkButton({ href, children, ...delegated }) {
const tag = typeof href === 'string'
? 'a'
: 'button';
return (
<Wrapper as={tag} href={href} {...delegated}>
{children}
</Wrapper>
);
}
선택 우선순위 높이기
다른 CSS 파일과 같이 작업할 때, 내 스타일의 우선순위를 높이는 방법이지만,
important와 같이 기존 규칙을 파괴하므로 조심해야 합니다.
const Wrapper = styled.div`
p {
color: blue;
}
`
const Paragraph = styled.p`
color: red;
&& {
color: green;
}
`;
// Somewhere:
<Wrapper>
<Paragraph>I'm green!</Paragraph>
</Wrapper>
바벨 플러그인
import styled from 'styled-components/macro';
다른 경우 : follow the official documentation
멘탈 모델
- CSS 선언을 제거하는 것이 안전한지 자신 있게 알 수 있습니다.
- 완전히 독립적인 컴포넌트이기 때문입니다.
- 특정성(specificity)을 높이기 위한 트릭이 필요없습니다.
- 수동 테스트를 많이 할 필요 없이 페이지가 어떻게 생겼는지 정확히 이해할 수 있습니다.
'FrontEnd' 카테고리의 다른 글
CSS 애니메이션 : Transition (0) | 2022.06.25 |
---|---|
CSS 애니메이션 : Transforms (0) | 2022.06.25 |
The Difference Between VAC(View Asset Component) Pattern and Container/Presenter Pattern (0) | 2022.06.25 |
리액트 개발자를 위한 CSS Variable (0) | 2022.06.24 |
React Portal과 타입스크립트로 모달 만들기 (0) | 2022.06.22 |