[TSR] React - 컴포넌트 디자인

해당글은 오늘 공부한 내용을 정리하여 메모하는 형식으로 작성한 것으로
설명이나 이해를 돕는 글이 아님을 명시합니다.

Intro

Component Driven Development

  • 프로젝트 진행 시, 같은 디자인의 기능을 하는 부분을 반복하여 개발하게 되면 불필요한 시간이 낭비된다. 그러므로 시간을 절약하기 위해서 디자인과 개발 단계에서부터 재사용할 수 있는 UI 컴포넌트를 미리 디자인하고 개발이 이루어져야한다.
  • 위 문제를 해결하기 위한 개발 방법이 Component Driven Development (CDD) 이다.
  • 부품 단위로 UI 컴포넌트를 만들어 나가는 개발 진행 방법
  • ex) BBC, UN

Storybook

  • Component Explorer (컴포넌트 탐색기) : Component Driven Development 가 트렌드로 자리 잡게 되면서 이를 지원하는 도구 중 하나
  • Storybook: Component Explorer에는 많은 UI 개발도구 중 하나

Storybook은 무엇인가?

  • 각각의 컴포넌트들을 따로 볼 수 있게 구성되어 한 번에 하나의 컴포넌트에서 작업 가능하다.
  • 전체 UI를 한눈에 보고 개발 할 수 있다.
  • 재사용성을 확대하기 위해 컴포넌트를 문서화하고, 자동으로 컴포넌트를 시각화하여 시뮬레이션하는 다양한 테스트 상태를 확인 수 있다. 이에 버그를 사전 방지 할 수 있다.
  • 테스트 및 작업 속도 향상되며 애플리케이션 또한 의존성 걱정없이 빌드 할 수 있다.

Storybook을 쓰는 이유

  • 기본적으로 독립적인 개발환경에서 실행되므로 개발자는 애플리케이션의 UI 컴포넌트를 집중적으로 개발 할 수 있다.
  • ex> 회사의 내부 개발자들을 위해 문서화 하여 회사의 UI 라이브러리로 사용, 외부 공개용 디자인 시스템을 개발하기 위한 기본 플랫폼으로 사용

storyBook 지원 기능

  • UI 컴포넌트들을 카탈로그화
  • 컴포넌트 변화를 Stories로 저장
  • 핫모듈 재로딩과 같은 개발 툴 경험을 제공
  • 리액트를 포함한 다양한 뷰 레이어 지원

Storybook 설치 및 셋팅

# Clone the template
npx degit chromaui/intro-storybook-react-template taskbox

cd taskbox

# Install dependencies
yarn


CSS in JS 방법론

구조적 CSS 작성 방법의 발전

  • CSS 발전 : CSS => SASS => BEM => CSS Modules => Styled Components

구조화된 CSS가 필요한 이유

  • CSS의 일관적인 패턴이 없어서 프로젝트 규모가 크거나 복잡도 커지면 개발자들이 관리하기 힘들다.
  • 또한 다양한 디바이스들의 디스플레이를 대응하기 위한 CSS의 복잡도는 더 올라가게 된다.

CSS 전처리기 출현

  • CSS 전처리기(CSS Preprocessor) : CSS가 구조적으로 작성될 수 있게 도움을 주는 도구
  • CSS의 문제점들(많은 반복적인 작업, 클래스의 상속과 같은 사항 등)을 프로그래밍 개념(변수, 함수, 상속 등)을 활용하여 해결
  • 각 CSS 전처리기에 맞는 Compiler를 사용해야 하고 컴파일를 실행야만 웹서버가 인지한다.
  • Compiler는 기본 CSS로 변환하는 일을 한다.

SASS

  • SASS(Syntactically Awesome Style Sheets)은 CSS를 확장해 주는 스크립팅 언어이다.
  • CSS 전처리기 중에서 가장 유명하다.
  • CSS를 만들어주는 언어이다.
  • 자바스크립트처럼 특정 속성(ex. color, margin, width 등)의 값(ex. #ffffff, 25rem, 100px 등)을 변수로 선언하여 필요한 곳에 선언된 변수를 적용할 수도 있다. 이로써 반복되는 코드를 한 번의 선언으로 여러 곳에서 재사용할 수 있다.
  • SASS는 SCSS 코드를 읽어서 전처리한 다음 컴파일해서 전역 CSS 번들 파일을 만들어 주는 전처리기(preprocessor)의 역할 한다.

전처리기 문제

  • 전처리기(preprocessor)가 내부에서 어떤 작업을 하는지는 알지 못하고, 스타일이 겹치는 문제를 해결하기 위해 단순히 계층 구조를 만들어 내는 것에 의지하게 되었다.
    그 결과 컴파일된 CSS의 용량은 어마어마하게 커지게 되었다.

CSS 방법론

  • CSS 전처리기의 문제를 보완하기 위해 BEM, OOCSS, SMACSS 같은 CSS 방법론이 나왔다.
  • CSS방법론은 협업과 연결되므로 CSS 작성에 있어서 방법들을 규칙으로 정해두는 것은 매우 중요하다.
  • 방법론 공통 지향점
    • 코드의 재사용
    • 코드의 간결화(유지 보수 용이)
    • 코드의 확장성
    • 코드의 예측성(클래스 명으로 의미 예측)

BEM

  • BEM이란 Block, Element, Modifier로 구분하여 클래스명을 작성하는 방법
    • Block: 전체를 감싸고 있는 블럭 요소
    • Element: 블럭이 포함하고 있는 한 조각
    • Modifier: 블럭 또는 엘리먼트의 속성(블럭 또는 엘리먼트의 외관이나 상태를 변화가능하게 하는 부분)
  • Block, Element, Modifier 각각은 __로 구분
  • 클래스명은 BEM 방식의 이름을 여러 번 반복하여 재사용할 수 있도록 하며 HTML/CSS/SASS 파일에서도 더 일관된 코딩 구조를 만들어 준다.
/* Block__Element--Modifier */
.header__navigation--navi-text { color: red}

CSS 방법론의 문제

  • 클래스명 선택자가 장황해지고, 긴 클래스명 때문에 마크업이 불필요하게 커졌다.
  • 재사용하려고 할 때마다 모든 UI 컴포넌트를 명시적으로 확장해야만 했다.
  • 언어 로직 상에 진정한 캡슐화(encapsulation : 객체의 속성과 행위를 하나로 묶고 실제 구현 내용 일부를 외부에 감추어 은닉하는 개념)의 개념이 없다. => 유일한 클래스명을 선택하는 것에 의존

CSS-in-JS : style-component

  • CSS-in-JS가 만들어진 이유 : 컴포넌트 단위의 개발에서 캡슐화가 중요해졌지만 CSS는 컴포넌트 기반의 방식으로 만들어진 적이 없어서 이 문제를 해결하기 위해서 CSS를 컴포넌트 영역 컴포넌트 영역으로 가져와 쓰는 CSS-in-JS가 만들어졌다.

Styled-Component

  • 기능적(Functional) 혹은 상태를 가진 컴포넌트들로부터 UI를 완전히 분리해 사용할 수 있는 아주 단순한 패턴을 제공한다.

Style-Component

공식문서

  • React 의 컴포넌트 기반 개발 환경에서 스타일링을 위한 CSS의 성능 향상을 위해 만들어졌다.
  • 기존 CSS 문법으로도 스타일 속성이 추가된 React 컴포넌트를 만들 수 있다.
const Button = styled.a`
  display: inline-block;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
`;

특징

  • Automatic critical CSS
    • 화면에 어떤 컴포넌트가 렌더링 되었는지 추적해서 해당하는 컴포넌트에 대한 스타일을 자동으로 삽입한다.
    • 사용자가 어플리케이션을 사용할 때 최소한의 코드만으로 화면이 띄워지도록 할 수 있다.
  • No class name bugs
    • 스스로 유니크한 className 을 생성하므로써 className 의 중복이나 오타로 인한 버그를 줄인다.
  • Easier deletion of CSS
    • 모든 스타일 속성이 특정 컴포넌트와 연결되어 있기 때문에 컴포넌트를 더 이상 사용하지 않아 삭제할 경우 이에 대한 스타일 속성도 함께 삭제된다.
  • Simple dynamic styling
    • className을 일일이 수동으로 관리할 필요 없이 React 의 props 나 전역 속성을 기반으로 컴포넌트에 스타일 속성을 부여하기 때문에 간단하고 직관적이다.
  • Painless maintenance
    • 컴포넌트에 스타일을 상속하는 속성을 찾아 다른 CSS 파일들을 검색하지 않아도 되기 때문에 코드의 크기가 커지더라도 유지보수가 어렵지 않다.
  • Automatic vendor prefixing
    • 개별 컴포넌트마다 기존의 CSS 를 이용하여 스타일 속성을 정의하면 될 뿐 이외의 것들은 Styled Component 가 알아서 처리해준다.

설치

# with npm
$ npm install --save styled-components

# with yarn 
$ yarn add styled-components

권장사항

{
 "resolutions": {
   "styled-components": "^5"
 }
}
  • Styled Component에서 package.json에 다음 코드를 추가하도록 권장한다.
  • 여러 버전의 Styled Component가 설치되어 발생하는 문제를 줄여준다.

사용법

Getting Started

  • Styled Component 는 tagged template literals 라는 ES6 문법을 이용한다.
  • 컴포넌트를 만들 때에 해당 문법을 사용하여 컴포넌트의 스타일 속성을 정의하면 별도의 CSS 파일 없이도 스타일 속성을 지닌 컴포넌트를 만들 수 있다.
import styled from "styled-components";

// <h1> 태그를 렌더링 할 title component를 만듭니다.
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// <section> 태그를 렌더링 할 Wrapper component를 만듭니다.
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;
- 같은 스타일 속성을 지닌 여러개의 컴포넌트들 중 몇 개의 컴포넌트에는 약간의 변화를 주고 싶은 경우

export default function App() {
  // 일반적으로 컴포넌트를 사용하는 것처럼 Title과 Wrapper를 사용하시면 됩니다!
  return (
    <Wrapper>
      <Title>Hello World!</Title>
    </Wrapper>
  );
}

Adapting based on props & Extending Styles

  • 스타일 속성을 지닌 컴포넌트를 정의할 때에 함수를 전달하고, 그 함수 안에서 props 를 사용할 수도 있다.
  • 같은 스타일 속성을 지닌 여러개의 컴포넌트들 중 몇 개의 컴포넌트에는 약간의 변화를 줄 수 있다.
import styled from "styled-components";

const Button = styled.button`
  /* Adapt the colors based on primary prop */
  background: ${(props) => (props.primary ? "palevioletred" : "white")};
  color: ${(props) => (props.primary ? "white" : "palevioletred")};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;
/* change part of Button style */
const Tomato = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

export default function App() {
  return (
    <div className="App">
      <Button>Normal</Button>
      <Button primary>Primary</Button>
      <Tomato>Tomato</Tomato>
    </div>
  );
}

Passed props

  • 컴포넌트에 props 로 스타일 속성이 전달된다면 해당 컴포넌트는 props 로 전달된 속성을 우선 적용하며, 전달되는 속성이 없다면 기본으로 설정된 속성을 적용한다.
import styled from "styled-components";

// Styled Component로 만들어진 Input 컴포넌트 입니다.
const Input = styled.input`
  padding: 0.5em;
  margin: 0.5em;
  color: ${(props) => props.inputColor || "red"};
  background: papayawhip;
  border: none;
  border-radius: 3px;
`;

export default function App() {
  return (
    <div>
      {/* 아래 Input 컴포넌트는 styled component인 Input 컴포넌트에 지정된 inputColor(red)가 적용되었습니다.  */}
      <Input defaultValue="김코딩" type="text" />
      {/* 아래 Input 컴포넌트는 props로 전달된 커스텀 inputColor(blue)가 적용되었습니다. */}
      <Input defaultValue="박해커" type="text" inputColor="blue" />
    </div>
  );
}

Advanced

Styled Component의 정의는 render 메소드 밖에 정의
Styled Component 의 정의는 리턴문 밖에서 이루어져야 한다.
만약 Styled Component 가 리턴문 안에서 정의되면 컴포넌트가 리렌더링 될 때마다 스타일 속성을 지닌 컴포넌트가 매번 새로 정의되고, 이는 렌더링 속도 저하에 큰 영향을 끼친다.

/* Best Practice */

const StyledWrapper = styled.div`
  /* ... */
`Pseudoelements, pseudoselectors, and nesting
const Wrapper = ({ message }) => {
  return <StyledWrapper>{message}</StyledWrapper>
}

/* Bad Practice */

const Wrapper = ({ message }) => {
  // WARNING: THIS IS VERY VERY BAD AND SLOW, DO NOT DO THIS!!!
  const StyledWrapper = styled.div`
    /* ... */
  `

  return <StyledWrapper>{message}</StyledWrapper>
}

Pseudoelements, pseudoselectors, and nesting
Styled Component 는 중첩 스타일링을 위해 SCSS 와 같은 전처리 기능을 자동으로 지원한다.

  • 중첩 스타일링을 하기 위해서는 ampersand(&) 기호를 사용한다.
  • ampersand(&) 를 사용하지 않으면 평범한 후손 셀렉터처럼 동작한다.
  • ampersand(&)는 순수 CSS 와 Styled Component 를 혼용해서 사용해야 하는 경우에 각 스타일 간의 충돌을 피하는 데에 유용하게 사용된다.

TailWind CSS

공식사이트
Tailwind Cheat sheet

  • Utility-first CSS로서 각 class가 담당할 스타일을 미리 정의하고 필요한 class들을 조합해서 적용하는 식으로 사용하는 방법

디자인 시스템

  • 디자인 시스템 : 서비스를 만드는 데 사용한 공통 컬러, 서체, 인터랙션, 각종 정책 및 규정에 관한 모든 컴포넌트를 정리해놓은 것이며 불필요한 커뮤니케이션을 없애기 위해 체계적으로 정리한 시스템
  • 재사용이 가능한 UI 컴포넌트들로 이루어져, 복잡하고 견고하며 사용자가 접근하기에 용이한 사용자 인터페이스를 구축할 수 있다.
  • 디자이너와 개발자 모두 UI 컴포넌트를 다루기 때문에, 디자인 시스템은 두 분야를 연결하는 다리이다.

디자인 시스템을 도입하는 많은 기업들

Airbnb와 같은 거대 기술 기업부터 신생 스타트업까지, 회사들은 시간과 비용을 절약하기 위해 UI 패턴을 재사용하는 방식을 도입하고 있다.

재사용이 가능한 사용자 인터페이스는 새로운 개념이 아니다. 스타일 가이드, UI 키트 및 공유 가능한 위젯은 수십 년 동안 존재왔고, 오늘날 디자이너와 개발자들은 UI 컴포넌트 구조화를 위해 노력하고 있다. UI 컴포넌트는 사용자 인터페이스를 이루는 조각들의 시각적이고 기능적인 속성을 캡슐화한다.

최근에 등장한 유저 인터페이스(UI)들은 다양한 사용자 경험을 제공하기 위해 수백 개의 모듈식 UI 컴포넌트가 재배열된 구조로 이루어져 있다.



useRef

  • React로 모든 개발 요구 사항을 충족할 수는 없는 경우
    DOM 엘리먼트의 주소값을 활용해야 하는 경우:
    • focus
    • text selection
    • media playback
    • 에니메이션 적용
    • d3.js, greensock 등 DOM 기반 라이브러리 활용
  • 예외적인 상황에서 React는 useRef으로 DOM 노드, 엘리먼트, 그리고 리액트 컴포넌트 주소값을 참조할 수 있다.
  • 제시된 상황 제외한 대부분의 경우 기본 리액트 문법을 벗어나 useRef를 남용하는 것은 부적절하다.
  • React의 특징이자 장점인 선언적 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야 한다.

사용방법

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>
    </>
  );
}

예시

좋은 웹페이지 즐겨찾기