react - 컴포넌트 디자인

2021. 10. 28. 22:34·SE Bootcamp 내용 정리

학습 목표

* 컴포넌트 기반 bottom-up 방식 개발에 대한 이해

* storybook 활용: 컴포넌트 UI 개발에 도움을 주는 라이브러리

* 구조적으로 CSS를 작성하는 방법

* styled-Component 활용: 컴포넌트 기반 CSS 작성에 도움을 주는 라이브러리

* useRef Hook 활용: DOM Reference를 활용하기 위한 hook

컴포넌트 단위로 개발하기

Component Driven Development(CDD)

부품 단위로 UI 컴포넌트를 만들어 나가는 개발 방식(레고처럼)
→ 컴포넌트 생성 → 컴포넌트 결합 → 페이지 조립

 

재사용할 수 있는 UI 컴포넌트를 활용하는 방식이다

컴포넌트 UI 개발을 위한 Storybook

Component Driven Development를 지원하는 도구 중 하나인 Component Explorer(컴포넌트 탐색기)
→ 컴포넌트 탐색기에 존재하는 개발 도구 중 하나가 Storybook 이다

 

Storybook 이란?

UI 개발(Component Driven Development)를 하기 위한 도구
→ 각각의 컴포넌트를 따로 볼 수 있게 구성해 주어, 한번에 하나의 컴포넌트에서 작업할 수 있다
→ 전체 UI를 한눈에 보고 개발할 수 있다

 

UI 컴포넌트의 재사용성을 확대하기 위해 컴포넌트를 문서화, 시각화(자동으로) 하여 시뮬레이션할 수 있는 다양한 테스트 상태를 확인할 수 있다
→ 버그를 사전에 방지할 수 있도록 해준다

 

테스트 및 개발 속도 향상 및 애플리케이션의 의존성을 걱정하지 않고 빌드할 수 있다

왜 Storybook 같은 UI 개발 도구를 사용하는가?

Storybook은 기본적으로 독립적인 개발 환경에서 실행됨
→ 애플리케이션의 다양한 상황에 구애받지 않고 UI 컴포넌트만 집중적으로 개발할 수 있다

StoryBook의 주요 기능

* UI 컴포넌트들을 카탈로그화

* 컴포넌트 변화를 Stories로 저장

* 핫 모듈 재로딩과 같은 개발 툴 경험을 제공

* 리액트를 포함한 다양한 뷰 레이어 지원

Storybook 설치 및 세팅

# clone the template(템플릿 클론 받기)
npx degit chromaui/intro-storybook-react-template taskbox

cd taskbox

# Install dependencies(의존성 설치)
yarn
// yarn 명령어를 찾을 수 없다면 npm install -g yarn 부터 실행

 

Storybook hands-on(실습)

src> stories > Button.stories.js 파일 코드의 예


import React from "react";
import { Button } from "@storybook/react/demo";

export default {
  title: "Button",
  component: Button
};

export const Primary = () => (
  <Button>Hello Button</Button>
);

export const Secondary = () => (
 <Button>Bye Button</Button>
);

// Button 컴포넌트를 import 해 와서, 그 컴포넌트를 custom하여  Primary 컴포넌트와 Secondary 컴포넌트를 생성
// 이 2개의 function 컴포넌트들을 export 하면 stroybook 에서 UI 목업을 확인할 수 있다

CSS in JS 방법론

구조적인 CSS 작성 방법의 발전

구조화된 CSS가 필요하게 된 이유

프로젝트의 규모와 복잡도가 점점 커지고 작업 팀원 수도 많아지면서 CSS에 대한 일관된 패턴의 필요성이 생김.
또한, 모바일과 태블릿 등 다양한 디바이스가 등장하면서 웹 사이트들이 다양한 디스플레이를 커버해야 하므로, CSS가 더욱 복잡해짐

 

→ 효율적인 CSS 작업을 위한 구조화된 CSS의 필요성이 대두

CSS 구조화를 위한 다양한 시도

CSS 전처리기의 등장

위와 같은 문제점을 해결하기 위해 CSS 전처리기(CSS Preprocessor)라는 개념이 등장
→ CSS가 구조적으로 작성될 수 있게 도와주는 도구

 

CSS문서를 작성할 때 생기는 반복적인 작업, 번거로운 작업 등을 프로그래밍 개념(변수, 함수, 상속 등)을 활용하여 해결할 수 있게 도와주는 도구이다

 

대신, 이 CSS 전처리기 자체만으로는 웹 서버가 인지하지 못하므로, 각 CSS 전처리기에 맞는 컴파일러를 사용해야 하고, 컴파일을 하면 우리가 사용할 수 있는 CSS 문서로 변환되는 구조

 

→ 이를 통해 CSS 파일을 구조화 및 CSS파일을 몇개의 작은 파일로 분리하는 방법이 가능해짐

SASS(Syntactically Awesome Style Sheets)

CSS 전처리기 중 가장 유명한 도구
→ CSS를 확장해 주는 스크립팅 언어; CSS를 만들어주는 언어

 

자바스크립트 비스무리하게 사용하는 형태라 보면 된다
→SASS는 SCSS 코드를 읽어서 전처리한 다음 컴파일해서 전역 CSS 번들 파일을 만들어주는 전처리기 역할을 수행

 

그러나, SASS의 장점(구조화)보다 단점이 더 많다는 한계가 존재
→ 전처리기 내부에서 어떤 작업이 이루어지는지는 모르고, 컴파일된 CSS의 용량만 비대해지는 문제

 

CSS 방법론의 대두

CSS 전처리기의 문제를 보완하기 위해 등장한 방법론
→ 대표적으로 BEM, OOCSS, SMACSS 방법론이 있음

CSS 방법론의 공통 지향점

* 코드의 재사용

* 코드의 간결화(유지보수 용이)

* 코드의 확장성

* 코드의 예측성(클래스 명으로 의미 예측)

팀원들이 협업하는 상황에서 CSS 방법론을 규칙으로 정해두는 것이 좋다

BEM

Block, Element, Modifier 로 구분하여 클래스 명을 작성하는 방법
→ Block과 Element 사이는 __(언더바 2개), Element와 Modifier 사이는 --(바 2개)로 구분한다.

 


Block        Element    Modifier
.header__navigation--navi-text {
    color: red;
}
// Block: 전체를 감싸고 있는 블럭 요소
// Element: 블럭이 포함하고 있는 한 조각
// Modifier: 블럭 또는 요소의 특성(블록이나 엘리먼트의 외관/상태를 변하게 하는 부분)

이러한 클래스 명 작성은 BEM 방식의 이름을 여러 번 반복하여 재사용 가능하게 하며, HTML/CSS/SASS 파일에서도 더 일관된 코딩 구조를 만들어 줌

 

그러나, 이러한 방법론에도 문제점이 존재!
→ 클래스명이 장황해짐, 또 그에 따라 마크업도 불필요하게 커짐, 재사용시 마다 모든 UI 컴포넌트를 명시적으로 확장해야 함

 

또한, SASS(전처리기)와 BEM(방법론) 둘다 캡슐화를 이룰 수 없다는 문제가 있음
→ 개발자들이 유일한 클래스 명을 써야 한다는 단점!

CSS-in-JS의 등장: Styled-Component

애플리케이션 개발이 많아지면서, 컴포넌트 단위 개발에서 캡슐화 가 중요해짐
→ CSS는 컴포넌트 기반 방식을 따로 지원하지 않음
→ CSS를 컴포넌트의 영역으로 불러들이기 위해 CSS-in-JS가 등장함

 

대표적인 CSS-in-JS: Styled-Component
→ 컴포넌트들로부터 UI를 완전히 분리해 사용할 수 있는 단순한 패턴을 제공

CSS 방법론들의 특징, 장단점 비교


* CSS
- 특징: 기본적인 스타일링 방법
- 장점: 없음
- 단점: 일관된 패턴 찾기가 어려움, !important의 남용

* SASS(전처리기)
- 특징: 프로그래밍 방법론을 도입해 컴파일된 CSS를 만들어 내는 전처리기
- 장점: 변수/함수/상속 개념을 활용해 컴포넌트의 재사용 가능, CSS를 구조화
- 단점: 전처리 과정이 필요, 디버깅의 어려움, 컴파일된 CSS파일이 커짐(용량)

* BEM 방법론
- 특징: CSS 클래스 명 작성에 일관된 패턴을 강제하는 방법론
- 장점: 클래스 명 네이밍으로 문제 해결, 전처리 과정이 불필요
- 단점: 선택자의 이름이 장황해지고, 클래스의 목록이 너무 많아짐

* Styled-Component(CSS-in-JS)
- 특징: 컴포넌트 기반으로 CSS를 작성할 수 있게 도와주는 라이브러리
- 장점: CSS를 컴포넌트 안으로 캡슐화, 네이밍이나 최적화를 신경 안 써도 됨
- 단점: 빠른 페이지 로드에 불리함

 

Styled-Component: 컴포넌트 기반 CSS 작성에 적합한 라이브러리

CSS-in-JS 관련 react 라이브러리 중 하나
→ Styled-Component를 사용하면 기존 CSS 문법으로도 스타일 속성이 추가된 react 컴포넌트 작성이 가능

Styled-Component 를 사용하여 버튼을 만드는 예시

const Button = styled.a`
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
`;

// 변수 선언하듯이 Button을 만들고, tag의 속성을 정의(여기서는 `a` 태그)
// 백틱(` `)안에 기존의 CSS 문법을 이용하여 스타일 속성을 정의

 

Sytled-Component의 특징

* Automatic critical CSS
화면에 렌더링된 컴포넌트를 추적해서 그에 대한 스타일을 자동으로 삽입해 줌

* No class name bugs
스스로 유니크한 className을 생성해 줌
→ className 관련한 버그를 줄여 준다

* Easier deletion of CSS
모든 스타일 속성이 특정 컴포넌트와 연결되어 있으므로, 컴포넌트를 사용하지 않아 삭제하는 경우,
이에 따른 스타일 속성(CSS)도 함께 삭제됨

* Simple dynamic styling
className을 수동으로 관리할 필요가 없이, react의 props나 전역 속성을 기반으로 
컴포넌트에 스타일링을 부여하므로 훨씬 간단하고 직관적인 스타일링 가능

* Painless maintenance
컴포넌트에 스타일을 상속하는 속성을 찾아 CSS 파일 검색할 필요가 없다
→ 코드의 크기가 커져도 유지보수에 문제없음!

* Automatic vendor prefixing
개별 컴포넌트마다 기존의 CSS를 이용하여 스타일 속성을 정의하면 된다
→ 이외에 것들은 Styled Component가 알아서 처리해 줌

설치하기(Installation)

터미널에서 다음 코드로 Styled Component 라이브러리를 설치

// 해당 폴더에서 종속 목록으로 설치하고
# with npm(npm 설치된 상태에서)
$ npm install --save styled-components

// yarn에서도 추가해 주자
# with yarn(yarn 설치된 상태에서 경로: /사용자/taskbox/ ) 
$ yarn add styled-components

Styled Component에서는 package.json에 다음 코드를 추가하는 것을 권장함
이는 여러 버전의 Styled Component가 설치되어 발생하는 문제를 줄여 준다

//package.json 파일에 코드 추가하기

{
  "resolutions": {
    "styled-components": "^5"
  }
}

시작하기

Styled Component는 tagged template literals(템플릿 리터럴) 이라는 ES6 문법을 사용한다
→ 이 문법을 사용해 컴포넌트의 스타일 속성을 정의하면 별도의 CSS 파일 없이도 스타일 속성을 가진 컴포넌트를 생성할 수 있다

 

→ index.css 와 같은 css 파일을 생성하지 않고도 그때 그때 필요한 만큼만 css를 컴포넌트에 적용하는 것과 비슷하다


import styled from "styled-components";

// <h1> 태그를 렌더링 할 Title component을 선언(생성)
const Title = styled.h1`    // styled.태그명`    css 속성    `
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>
);
}

h1 태그의 스타일 속성은 styled.h1을, section 태그의 스타일 속성은 styled.section을 사용하는 구조
→ 위 코드는 스타일을 정의함과 동시에 해당 스타일을 가진 컴포넌트를 만들 수 있는 형태이다

Adapting based on props & Extending Styles

스타일 속성을 지닌 컴포넌트를 정의할 때에 함수를 전달하고, 그 함수 안에서 props를 사용하는 경우

// Button 컴포넌트
// Button 컴포넌트의 background와 color 속성에서 `primary`라는 props의 전달 여부에 따라
// 컬러값을 정의하고 있음(삼항 연산자)

... 생략

  background: ${(props) => (props.primary ? "palevioletred" : "white")};    
  //속성의 값(value)에다가 삼항 연산자를 통해 속성값을 이용
 
  color: ${(props) => (props.primary ? "white" : "palevioletred")};
... 생략

// App component
...생략

  <Button>Normal</Button>
  <Button primary>Primary</Button>

…생략

 

같은 스타일 속성을 지닌 여러개의 컴포넌트 중 일부 컴포넌트에 약간의 변화를 주는 경우

→ 기존 컴포넌트를 styled()로 감싼 후에 변경하고 싶은 속성만 새로 정의(` `)해주면 기존 속성을 확장하여 사용할 수 있다(상속 느낌처럼?)

// 기존의 Button 컴포넌트에 Tomato 컴포넌트만을 위한 새로운 속성 추가
const Tomato = styled(Button)`        // styled(컴포넌트명)`    css 속성    `
  color: tomato;
  border-color: tomato;
`;

Passed props

컴포넌트에 props로 스타일 속성이 전달되는 경우
→ 해당 컴포넌트는 props로 전달된 속성을 우선 적용한다. 그렇지 않은 경우(전달되지 않은 경우), 기본으로 설정된 속성을 적용한다

 

아래는 props로 속성이 전달된 경우 그 속성이 적용되고 그렇지 않은 경우 설정한 기본값이 적용되는 예제

// `Input` 컴포넌트의 color 속성에서 `inputColor=blue`가 props로 전달된 경우 
//해당 속성값(value)가 color에 적용되고, 그렇지 않은 경우(||) 기본 color 값인 red가 적용

// tip: {} 안에서 속성값 같은 것을 설정할 때는 “ “(따옴표) 안에 그 값을 넣어줘야 된다!

import styled from "styled-components";

// Styled Component로 만들어진 Input 컴포넌트 
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: ${(props) => props.inputColor || "red"};    // props.inputColor의 전달 여부
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>
);
}

DOM reference를 잘 활용할 수 있는 useRef

react로 많은 프론트엔드 요구사항을 구현할 수 있지만, 모든 개발 요구사항을 충족할 수는 없다
→ 다음과 같이 DOM 엘리먼트의 주소값을 활용하는 경우와 같은 예외적인 상황 존재

//DOM 엘리먼트의 주소값의 활용이 필요한 경우

* focus
* text selection
* media playback
* 애니메이션 적용
* d3.js, greensock 등 DOM 기반 라이브러리 활용

이런 예외적인 상황에서 react에서는 useRef으로 DOM 노드, 엘리먼트, 리액트 컴포넌트 주소값을 참조할 수 있다

 

다음과 같이 코드를 작성하는 형태로 가능

const 주소값을_담는_그릇 = useRef(참조자료형)    // note: `참조자료형`을 넣어야 함!
// 이제 `주소값을_담는_그릇` 변수에 어떤 주소값이든 담을 수 있다

... 생략

return (
    <div>
      <input ref={주소값을_담는_그릇} type="text" />
        {/* React에서 사용 가능한 ref라는 속성에 주소값을_담는_그릇을 값으로 할당하면*/}
        {/* 주소값을_담는_그릇 변수에는 input DOM 엘리먼트의 주소가 담긴다 */}
        {/* 향후 다른 컴포넌트에서 input DOM 엘리먼트를 활용할 수 있다 */}
    </div>
  );

 

이 주소값은 컴포넌트가 재렌더링(re-render)되더라도 바뀌지 않는다
→ 이 특성을 이용하여 제한된 상황에서 useRef 를 활용할 수 있다

// 특정 상황(제한된 상황)에서 useRef를 활용하는 예시

function TextInputWithFocusButton() {
  const inputEl = useRef(null);        // 그냥 값이 없다(null)라는 뜻 undefined랑은 다름!
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useRef를 남용하는 것은 부적절하고, react의 특징인 선언적 프로그래밍의 원칙과 배치되므로 조심해서 사용해야 한다
→ 기본적으로 위의 예외 상황을 제외하고는 대부분의 경우 기본 리액트 문법으로 가능하다

 

focus 연습하기

focus()는 말 그대로 포커스를 주는 것이다
아래 예제 코드로 테스트해보자

import React, { useRef } from "react";

const Focus = () => {
const firstRef = useRef(null);    //주소값을 담는 변수 생성
const secondRef = useRef(null);
const thirdRef = useRef(null);

let str = "";    // 입력값을 담을 변수 생성
const handleInput = (event) => {
// console.log(event.key, event);
if (event.key !== "Enter" && event.key !== "Backspace") {
str += event.key;    // 엔터키와 백스페이스 아닌 경우에만 값이 저장되도록 함
}
if (event.key === "Backspace") {
str = str.slice(0, str.length – 1);        
// 백스페이스 누르면 바로 전 입력값이 지워지도록
}
console.log(str);
if (event.key === "Enter") {    //엔터키 눌렀을때
if (str === "hello" && event.target === firstRef.current) {
//현재 타겟이 firstRef가 맞는지?

secondRef.current.focus();    // secondRef로 현재 포커스를 옮김
event.target.value = "";
} else if (str === "world" && event.target === secondRef.current) {
thirdRef.current.focus();
event.target.value = "";
} else if (str === "codingtest" && event.target === thirdRef.current) {
firstRef.current.focus();
event.target.value = "";
} else {
return;
}
str = "";
}
};

return (
<div>
<h1>타자연습</h1>
<h3>각 단어를 바르게 입력하고 엔터를 누르세요.</h3>
<div>
<label>hello </label>
<input ref={firstRef} onKeyUp={handleInput} type="text" />
{/* ref 속성에 firstRef라는 주소값을 담는 변수를 값으로 할당함*/}
</div>
<div>
<label>world </label>
<input ref={secondRef} onKeyUp={handleInput} />
</div>
<div>
<label>codingtest </label>
<input ref={thirdRef} onKeyUp={handleInput} />
</div>
</div>
);
};

export default Focus;

media playback 연습하기

play()와 pause() 를 이용하여 현재 참조주소값에서 media를 플레이, 멈춤하도록 하는 예시

import { useRef } from "react";

export default function App() {
const videoRef = useRef(null); // 주소값을 담을 변수

const playVideo = () => {//플레이 버튼 클릭시 작동되는 함수
videoRef.current.play(); 
//ref 속성에 지정한 videoRef를 활용하여 media를 플레이
console.log(videoRef.current);
};

const pauseVideo = () => {// 멈춤 버튼 클릭시 작동
videoRef.current.pause();
//ref 속성에 지정한 videoRef를 활용하여 media를 멈춤
// videoRef.current.remove();
// .remove() 을 쓰면 출력되는 media가 삭제?되는 듯하다
console.log(videoRef.current);
};

return (
<div className="App">
<div>
<button onClick={playVideo}>Play</button>
<button onClick={pauseVideo}>Pause</button>
</div>
{/* video 엘리먼트(태그?)에서 ref 속성을 지정*/}
<video ref={videoRef} width="320" height="240" controls>
<source
type="video/mp4"
src="https://b01-kr-naver-vod.pstatic.net/music/c/read/v2/VOD_ALPHA/musicvideo_2021_10_18_0/c699346e-2fe8-11ec-a051-48df37269ee2.mp4?_lsu_sa_=69a5b3f7a1716196cfdc05dc60d52bba0ee335787a010fa73d67c7caf71937f5e6233aac6885b305c0f235b25f350b8761518ffa00cba7d716b7899a056e9cc2181b3f7132c6bfab3586a3263eb80618"

/>
</video>
</div>
);
}

동영상 소스 따는 방법
해당 동영상을 켠 페이지에서 개발자 도구(f12)의 Elements 탭을 킨 상태에서 동영상 클릭 후 shift+ctrl+c 누르거나 화면 상단의 화살표 모양(Elements 탭의 왼쪽 근처에 있는)을 누르면 현재 활성화된 엘리먼트로 이동한다는 점!

저작자표시 (새창열림)

'SE Bootcamp 내용 정리' 카테고리의 다른 글

react - 상태 관리 2(Redux 연습, 공식 문서 탐구)  (0) 2021.11.02
react - 상태 관리 1(기본 상태 관리, Redux)  (0) 2021.11.02
Web Server - 서버 만들기 연습  (0) 2021.10.28
Web Server - 기초 / express 활용하기  (0) 2021.10.27
react - 데이터 흐름의 이해와 비동기 요청 처리 보충 내용  (0) 2021.10.21
'SE Bootcamp 내용 정리' 카테고리의 다른 글
  • react - 상태 관리 2(Redux 연습, 공식 문서 탐구)
  • react - 상태 관리 1(기본 상태 관리, Redux)
  • Web Server - 서버 만들기 연습
  • Web Server - 기초 / express 활용하기
레실이
레실이
  • 레실이
    레실이의 티스토리
    레실이
  • 전체
    오늘
    어제
    • 분류 전체보기 (87) N
      • SE Bootcamp 내용 정리 (63)
      • 알고리즘 연습 (7)
      • Project 주저리 (4)
      • 기술 면접 source (3)
      • 개발 일상 (8) N
      • 생성 AI 활용 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    CORS
    react
    Python
    JS
    Ajax
    알고리즘
    데이터베이스
    자료구조
    인증/보안
    ORM
    state
    PickAndDrink
    문자열
    ubuntu
    중복 순열
    cmarket
    promise
    useState
    객체
    mongoDB
    CSR
    CSS
    Linux
    node.js
    MVC
    node
    DOM
    JavaScript
    IT
    useRef
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
레실이
react - 컴포넌트 디자인
상단으로

티스토리툴바