프로잭트 중 이전에 요청 받았던 버튼을 똑같이 사용하도록 요청받았다.
여러 프로젝트 혹은 여러 팀 간에 같은 UI 컴포넌트를 공유한다면,
디자인과 개발 단계에서부터 재사용할 수 있는 UI 컴포넌트를 미리 디자인하고 개발하면 이런 고민을 해결할 수 있다.
이 고민을 해결하기 위해 등장한 개발 방법이 바로 Component Driven Development(CDD) 이다.
레고처럼 조립해 나갈 수 있는 부품 단위로 UI 컴포넌트를 반들어 나가는 개발을 진행할 수 있다.
인터넷이 만들어진 이후 기술의 발달과 함께 사용자들은 다양한 환경(디바이스)에서 인터넷을 사용하기 시작했다.
이에따라 개발자들의 CSS 작성 방식도 꾸준히 진화해 오고 있다.
CSS 작업을 효율적으로 하기 위해 구조화된 CSS의 필요성이 대두되었고, CSS를 구조화하는 방법에 대한 연구가 필요해졌다.
CSS 전처리기 CSS Preprocessor
라는 개념이 등장했다.
CSS가 구조적으로 작성될 수 있도록 도움을 주는 도구이다.
여러가지 요인으로 CSS 문서는 양이 많아지고 이로 인해 이후 유지관리에 많은 영향을 끼친다.
이런 CSS의 문제점들을 프로그래밍 개념( 변수, 함수, 상속 등)을 활용하여 해결해 나갈 수 있다.
하지만 CSS 전처리기 자체만으로는 웹 서버가 인지하지 못하기 때문에 CSS 전처리기에 맞는 Compiler를 사용해야 하고 컴파일을 하게 되면 실제로 우리가 사용하는 CSS 문서로 변환이 된다.
이를 통해 CSS 파일들을 잘 구조화할 수 있게 되었고, 최소한 CSS 파일을 몇 개의 작은 파일로 분리할 수 있는 방법이 생겼다.
/** CSS **/
.alert(
border: 1px solid color;
)
.button(
color : color
)
/** --> SASS **/
$base-color: color
.alert(
border: 1px solid $border-dark
)
.button(
color: $border-dark
)
CSS 전처리기 중에서 가장 유명한 SASS 는 Syntactically Awesome Style Sheets의 약자로 CSS를 확장해 주는 스크립팅 언어이다.
즉, CSS를 만들어주는 언어로서 자바스크립트처럼 특정 속성의 값을 변수로 선언하여 필요한 곳에 선언된 변수를 적용할 수도 있고, 반복되는 코드를 한 번의 선언으로 여러 곳에서 재사용할 수 있도록 해주는 등의 기능을 가졌다.
하지만 얼마 지나지 않아서 SASS가 'CSS의 구조화'를 해결해 주는 것의 장점보다 다른 문제들을 더 많이 만들어낸다는 것이 밝혀진다.
결국 전처리기가 내부에서 어떤 작업을 하는지 모른채로 스타일이 겹치는 문제를 해결하기 위해 단순히 계층 구조를 만들어 내는 것에 의지하게 되었고, 그 결과 컴파일 된 CSS의 용량은 어마어마하게 커지게 되었다.
CSS 전처리기의 문제를 보완하기 위해 BEM, OOCSS, SMACSS 같은 CSS 방법론이 대두 되었다. 각각의 장단점이 있으나 결국 세 방법론 모두 같은 지향점을 가지고 있다.
Block, Element, Modifier로 구분하여 클래스명을 작성하는 방법
대표적인 CSS 방법론으로는 BEM이 있다.
BEM이란 Block, Element, Modifier로 구분하여 클래스명을 작성하는 방법이며, Block, Element, Modifier 각각은 --dhk __ 로 구분한다.
클래스명은 BEM 방식의 이름을 여러 번 반복하여 재사용할 수 있도록 하며 HTML/CSS/SASS 파일에서도 더 일관된 코딩 구조를 만들어 준다.
하지만 이러한 방법론들에서도 문제점이 발생하기 시작한다.
클래스명 선택자가 장황해지고, 마크업이 불필요하게 커지며, 재사용하려고 할 때마다 모든 UI 컴포넌트를 명시적으로 확장해야만 했다.
Encapsulation : 객체의 속성과 행위를 하나로 묶고 실제 구현 내용 일부를 외부에 감추어 은닉하는 개념
이 없다는 것이 문제점. 이로 인해 개발자들이 유일한 클래스명을 선택하는 것에 의존할 수 밖에 없었다.
애플리케이션으로 개발 방향이 진화하면서 컴포넌트 단위의 개발은 캡슐화의 중요성을 불러왔다.
CSS를 컴포넌트 영역으로 불러들이기 위해서 CSS - in -JS가 탄생해서 이 문제를 정확하게 해결했다.
CSS-in-JS에는 대표적으로 Styled-Component가 있다.
기능적Functional
혹은 상태를 가진 컴포넌트들로부터 UI를 완전히 분리해 사용할 수 있는 아주 단순한 패턴을 제공한다.
특징 | 장점 | 단점 | |
---|---|---|---|
CSS | 기본적인 스타일링 방법 | - | 일관된 패턴을 갖기 어려움 !important의 남용 |
SASS (preprocessor) | 프로그래밍 방법론을 도입하며, 컴파일된 CSS를 만들어내는 전처리기 | 변수/함수/상속 개념을 활용하여 재사용 가능 CSS의 구조화 | 전처리 과정이 필요, 디버깅의 어려움이 있음 컴파일한 CSS파일이 거대해짐 |
BEM | CSS 클래스명 작성에 일관된 패턴을 강제하는 방법론 | 네이밍으로 문제 해결, 전처리 과정 불필요 | 선택자의 이름이 장황하고, 클래스 목록이 너무 많아짐 |
Styled-Componet (CSS-in-JS) | 컴포넌트 기반으로 CSS를 작성할수 있게 도와주는 라이브러리 | CSS를 컴포넌트 안으로 캡슐화, 네이밍이나 최적화를 신경 쓸 필요가 없음 | 빠른 페이지 로드에 불리함 |
Styled Components는 앞서 배운 CSS in JS라는 개념이 대두되면서 나온 라이브러리.
기존에 HTML,CSS,JS 로 쪼개서 개발하던 방법에서, React 등의 라이브러리의 등장으로 컴포넌트 단위 개발이 주류가 되었지만, CSS는 그렇지 못했단 점에서 출발한 개념.
CSS in JS 라이브러리를 사용하면 CSS도 쉽게 JS 안에 넣어줄 수 있으므로, HTML + JS + CSS까지 묶어서 하나의 JS 파일 안에서 컴포넌트 단위로 개발할 수 있게 된다.
이런 CSS in JS 라이브러리 중에서 현재 가장 인기 있는 라이브러리가 바로 Styled Components이다.
Styled Components로 컴포넌트를 만드는 방법은 다음과 같다.
const 컴포넌트이름 = styled.태그종류`
CSS속성1 : 속성값;
CSS속성2 : 속성값;
`
Styled Components는 ES6의 Templet Literals 문법을 사용한다. 즉, '
이 아니라 백틱(`
) 을 사용한다.
import styled from "styled-components";
//import 를 꼭 해주자.
//Styled Components로 컴포넌트를 만들고
const 컴포넌트이름 = styled.태그종류`
CSS속성1 : 속성값;
CSS속성2 : 속성값;
`
export default function App(){
// React 컴포넌트를 사용하듯이 사용하면 된다.
return <컴포넌트이름> 내용 </컴포넌트이름>;
}
const 컴포넌트이름 = styled(재활용할 컴포넌트)`
추가할 CSS속성1:속성값;
추가할 CSS속성2:속성값;
`
이미 만들어진 컴포넌트를 재활용해서 새로운 컴포넌트를 만들 수도 있다. 컴포넌트를 선언하고 styled()
에 재활용할 컴포넌트를 전달해준 다음, 추가하고 싶은 스타일 속성을 작성해주면 된다.
import styled from "styled-components";
//import 를 꼭 해주자.
//Styled Components로 컴포넌트를 만들고
const 컴포넌트이름1 = styled.태그종류`
CSS속성1 : 속성값;
CSS속성2 : 속성값;
`
//만들어진 컴포넌트를 재활용해 컴포넌트를 만들 수 있다
const 컴포넌트이름2 = styled(컴포넌트이름1)`
추가할 CSS속성1:속성값;
추가할 CSS속성2:속성값;
`
//재활용한 컴포넌트를 재활용할 수도 있다.
const 컴포넌트이름3 = styled(컴포넌트이름2)`
추가할 CSS속성1:속성값;
추가할 CSS속성2:속성값;
`
export default function App() {
return (
<>
<컴포넌트이름> 내용 </컴포넌트이름>
<br />
<컴포넌트이름2> 내용 </컴포넌트이름2>
<br />
<컴포넌트이름3> 내용 </컴포넌트이름3>
</>
);
}
Styled Component로 만든 컴포넌트도 React 컴포넌트처럼 props를 내려줄 수 있다. 내려준 props 값에 따라서 컴포넌트를 렌더링하는 것도 가능하다.
const 컴포넌트이름 = styled.태그종류`
css속성:${(props)=> 함수 코드}
`
Styled Components는 템플릿 리터럴 문법( ${ }
)을 사용하여 JavaScript 코드를 사용할 수 있다. props를 받아오려면 props를 인자로 받는 함수를 만들어 사용하면 된다.
const 컴포넌트이름 = styled.태그종류`
background:${(props)=> props.blue?"blue":"white"}
`
삼항연산자를 활용해 컴포넌트에 blue라는 props가 있는지 확인하고, 있으면 배경색으로 blue를 없으면 white를 지정해주는 예시코드.
props 값을 통째로 활용해서 컴포넌트 렌더링에 활용할 수 있다.
const 컴포넌트이름 = styled.태그종류`
background:${(props)=> props.color?props.color:"white"}
`;
<styled.태그종류>내용1</stlyed.태그종류>
<styled.태그종류 color="red">내용2</stlyed.태그종류>
<styled.태그종류 color='yellow'>내용3</stlyed.태그종류>
똑같이 상항연산자를 사용하고 있지만, 이번에는 props.color
가 없다면 white
를, 있다면 props.color
의 값을 그대로 가져와서 스타일 속성 값으로 리턴해준다.
const 컴포넌트이름 = styled.태그종류`
background:${(props)=> props.color||"white"}
`;
<styled.태그종류>내용1</stlyed.태그종류>
<styled.태그종류 color="red">내용2</stlyed.태그종류>
<styled.태그종류 color='yellow'>내용3</stlyed.태그종류>
꼭 삼항연산자만 사용해야하는 것은 아니다. JS 코드라면 무엇이든 사용할 수 있으므로 원하는 값을 사용할 수 있도록 함수코드를 만들어서 사용하면 된다.
스타일을 컴포넌트로 만들 수 있다는 것은 좋지만, 전역에 스타일을 설정하고 싶을 땐 어떻게 하면 좋을까?
우선 전역 스타일을 설정하기 위해 Style Components에서 createGlobalStyle
함수를 불러온다.
import { createGlobalStyle } from "styled-components";
그 다음 이 함수를 사용해 CSS 파일에서 작성하듯 설정해주고 싶은 스타일을 작성한다.
const GlobalStyle = createGlobalStyle`
button{
padding : 2rem;
margin : 1.5rem;
border-radius : 30px;
}
`
이렇게 만들어진 <GlobalStyle>
컴포넌트를 최상위 컴포넌트에서 사용해주면 전역에 <GlobalStyle>
컴포넌트의 스타일이 적용된다.
function App(){
return(
<>
<GlobalStyle />
<input>전역 스타일 적용하기</input>
</>
)
}
Component Driven Development 가 트렌드로 자리 잡게 되면서 이를 지원하는 도구 중 하나인 컴포넌트 탐색기Component Explorer
가 등장했다. Component Explorer에는 많은 UI 개발 도구가 다양하게 있는데 그 중 하나가 Storybook이다.
Component Dreven Development를 하기 위한 도구.
각각의 컴포넌트들을 따로 볼 수 있게 구성해주어 한 번에 하나의 컴포넌트에서 작업할 수 있다. 복잡한 개발 스택을 시작하거나, 특정 데이터를 데이터 베이스로 강제 이동하거나, 애플리케이션을 탐샐할 필요 없이 전체 UI를 한눈에 보고 개발할 수 있다.
Storybook은 기본적으로 독립적인 개발 환경에서 실행된다. 개발자는 애플리케이션의 다양한 상황에 구애받지 않고 UI 컴포넌트를 집중적으로 개발 할 수 있다.
지원하는 기능
React만 가지고 거의 대부분의 프론트엔드 요구사항을 구현할 수 있었다.
DOM 지식이 필요없다고 생각할 수도 있다.
하지만 리엑트로 모든 개발 요구사항을 충족할 수는 없다.
아래와 같이 DOM 엘리먼트의 주소값을 활용해야 하는 경우 특히 그렇다
리엑트는 이런 예외적인 상황에서 useRef
로 DOM Node, element, React 컴포넌트 주소값을 참조할 수 있다.