React에서 Styled-Components를 사용하는 방법
게시 됨: 2022-03-10스타일 구성 요소는 구성 요소와 스타일 지정 간의 격차를 해소하는 CSS-in-JS 도구로, 기능적이고 재사용 가능한 방식으로 구성 요소를 스타일링할 수 있도록 다양한 기능을 제공합니다. 이 기사에서는 스타일이 지정된 구성 요소의 기본 사항과 이를 React 애플리케이션에 적절하게 적용하는 방법을 배웁니다. 이 튜토리얼을 진행하기 전에 이전에 React에 대해 작업했어야 합니다. React 컴포넌트 스타일링에서 다양한 옵션을 찾고 있다면 해당 주제에 대한 이전 게시물을 확인할 수 있습니다.
CSS의 핵심은 DOM 트리에서 위치에 관계없이 모든 HTML 요소를 전역적으로 대상으로 지정할 수 있는 기능입니다. 이는 구성 요소와 함께 사용할 때 방해가 될 수 있습니다. 구성 요소는 합리적인 범위에서 사용되는 위치(로컬라이제이션이라고 함)에 더 가까운 코로케이션(예: 상태 및 스타일과 같은 자산 유지)을 요구하기 때문입니다.
React 자신의 말로 스타일이 지정된 구성 요소는 "구성 요소에 대한 시각적 기본 요소 "이며 목표는 구성 요소에 스타일을 지정할 수 있는 유연한 방법을 제공하는 것입니다. 그 결과 구성 요소와 해당 스타일 간의 긴밀한 결합이 이루어집니다.
참고: Styled 구성 요소는 React와 React Native 모두에서 사용할 수 있으며 React Native 가이드를 확실히 확인해야 하지만 여기서는 React의 styled 구성 요소에 중점을 둡니다.
스타일 구성 요소가 필요한 이유
스타일 범위를 지정하는 것 외에도 스타일이 지정된 구성 요소에는 다음 기능이 포함됩니다.
- 자동 공급업체 접두사
표준 CSS 속성을 사용할 수 있으며 스타일이 지정된 구성 요소는 필요한 경우 공급업체 접두사를 추가합니다. - 고유한 클래스 이름
스타일이 지정된 구성 요소는 서로 독립적이며 라이브러리에서 자동으로 처리하므로 이름에 대해 걱정할 필요가 없습니다. - 죽은 스타일 제거
스타일이 지정된 구성 요소는 코드에 선언된 경우에도 사용하지 않는 스타일을 제거합니다. - 그리고 더 많은.
설치
스타일이 지정된 구성 요소를 설치하는 것은 쉽습니다. CDN이나 Yarn과 같은 패키지 관리자를 통해 수행할 수 있습니다.
yarn add styled-components
... 또는 npm:
npm i styled-components
우리의 데모는 create-react-app을 사용합니다.
시작하기
스타일이 지정된 구성 요소에 대해 가장 먼저 알아차릴 수 있는 것은 해당 구문으로, 스타일이 지정된 구성 요소 뒤에 숨겨진 마법을 이해하지 못한다면 벅찰 수 있습니다. 간단히 말해서 스타일이 지정된 구성 요소는 JavaScript의 템플릿 리터럴을 사용하여 구성 요소와 스타일 간의 격차를 해소합니다. 따라서 스타일이 지정된 구성 요소를 만들 때 실제로 만드는 것은 스타일이 있는 React 구성 요소입니다. 다음과 같이 보입니다.
import styled from "styled-components"; // Styled component named StyledButton const StyledButton = styled.button` background-color: black; font-size: 32px; color: white; `; function Component() { // Use it like any other component. return <StyledButton> Login </StyledButton>; }
여기서 StyledButton
은 스타일이 지정된 구성 요소이며 포함된 스타일이 있는 HTML 버튼으로 렌더링됩니다. styled
는 JavaScript의 스타일을 실제 CSS로 변환하는 내부 유틸리티 메서드입니다.
원시 HTML 및 CSS에서는 다음과 같이 됩니다.
button { background-color: black; font-size: 32px; color: white; } <button> Login </button>
스타일이 지정된 구성 요소가 React 구성 요소인 경우 props를 사용할 수 있습니까? 응 우리는 할 수있어.
소품을 기반으로 한 적응
스타일이 지정된 구성 요소는 기능적 이므로 요소의 스타일을 동적으로 쉽게 지정할 수 있습니다. 페이지에 두 가지 유형의 버튼이 있다고 가정해 보겠습니다. 하나는 검정색 배경이고 다른 하나는 파란색입니다. 두 개의 스타일 구성 요소를 만들 필요가 없습니다. 소품에 따라 스타일을 조정할 수 있습니다.
import styled from "styled-components"; const StyledButton = styled.button` min-width: 200px; border: none; font-size: 18px; padding: 7px 10px; /* The resulting background color will be based on the bg props. */ background-color: ${props => props.bg === "black" ? "black" : "blue"; `; function Profile() { return ( <div> <StyledButton bg="black">Button A</StyledButton> <StyledButton bg="blue">Button B</StyledButton> </div> ) }
StyledButton
은 props를 허용하는 React 컴포넌트이기 때문에 bg
prop의 존재 여부나 값에 따라 다른 배경색을 할당할 수 있습니다.
그러나 버튼에 type
을 지정하지 않았음을 알 수 있습니다. 그걸하자:
function Profile() { return ( <> <StyledButton bg="black" type="button"> Button A </StyledButton> <StyledButton bg="blue" type="submit" onClick={() => alert("clicked")}> Button B </StyledButton> </> ); }
스타일이 지정된 구성 요소는 받는 props 유형을 구분할 수 있습니다. 그들은 type
이 HTML 속성이라는 것을 알고 있으므로 자체 처리에서 bg
소품을 사용하는 동안 실제로 <button type="button">Button A</button>
를 렌더링합니다. 이벤트 핸들러도 연결한 방법에 주목하세요?
속성에 대해 말하자면 확장 구문을 사용하면 attrs
생성자를 사용하여 props를 관리할 수 있습니다. 이것 좀 봐:
const StyledContainer = styled.section.attrs((props) => ({ width: props.width || "100%", hasPadding: props.hasPadding || false, }))` --container-padding: 20px; width: ${(props) => props.width}; // Falls back to 100% padding: ${(props) => (props.hasPadding && "var(--container-padding)") || "none"}; `;
너비를 설정할 때 삼항이 필요하지 않다는 것을 알 수 있습니까? width: props.width || "100%",
width: props.width || "100%",
. 또한 CSS 사용자 정의 속성을 사용할 수 있기 때문에 사용했습니다!
참고: 스타일이 지정된 구성 요소가 React 구성 요소이고 props를 전달할 수 있다면 상태도 사용할 수 있습니까? 라이브러리의 GitHub 계정에는 바로 이 문제를 해결하는 문제가 있습니다.
스타일 확장
랜딩 페이지에서 작업 중이고 컨테이너를 중앙에 유지하기 위해 특정 최대 너비로 설정했다고 가정해 보겠습니다. 이에 대한 StyledContainer
가 있습니다.
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `;
그런 다음 양쪽에 20픽셀 대신 10픽셀의 패딩이 있는 더 작은 컨테이너가 필요하다는 것을 알게 됩니다. 첫 번째 생각은 스타일이 지정된 또 다른 구성 요소를 만드는 것일 수 있으며 당신이 옳을 수도 있지만 스타일을 복제하고 있다는 사실을 깨닫는 데 시간이 걸리지 않을 것입니다.
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; const StyledSmallContainer = styled.section` max-width: 1024px; padding: 0 10px; margin: 0 auto; `;
위의 스니펫과 같이 StyledSmallContainer
를 생성하기 전에 스타일을 재사용하고 상속하는 방법을 알아보겠습니다. spread
연산자가 작동하는 방식과 다소 비슷합니다.
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; // Inherit StyledContainer in StyledSmallConatiner const StyledSmallContainer = styled(StyledContainer)` padding: 0 10px; `; function Home() { return ( <StyledContainer> <h1>The secret is to be happy</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer> <h1>The road goes on and on</h1> </StyledSmallContainer> ); }
StyledSmallContainer
에서 StyledContainer
에서 모든 스타일을 가져오지만 패딩이 재정의됩니다. 일반적으로 StyledSmallContainer
에 대해 렌더링되는 섹션 요소를 얻게 된다는 점을 명심하세요. 이것이 StyledContainer
가 렌더링하는 것이기 때문입니다. 그러나 그것이 돌에 새겨져 있거나 변경할 수 없다는 것을 의미하지는 않습니다.
"as" 다형성 소품
as
polymorphic prop을 사용하면 렌더링되는 end 요소를 바꿀 수 있습니다. 한 가지 사용 사례는 스타일을 상속하는 경우입니다(마지막 예제에서와 같이). 예를 들어 StyledSmallContainer
section
보다 div
를 선호하는 경우 다음과 같이 선호하는 요소의 값을 사용하여 스타일이 지정된 구성 요소에 as
소품을 전달할 수 있습니다.
function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as="div"> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }
이제 StyledSmallContainer
는 div
로 렌더링됩니다. 사용자 정의 구성 요소를 값으로 가질 수도 있습니다.
function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as={StyledContainer}> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }
당연하게 여기지 마세요.
SCSS와 유사한 구문
CSS 전처리기 Stylis를 사용하면 스타일이 지정된 구성 요소가 중첩과 같은 SCSS와 유사한 구문을 지원할 수 있습니다.
const StyledProfileCard = styled.div` border: 1px solid black; > .username { font-size: 20px; color: black; transition: 0.2s; &:hover { color: red; } + .dob { color: grey; } } `; function ProfileCard() { return ( <StyledProfileCard> <h1 className="username">John Doe</h1> <p className="dob"> Date: <span>12th October, 2013</span> </p> <p className="gender">Male</p> </StyledProfileCard> ); }
생기
스타일이 지정된 구성 요소에는 (재사용 가능한) 애니메이션 키프레임을 구성하는 데 도움이 되는 keyframes
도우미가 있습니다. 여기서의 장점은 키프레임이 스타일이 지정된 구성 요소에서 분리되어 필요할 때마다 내보내고 재사용할 수 있다는 것입니다.
import styled, {keyframes} from "styled-components"; const slideIn = keyframes` from { opacity: 0; } to { opacity: 1; } `; const Toast = styled.div` animation: ${slideIn} 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; border-radius: 5px; padding: 20px; position: fixed; `;
글로벌 스타일링
CSS-in-JS의 원래 목표와 확장하여 스타일이 지정된 구성 요소는 스타일의 범위를 지정하는 것이지만 스타일이 지정된 구성 요소의 전역 스타일을 활용할 수도 있습니다. 우리는 대부분 범위 스타일로 작업하기 때문에 이것이 변하지 않는 공장 설정이라고 생각할 수도 있지만 그것은 틀릴 것입니다. 생각해 보십시오. 범위 지정이란 실제로 무엇입니까? 글로벌 스타일이라는 이름으로 기술적으로 다음과 유사한 작업을 수행하는 것이 가능합니다.
ReactDOM.render( <StyledApp> <App /> </StyledApp>, document.getElementById("root") );
그러나 우리는 이미 존재의 유일한 이유가 전역 스타일링인 도우미 함수인 createGlobalStyle
을 가지고 있습니다. 그렇다면 왜 책임을 부정하는가?
createGlobalStyle
을 사용할 수 있는 한 가지는 CSS를 정규화하는 것입니다.
import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ `; // Use your GlobalStyle function App() { return ( <div> <GlobalStyle /> <Routes /> </div> ); }
참고: createGlobalStyle
을 사용하여 만든 스타일은 자식을 허용하지 않습니다. 설명서에서 자세히 알아보십시오.
이 시점에서 왜 createGlobalStlye
를 전혀 사용하지 않아야 하는지 궁금할 것입니다. 다음은 몇 가지 이유입니다.
- 루트 렌더 외부의 어떤 것도 대상으로 지정할 수 없습니다(예:
html
,body
등). -
createGlobalStyle
은 스타일을 삽입하지만 실제 요소는 렌더링하지 않습니다. 마지막 예제를 자세히 보면 렌더링할 HTML 요소를 지정하지 않았음을 알 수 있습니다. 요소가 실제로 필요하지 않을 수도 있기 때문에 이것은 멋진 일입니다. 결국, 우리는 글로벌 스타일에 관심이 있습니다. 우리는 특정 요소가 아닌 전체적으로 선택자를 대상으로 합니다. -
createGlobalStyle
은 범위가 지정되지 않으며 앱의 어느 곳에서나 렌더링될 수 있으며 DOM에 있는 한 적용할 수 있습니다. 구조 가 아니라 개념 을 생각하십시오.
import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ .app-title { font-size: 40px; } `; const StyledNav = styled.nav` /* Your styles here */ `; function Nav({children}) { return ( <StyledNav> <GlobalStyle /> {children} </StyledNav> ); } function App() { return ( <div> <Nav> <h1 className="app-title">STYLED COMPONENTS</h1> </Nav> <Main /> <Footer /> </div> ); }
구조에 대해 생각한다면 app-title
은 GlobalStyle
에 설정된 대로 스타일이 지정되어서는 안 됩니다. 하지만 그런 식으로 작동하지 않습니다. GlobalStyle
을 렌더링하도록 선택하는 곳마다 구성 요소가 렌더링 될 때 삽입됩니다.
주의 : createGlobalStyles
는 DOM에 있는 경우에만 렌더링됩니다.
CSS 도우미
이미 우리는 소품을 기반으로 스타일을 조정하는 방법을 보았습니다. 조금 더 나아가고 싶다면? CSS 도우미 기능은 이를 달성하는 데 도움이 됩니다. 상태가 서로 다른 두 개의 텍스트 입력 필드가 있다고 가정해 봅시다. 비어 있음과 활성, 각각 다른 색을 가집니다. 우리는 할 수있어:
const StyledTextField = styled.input` color: ${(props) => (props.isEmpty ? "none" : "black")}; `;
잘 지내고 있습니다. 결과적으로 채워진 다른 상태를 추가해야 하는 경우 스타일을 수정해야 합니다.
const StyledTextField = styled.input` color: ${(props) => props.isEmpty ? "none" : props.active ? "purple" : "blue"}; `;
이제 삼항 연산의 복잡성이 증가하고 있습니다. 나중에 텍스트 입력 필드에 다른 상태를 추가하면 어떻게 될까요? 또는 각 상태에 색상 이외의 추가 스타일을 부여하려면 어떻게 해야 합니까? 스타일을 삼항 연산으로 압축하는 것을 상상할 수 있습니까? css
도우미가 유용합니다.
const StyledTextField = styled.input` width: 100%; height: 40px; ${(props) => (props.empty && css` color: none; backgroundcolor: white; `) || (props.active && css` color: black; backgroundcolor: whitesmoke; `)} `;
우리가 한 것은 더 많은 스타일과 더 이해하기 쉽고 조직적인 구문을 수용하기 위해 삼항 구문을 확장한 것입니다. 앞의 문장이 잘못된 것 같다면 코드가 너무 많은 일을 하려고 하기 때문입니다. 따라서 뒤로 물러나서 수정해 보겠습니다.
const StyledTextField = styled.input` width: 100%; height: 40px; // 1. Empty state ${(props) => props.empty && css` color: none; backgroundcolor: white; `} // 2. Active state ${(props) => props.active && css` color: black; backgroundcolor: whitesmoke; `} // 3. Filled state ${(props) => props.filled && css` color: black; backgroundcolor: white; border: 1px solid green; `} `;
우리의 세련미는 스타일을 관리하기 쉽고 이해하기 쉬운 세 덩어리로 나눕니다. 승리다.
스타일시트관리자
CSS 도우미와 마찬가지로 StyleSheetManager
는 스타일 처리 방법을 수정하는 도우미 메서드입니다. 하위 트리에서 공급업체 접두사를 선택 해제하는 데 도움이 되는 disableVendorPrefixes
(전체 목록을 확인할 수 있음)와 같은 특정 소품이 필요합니다.
import styled, {StyleSheetManager} from "styled-components"; const StyledCard = styled.div` width: 200px; backgroundcolor: white; `; const StyledNav = styled.div` width: calc(100% - var(--side-nav-width)); `; function Profile() { return ( <div> <StyledNav /> <StyleSheetManager disableVendorPrefixes> <StyledCard> This is a card </StyledCard> </StyleSheetManager> </div> ); }
disableVendorPrefixes
는 <StyleSheetManager>
에 소품으로 전달됩니다. 따라서 <StyleSheetManager>
로 래핑된 스타일이 지정된 구성 요소는 비활성화되지만 <StyledNav>
의 구성 요소는 비활성화되지 않습니다.
더 쉬운 디버깅
내 동료 중 한 명에게 스타일이 지정된 구성 요소를 소개할 때 그들의 불만 중 하나는 DOM 또는 React 개발자 도구에서 렌더링된 요소를 찾기가 어렵다는 것이었습니다. 이것은 스타일이 지정된 구성 요소의 단점 중 하나입니다. 고유한 클래스 이름을 제공하려고 할 때 고유한 해시를 요소에 할당합니다. 이 해시는 비밀스럽긴 하지만 디버깅을 더 쉽게 하기 위해 displayName
을 읽을 수 있도록 합니다.
import React from "react"; import styled from "styled-components"; import "./App.css"; const LoginButton = styled.button` background-color: white; color: black; border: 1px solid red; `; function App() { return ( <div className="App"> <LoginButton>Login</LoginButton> </div> ); }
기본적으로 스타일이 지정된 구성 요소는 DOM에서 LoginButton
을 <button class="LoginButton-xxxx xxxx">Login</button>
으로 렌더링하고 React 개발자 도구에서 LoginButton
으로 렌더링하므로 디버깅이 더 쉬워집니다. 이 동작을 원하지 않으면 displayName
부울을 토글할 수 있습니다. 이를 위해서는 Babel 구성이 필요합니다.
참고 : 문서에는 babel-plugin-styled-components
패키지와 .babelrc
구성 파일이 지정되어 있습니다. 이것의 문제는 우리가 create-react-app
을 사용하기 때문에 꺼내지 않는 한 많은 것을 구성할 수 없다는 것입니다. 이것이 Babel 매크로가 들어오는 곳입니다.
npm 또는 Yarn을 사용하여 babel-plugin- babel-plugin-macros
를 설치하고 다음 내용과 함께 애플리케이션 루트에 babel-plugin-macros.config.js
를 생성해야 합니다.
module.exports = { styledComponents: { displayName: true, fileName: false, }, };
fileName
값이 반전되면 더욱 고유한 정밀도를 위해 displayName
에 파일 이름이 접두사로 붙습니다.
이제 macro
에서 가져와야 합니다.
// Before import styled from "styled-components"; // After import styled from "styled-components/macro";
결론
이제 CSS를 프로그래밍 방식으로 작성할 수 있으므로 자유를 남용하지 마십시오. 그 가치를 위해 스타일이 지정된 구성 요소에서 온전한 상태를 유지하기 위해 최선을 다하십시오. 무거운 조건문을 작성하려고 하거나 모든 것이 스타일이 지정된 구성 요소여야 한다고 가정하지 마십시오. 또한 모퉁이 어딘가에 있을 것이라고 추측만 하는 사용 사례에 대해 초기 스타일 구성 요소를 만들어 지나치게 추상화하지 마십시오.
추가 리소스
- 문서, 스타일이 지정된 구성 요소
- "React.js 및 스타일 구성 요소를 사용하여 재사용 가능한 구성 요소 시스템 구축", Lukas Gisder-Dube
- Next.js와 함께 사용
- Gatsby와 함께 사용