Styled-Components: 최신 웹용 CSS-in-JS 라이브러리
게시 됨: 2022-03-11CSS는 "오래된 웹"에 포함될 것으로 예상되는 문서용으로 설계되었습니다. Sass 또는 Less와 같은 전처리기의 출현은 커뮤니티가 CSS가 제공하는 것보다 더 많은 것을 필요로 한다는 것을 보여줍니다. 시간이 지남에 따라 웹 앱이 점점 더 복잡해짐에 따라 CSS의 제한 사항이 점점 더 가시적이고 완화하기 어려워졌습니다.
Styled-components 는 완전한 프로그래밍 언어인 JavaScript와 범위 지정 기능을 활용하여 코드를 구성 요소로 구성하는 데 도움이 됩니다. 이는 대규모 프로젝트에서 CSS를 작성하고 유지 관리하는 일반적인 함정을 피하는 데 도움이 됩니다. 개발자는 부작용의 위험 없이 구성 요소의 스타일을 설명할 수 있습니다.
뭐가 문제 야?
CSS 사용의 한 가지 이점은 스타일이 코드에서 완전히 분리된다는 것입니다. 이것은 개발자와 디자이너가 서로 간섭하지 않고 병렬로 작업할 수 있음을 의미합니다.
반면에 styled-components 를 사용하면 스타일과 논리를 강력하게 결합하는 함정에 빠지기 쉽습니다. Max Stoiber는 이것을 피하는 방법을 설명합니다. 로직과 프리젠테이션을 분리한다는 아이디어는 확실히 새로운 것은 아니지만, React 컴포넌트를 개발할 때 지름길을 택하고 싶은 유혹을 받을 수 있습니다. 예를 들어 클릭 동작과 버튼 스타일을 처리하는 유효성 검사 버튼에 대한 구성 요소를 쉽게 만들 수 있습니다. 두 구성 요소로 나누려면 조금 더 많은 노력이 필요합니다.
컨테이너/프레젠테이션 아키텍처
이것은 아주 간단한 원리입니다. 구성 요소는 사물의 모양을 정의하거나 데이터와 논리를 관리합니다. 프레젠테이션 구성 요소의 매우 중요한 측면은 종속성이 없어야 한다는 것입니다. 그들은 소품을 받고 그에 따라 DOM(또는 자식)을 렌더링합니다. 반면에 컨테이너는 데이터 아키텍처(상태, redux, 플럭스 등)에 대해 알고 있지만 표시에 대해 책임을 져서는 안 됩니다. Dan Abramov의 기사는 이 아키텍처에 대한 매우 훌륭하고 자세한 설명입니다.
SMACSS를 기억하다
CSS를 위한 확장 가능 및 모듈식 아키텍처가 CSS 구성을 위한 스타일 가이드이지만 기본 개념은 대부분 자동으로 스타일이 지정된 구성 요소가 따릅니다. 아이디어는 CSS를 5가지 범주로 나누는 것입니다.
- Base 는 모든 일반 규칙을 포함합니다.
- Layout 의 목적은 콘텐츠의 다양한 섹션(예: 머리글, 바닥글, 사이드바, 콘텐츠)뿐만 아니라 구조적 속성을 정의하는 것입니다.
- 모듈 에는 UI의 다양한 논리적 블록에 대한 하위 범주가 포함됩니다.
- State 는 요소의 상태를 나타내는 수정자 클래스를 정의합니다(예: 필드 오류, 비활성화된 버튼).
- 테마 에는 수정 가능하거나 사용자 기본 설정에 따라 달라질 수 있는 색상, 글꼴 및 기타 외형적 측면이 포함됩니다.
styled-component 를 사용하는 동안 이 분리를 유지하는 것은 쉽습니다. 프로젝트에는 일반적으로 일종의 CSS 정규화 또는 재설정이 포함됩니다. 이것은 일반적으로 기본 범주에 속합니다. 일반적인 글꼴 크기, 줄 크기 등을 정의할 수도 있습니다. 이는 일반 CSS(또는 Sass/Less) 또는 styled-components 에서 제공하는 injectGlobal
함수를 통해 수행할 수 있습니다.
레이아웃 규칙의 경우 UI 프레임워크를 사용하면 아마도 컨테이너 클래스 또는 그리드 시스템을 정의할 것입니다. 작성하는 레이아웃 구성 요소의 고유한 규칙과 함께 해당 클래스를 쉽게 사용할 수 있습니다.
모듈 다음에는 styled-components 아키텍처가 자동으로 따라옵니다. 스타일이 외부 파일에 설명되지 않고 구성 요소에 직접 첨부되기 때문입니다. 기본적으로 작성하는 각 스타일 구성 요소는 자체 모듈이 됩니다. 부작용 걱정 없이 스타일링 코드를 작성할 수 있습니다.
상태 는 구성 요소 내에서 변수 규칙으로 정의하는 규칙입니다. CSS 속성 값을 보간하는 함수를 정의하기만 하면 됩니다. UI 프레임워크를 사용하는 경우 구성 요소에 추가할 유용한 클래스도 있을 수 있습니다. CSS 의사 선택기 규칙(마우스 오버, 포커스 등)도 있을 것입니다.
테마 는 구성 요소 내에서 간단히 삽입할 수 있습니다. 테마를 애플리케이션 전체에서 사용할 변수 세트로 정의하는 것이 좋습니다. 예를 들어 대비 및 하이라이트를 처리하기 위해 프로그래밍 방식으로(라이브러리를 사용하거나 수동으로) 색상을 파생시킬 수도 있습니다. 프로그래밍 언어의 모든 기능을 마음대로 사용할 수 있다는 것을 기억하십시오!
솔루션을 위해 함께 모으십시오
더 쉬운 탐색 경험을 위해 함께 유지하는 것이 중요합니다. 우리는 그것들을 유형(프레젠테이션 대 논리)별로 구성하는 것이 아니라 기능별로 구성하고 싶습니다.
따라서 모든 일반 구성 요소(버튼 등)에 대한 폴더가 생깁니다. 나머지는 프로젝트 및 해당 기능에 따라 구성해야 합니다. 예를 들어 사용자 관리 기능이 있는 경우 해당 기능과 관련된 모든 구성 요소를 그룹화해야 합니다.
styled-components의 컨테이너/프레젠테이션 아키텍처를 SMACSS 접근 방식에 적용하려면 추가 유형의 구성 요소인 구조가 필요합니다. 우리는 세 종류의 구성 요소로 끝납니다. 스타일, 구조 및 컨테이너. styled-components 는 태그(또는 구성 요소)를 장식하기 때문에 DOM을 구성하려면 이 세 번째 유형의 구성 요소가 필요합니다. 경우에 따라 컨테이너 구성 요소가 하위 구성 요소의 구조를 처리하도록 허용할 수 있지만 DOM 구조가 복잡해지고 시각적 목적을 위해 필요한 경우에는 분리하는 것이 가장 좋습니다. DOM이 일반적으로 매우 장황해지는 테이블이 좋은 예입니다.
예시 프로젝트
이러한 원칙을 설명하는 레시피를 표시하는 작은 앱을 빌드해 보겠습니다. 레시피 구성 요소 구축을 시작할 수 있습니다. 상위 구성 요소는 컨트롤러가 됩니다. 상태(이 경우 레시피 목록)를 처리합니다. 또한 데이터를 가져오기 위해 API 함수를 호출합니다.
class Recipes extends Component{ constructor (props) { super(props); this.state = { recipes: [] }; } componentDidMount () { this.loadData() } loadData () { getRecipes().then(recipes => { this.setState({recipes}) }) } render() { let {recipes} = this.state return ( <RecipesContainer recipes={recipes} /> ) } }
레시피 목록을 렌더링하지만 방법을 알 필요는 없습니다. 따라서 레시피 목록을 가져오고 DOM을 출력하는 또 다른 구성 요소를 렌더링합니다.

class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
여기에서 실제로 타일 그리드를 만들고 싶습니다. 실제 타일 레이아웃을 일반 구성 요소로 만드는 것이 좋습니다. 따라서 이를 추출하면 다음과 같은 새 구성 요소를 얻습니다.
class TilesContainer extends Component { render () { let {children} = this.props return ( <Tiles> { React.Children.map(children, (child, i) => ( <Tile key={i}> {child} </Tile> )) } </Tiles> ) } }
TilesStyles.js:
export const Tiles = styled.div` padding: 20px 10px; display: flex; flex-direction: row; flex-wrap: wrap; ` export const Tile = styled.div` flex: 1 1 auto; ... display: flex; & > div { flex: 1 0 auto; } `
이 구성 요소는 순전히 표시용입니다. 스타일을 정의하고 타일의 모양을 정의하는 다른 스타일의 DOM 요소 내부에서 수신하는 모든 자식을 래핑합니다. 이는 일반적인 프레젠테이션 구성 요소가 아키텍처적으로 어떻게 보일지에 대한 좋은 예입니다.
그런 다음 레시피가 어떻게 생겼는지 정의해야 합니다. 상대적으로 복잡한 DOM을 설명하고 필요할 때 스타일을 정의하려면 컨테이너 구성 요소가 필요합니다. 우리는 이것으로 끝납니다.
class RecipeContainer extends Component { onChangeServings (e) { let {changeServings} = this.props changeServings(e.target.value) } render () { let {title, ingredients, instructions, time, servings} = this.props return ( <Recipe> <Title>{title}</Title> <div>{time}</div> <div>Serving <input type="number" min="1" max="1000" value={servings} onChange={this.onChangeServings.bind(this)}/> </div> <Ingredients> {ingredients.map((ingredient, i) => ( <Ingredient key={i} servings={servings}> <span className="name">{ingredient.name}</span> <span className="quantity">{ingredient.quantity * servings} {ingredient.unit}</span> </Ingredient> ))} </Ingredients> <div> {instructions.map((instruction, i) => (<p key={i}>{instruction}</p>))} </div> </Recipe> ) } }
여기에서 컨테이너는 일부 DOM 생성을 수행하지만 이것이 포함하는 유일한 논리임을 주목하세요. 중첩된 스타일을 정의할 수 있으므로 스타일 지정이 필요한 각 태그에 대해 스타일이 지정된 요소를 만들 필요가 없습니다. 재료 항목의 이름과 수량에 대해 여기에서 수행하는 작업입니다. 물론, 우리는 그것을 더 분할하고 재료에 대한 새로운 구성 요소를 만들 수 있습니다. 프로젝트 복잡성에 따라 세부 수준을 결정하는 것은 사용자의 몫입니다. 이 경우에는 RecipeStyles 파일의 나머지 부분과 함께 정의된 스타일이 지정된 구성 요소일 뿐입니다.
export const Recipe = styled.div` background-color: ${theme('colors.background-highlight')}; `; export const Title = styled.div` font-weight: bold; ` export const Ingredients = styled.ul` margin: 5px 0; ` export const Ingredient = styled.li` & .name { ... } & .quantity { ... } `
이 연습의 목적을 위해 ThemeProvider를 사용했습니다. 스타일이 지정된 구성 요소의 소품에 테마를 주입합니다. 단순히 color: ${props => props.theme.core_color}
, 테마의 누락된 속성으로부터 보호하기 위해 작은 래퍼를 사용하고 있습니다.
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
모듈에서 고유한 상수를 정의하고 대신 사용할 수도 있습니다. 예: color: ${styleConstants.core_color}
장점
styled-components 사용의 장점은 원하는 만큼만 사용할 수 있다는 것입니다. 선호하는 UI 프레임워크를 사용하고 그 위에 스타일 구성 요소 를 추가할 수 있습니다. 이는 또한 기존 프로젝트 구성 요소를 구성 요소별로 쉽게 마이그레이션할 수 있음을 의미합니다. 표준 CSS를 사용하여 대부분의 레이아웃 스타일을 지정하고 재사용 가능한 구성 요소에 대해서만 styled-components 를 사용하도록 선택할 수 있습니다.
단점
디자이너/스타일 통합자는 변수를 처리하고 Sass/Less 대신 사용하려면 매우 기본적인 JavaScript를 배워야 합니다.
그들은 또한 프로젝트 구조를 탐색하는 방법을 배워야 할 것입니다. 그러나 수정해야 할 규칙이 포함된 올바른 CSS/Sass/Less 파일을 찾는 것보다 해당 구성 요소의 폴더에서 구성 요소의 스타일을 찾는 것이 더 쉽다고 주장합니다.
또한 구문 강조, 린트 등을 원하는 경우 도구를 약간 변경해야 합니다. 이 Atom 플러그인과 이 babel 플러그인으로 시작하는 것이 좋습니다.