학습 목표
* 컴포넌트 기반 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 |