- 학습 내용
이번 시간 다룰 내용은 React를 활용하여 컴포넌트를 만들 때 그것의 디자인을 어떻게 할지에 대한 고민과 효율적인 디자인 방법인 CDD 그리고 그 도구인 Storybook에 대한 간단한 내용과 이번 간단한 스프린트를 진행하면서 익힌 CSS 요소에 대한 내용을 정리하고 학습하고자 한다.
- Component-Driven Development(CDD)
보통 프로젝트를 진행하면 규모가 클수록 여러 팀이 협업해서 진행한다. 그러다 보니 당연히 이전에 다른 팀에서 개발한 기능과 같은 기능을 구현해야 하는 경우가 있을 수 있다. 당연히 이때 같은 기능을 다시 만드는 일은 아주 비효율적이다. 여기서 여러 팀 간에 같은 UI 컴포넌트를 공유한다면 이런 문제는 자연스럽게 해결될 것이다. 그래서 재사용할 수 있는 UI 컴포넌트를 개발 단계에서부터 미리 디자인하고 개발하면 좋다. 이러한 개발 방법을 바로 Component Driven Development(CDD)라고 부른다. 레고와 같이 부품 단위로 UI 컴포넌트를 만들고 공유하여 조립하는 것이다.
- Storybook
CDD가 트렌드로 자리 잡으면서 이를 지원하는 도구 중 하나인 Component Explorer(컴포넌트 탐색기)가 등장했다. 그리고 그중 UI 개발도구 중 하나가 Storybook이다. storybook이란 각각의 컴포넌트들을 따로 볼 수 있게 구성해주어 한번에 하나의 컴포넌트에서 작업할 수 있게 도와준다. 복잡한 개발 스택을 시작하거나, 특정 데이터를 데이터베이스로 강제 이동하거나, 앱을 탐색할 필요 없이 전체 UI를 한눈에 보고 개발할 수 있다. 또 컴포넌트를 문서화하고 자동으로 컴포넌트를 시각화하여 시뮬레이션할 수 있는 다양한 테스트 상태를 확인할 수 있다. 그리고 이를 통해 재사용성을 확대할 수 있다. 결국 이것들을 활용하여 테스트 및 개발 속도를 향상할 수 있고, 결정적으로 의존성을 걱정하지 않고도 개발을 할 수 있게 된다.
위와 같은 장점들로 인해 개발에서 앱의 다양한 상황에 구애받지 않고 UI 컴포넌트를 집중적으로 개발할 수 있는 점 때문에 UI 개발도구를 사용하는데 storybook 역시 당연히 이런 독립적인 개발환경을 제공해준다. storybook에서 지원하는 주요 기능은 다음과 같다.
1 | UI 컴포넌트들을 카탈로그화하기 |
2 | 컴포넌트 변화를 Stories에 저장하기 |
3 | 핫 모듈 재 로딩과 같은 개발 툴 경험을 제공 |
4 | 리액트에 포함된 다양한 뷰 레이어 지원 |
- 구조적인 CSS 작성 방법의 발전
프로젝트의 규모나 복잡도가 점점 커지고 함께 작업해야 할 팀원 수가 많아짐에 따라 CSS를 작성하는 일관된 패턴이 없다는 것이 개발자들에게 가장 큰 걸림돌이 되었다. 또 모바일이나 태블릿을 비롯한 다양한 디바이스들의 증장으로 웹사이트들이 다양한 디스플레이를 커버해야 했기에 CSS가 더욱 복잡해졌다. 이 때문에 구조화된 CSS가 필수요건이 되었다. 이런 문제점들을 해결하기 위해 CSS 전처리기(CSS Preprocessor)라는 개념이 등장했다.
CSS 전처리기란 CSS가 구조적으로 장성될 수 있게 도움을 주는 도구인데, 우리가 흔히 CSS문서를 작성할 때 Color값을 찾거나 tag를 다는 일 등 번거롭고 반복적인 작업이 많다. 뿐만 아니라 클래스의 상속과 같은 복잡한 일들은 CSS 문서를 복잡하게 하고 유지관리에 많은 영향을 끼친다. 이런 문제점들을 프로그래밍 개념(변수, 함수, 상속 등)을 활용하여 해결해주는 것이 CSS 전처리기이다. 이 CSS 전처리기 자체만으로는 웹 서버가 인지하지 못하기 때문에 각 CSS 전처리기에 맞는 Compiler를 사용해서 실제로 우리가 사용하는 CSS 문서로 변환되게 해야 한다. 이것들을 통해 전에 비해 훨씬 CSS 파일들을 잘 구조화할 수 있게 되었고, 최소한 CSS 파일을 몇 개의 작은 파일로 분리할 수 있게 되었다.
CSS 전처리기 중에서 가장 유명한 것이 SASS(Syntactically Awesome Style Sheets)인데, CSS를 확장해 주는 스크립팅 언어이다. CSS를 만들어주는 언어로서 자바스크립트처럼 특정 속성 (ex. color, margin, width 등)의 값 (ex. #ffffff, 25rem, 100px 등)등을 변수로 선언하여 필요한 곳에 선언된 변수를 적용할 수도 있고, 반복되는 코드를 한 번의 선언으로 여러 곳에서 재사용할 수 있도록 해주는 드으이 기능을 가졌다. 그래서 SASS는 SCSS코드를 읽어서 전 처리한 다음 컴파일해서 전역 CSS 번들 파일을 만들어 주는 전처리기(preprocessor)의 역할을 하는 것이다. 그러나 얼마 지나지 않아서 SASS가 CSS의 구조화를 해결해주는 장점보다 다른 문제들을 더 많이 낸다는 것들이 밝혀진다. 즉, 전처리기가 내부에서 어떤 작업을 하는지는 알지 못한 채, 스타일이 겹치는 문제를 해결하기만을 위해 단순히 계층 구조를 만들어 내는 것만 의식하고, 그 결과로 컴파일된 CSS의 용량만 어마어마하게 커지는 결과를 냈다.
이런 CSS 전처리기의 문제를 보완하기 위해 BEM, OOCSS, SMACSS 같은 CSS 방법론이 대두되었다. 이 세 방법론은 모두 같은 지향점을 가지고 있으나 각각의 장단점이 있다. 공통된 지향점으로 첫째, 코드의 재사용, 둘째로 코드의 간결화 이것은 유지보수를 용이하게 하는 것과 관련이 있다. 셋째로 코드의 확장성 넷째 코드의 예측성이다. 이러한 지향점은 협업에 있어서 규칙으로 정하는 중요한 요소가 된다.
대표적인 CSS 방법론으로 BEM이 있다. BEM이란 Block, Element, Modifier로 구분하여 클래스명을 작성하는 바업ㅂ인데, Block, Element, Modifier를 각각 -와 _로 구분한다. 클래스명은 BEM 방식의 이름을 여러 번 반복하여 재사용할 수 있도록 하며, HTML/CSS/SASS 파일에서도 더 일관된 코딩 구조를 만들어 준다.
Block | 전체를 감싸고 있는 블럭 요소 |
Element | 블럭이 포함하고 있는 한 조각 |
Modifier | 블럭 또는 요소의 속성 |
하지만 이런 방법론들에서도 문제점이 발생하기 시작한다. 클래스명 선택자가 장황해지고, 이런 긴 클래스명 때문에 마크업이 불필요하게 커지며, 재사용하려고 할 때마다 모든 UI 컴포넌트를 명시적으로 확장해야만 한다. 또한 언어 로직 상에 진정한 캡슐화(encapsulation: 객체의 속성과 행위를 하나로 묶고 실제 구현 내용 일부를 외부에 감추어 은닉하는 개념)의 개념이 없다는 것 때문에 개발자들이 유일한 클래스명을 선택하는 것에 의존할 수밖에 없다는 문제가 있다. 이것이 왜 문제냐면 앱으로 개발 방향이 진화하면서 컴포넌트 단위의 개발은 캡슐화의 중요성을 불러왔기 때문이다.
이로 인해 CSS도 컴포넌트 영역으로 불러들이기 위해 CSS-in-JS를 개발했고, 이를 통해 이 문제들을 정확하게 해결했다. 이것의 대표적인 것에 Styled-Component가 있다. Styled-Component는 기능적(Functional) 혹은 상태를 가진 컴포넌트들로부터 UI를 완전히 불리해 사용할 수 있는 아주 단순한 패턴을 제공해준다.
특징 | 장점 | 단점 | |
CSS | 기본적인 스타일링 방법 | - | 일관된 패턴을 갖기 어려움, !important의 남용 |
SASS (preprosessor) |
프로그래밍 방법론을 도입하여, 컴포일된 CSS를 만들어내는 전처리기 |
변수/함수/상속 개념을 활용하여 재사용 가능, CSS의 구조화 |
전처리 과정이 필요, 디버깅의 어려움이 있음, 컴파일한 CSS 파일이 거대해짐 |
BEM | CSS 클래스명 작성에 일관된 패턴을 강제하는 방법론 |
네이밍으로 문제 해결, 전처리 과정 불필요 |
선택자의 이름이 장황하고, 클래스 목록이 너무 많아짐 |
Styled-Component (CSS-in-JS) |
컴포넌트 기반으로 CSS를 작성할 수 있게 도와주는 라이브러리 |
CSS를 컴포넌트 안으로 캡슐화, 네이밍이나 최적화를 신경 쓸 필요가 없음 |
빠른 페이지 로드에 불리함 |
- Styled-Component
Styled-Component는 React의 컴포넌트 기반 개발 환경에서 스타일링을 위한 CSS의 성능 향상을 위해 탄생했다. Styled-Component를 사용하면 기존 CSS 문법으로도 스타일 속성이 추가된 React 컴포넌트를 만들 수 있다. 즉, JavaScript에서 변수를 선언하듯이 버튼을 만들고, tag의 속성을 정의하고, 백 틱 안에 기존 CSS 문법을 이용하여 스타일 속성을 정의해 주는 것이다.
const Button = styled.a`
display: inline-blcok;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
`;
Styled-Component의 특징은 아래와 같다
1) Automatic critical CSS
Styled-Component는 화면에 어떤 컴포넌트가 렌더링 되었는지 추적해서 해당 컴포넌트에 대한 스타일을 자동으로 삽입한다. 따라서 코드를 적절히 분배해 놓으면 사용자가 앱을 사용할 때 최소한의 코드만으로 화면이 렌더링 되도록 할 수 있다.
2) No class name bugs
Styled-Component는 스스로 유니크한 className을 생성한다. 이는 className의 중복이나 오타로 인한 버그를 줄여준다.
3) Easier deletion of CSS
기존에는 더 이상 사용하지 않거나 삭제한 컴포넌트에 해당하는 스타일 속성을 제거하기 위해 CSS 파일 안의 className을 이리저리 찾아야 했다. 하지만 Styled Component는 모든 스타일 속성이 특정 컴포넌트와 연결되어 있기 때문에 만약 컴포넌트를 더 이상 사용하지 않아 삭제할 경우 이에 대한 스타일 속성도 함께 삭제된다.
4) Simple dynamic styling
className을 일일이 수동으로 관리할 필요 없이 React의 props나 전역 속성을 기반으로 컴포넌트에 스타일 송석을 부여하기 때문에 간단하고 직관적이다.
5) Painless maintenance
컴포넌트에 스타일을 상속하는 속성을 찾아 다른 CSS파일들을 검색하지 않아도 되기 때문에 코드의 크기가 커지더라도 유지보수가 어렵지 않다.
6) Automatic vendor prefixing
개별 컴포넌트마다 기존의 CSS를 이용하여 스타일 속성을 정의하면 될 뿐이다. 이외의 것들은 Styled-Component가 알아서 처리해준다.
- Adapting based on props & Extending Styles
Styled-Component는 스타일 속성을 지닌 컴포넌트를 정의할 때 함수를 전달하고 그 함수 안에서 props를 사용할 수도 있다. <Button> 컴포넌트의 background와 color속성은 primary라는 props의 전달 여부에 따라 컬러 값을 정의한다.
// Button component
...
background: ${(props.primary ? "palevioletred" : "white")}
color: ${(props.primary ? "white" : "palevioletred")}
...
// App component
...
<Button>Normal</Button>
<Button primary>Primary</Button>
...
또 같은 스타일 속성을 지닌 여러 개의 컴포넌트들 중 몇 개의 컴포넌트에는 약간의 변화를 주고 싶은 경우가 있을 수 있다. 이때에는 상속받고자 하는 스타일 속성을 지닌 컴포넌트를 styled()로 감싼 뒤, 변경하고 싶은 속성만 새로 정의해 주면 기존 속성을 확장하여 사용할 수 있다.
const Tomato = styled(Button)`
color: tomato;
border-color: tomato;
`;
- DOM reference를 잘 활용할 수 있는 useRef
React는 예외적인 상황에서 useRef로 DOM 노드, 엘리먼트, 그리고 리액트 컴포넌트 주소 값을 참조할 수 있다. 아래 예시 코드처럼 작성하면 주소 값을 활용할 수 있다.
const 주소값을_담는_그릇 = useRef(참조자료형)
return (
<div>
<input ref={주소값을_담는_그릇} type="text" />
{/* React에서 사용 가능한 ref라는 속성에 주소값을_담는_그릇을 값으로 할당하면*/}
{/* 주소값을_담는_그릇 변수에는 input DOM 엘리먼트의 주소가 담깁니다. */}
{/* 향후 다른 컴포넌트에서 input DOM 엘리먼트를 활용할 수 있습니다. */}
</div>
);
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- 스프린트를 진행하며 사용했던 CSS 기법
1 | grid | grid는 두 방향(가로-세로) 레이아웃 시스템이다. flex와 달리 2차원을 표현할 수 있다. flex와 같이 display: grid;를 설정하는 것으로 시작한다. |
https://studiomeal.com/archives/533 |
2 | z-index | z-index는 위치 지정 요소와 그 자손 또는 하위 플렉스 아이템의 z축 순서를 지정할 때 사용한다. z-index가 크면 더 작은 z-index의 요소를 덮는다. |
https://developer.mozilla.org/ko/docs/Web/CSS/z-index |
3 | transform | transform은 요소의 회전, 크기 조절, 기울이기, 이동 효과를 부여할 때 사용한다. 요소의 좌표 공간을 변경하는 것이다. |
https://developer.mozilla.org/ko/docs/Web/CSS/transform |
4 | position | position은 문서에 요소를 어떻게 배치할것인지를 정할 때 사용한다. |
https://developer.mozilla.org/ko/docs/Web/CSS/position |
5 | transition | transition은 CSS 속성에 변화를 줄 때 애니메이션 속도를 조절할 때 사용한다. 속성 변경이 즉시 일어나지 않고 일정 기간에 걸쳐 일어나도록하여 효과를 주는 것이다. | https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Transitions/ |
'React' 카테고리의 다른 글
useState 동기적으로 사용하기 (0) | 2022.11.08 |
---|---|
[React] 상태 관리 (0) | 2021.12.08 |
React 기초3 state & props (0) | 2021.08.26 |
React 기초2 라우터(Router) (0) | 2021.08.13 |
React 기초1 (0) | 2021.08.13 |