on
react - 컴포넌트 디자인
react - 컴포넌트 디자인
학습 목표
* 컴포넌트 기반 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 = () => ( Hello Button ); export const Secondary = () => ( Bye 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"; // 태그를 렌더링 할 Title component을 선언(생성) const Title = styled.h1` // styled.태그명` css 속성 ` font-size: 1.5em; text-align: center; color: palevioletred; `; // 태그를 렌더링 할 Wrapper component를 선언(생성) const Wrapper = styled.section` padding: 4em; background: papayawhip; `; export default function App() { // 일반적으로 컴포넌트 사용하듯이 Title과 Wrapper를 사용 return ( Hello World! ); }
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 ...생략 Normal Primary …생략
같은 스타일 속성을 지닌 여러개의 컴포넌트 중 일부 컴포넌트에 약간의 변화를 주는 경우
→ 기존 컴포넌트를 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 ( {/* 아래 Input 컴포넌트는 styled component인 Input 컴포넌트에 지정된 inputColor(red)가 적용*/} {/* 아래 Input 컴포넌트는 props로 전달된 커스텀 inputColor(blue)가 적용*/} ); }
DOM reference를 잘 활용할 수 있는 useRef
react로 많은 프론트엔드 요구사항을 구현할 수 있지만, 모든 개발 요구사항을 충족할 수는 없다
→ 다음과 같이 DOM 엘리먼트의 주소값을 활용하는 경우와 같은 예외적인 상황 존재
//DOM 엘리먼트의 주소값의 활용이 필요한 경우 * focus * text selection * media playback * 애니메이션 적용 * d3.js, greensock 등 DOM 기반 라이브러리 활용
이런 예외적인 상황에서 react에서는 useRef으로 DOM 노드, 엘리먼트, 리액트 컴포넌트 주소값을 참조할 수 있다
다음과 같이 코드를 작성하는 형태로 가능
const 주소값을_담는_그릇 = useRef(참조자료형) // note: `참조자료형`을 넣어야 함! // 이제 `주소값을_담는_그릇` 변수에 어떤 주소값이든 담을 수 있다 ... 생략 return ( {/* React에서 사용 가능한 ref라는 속성에 주소값을_담는_그릇을 값으로 할당하면*/} {/* 주소값을_담는_그릇 변수에는 input DOM 엘리먼트의 주소가 담긴다 */} {/* 향후 다른 컴포넌트에서 input DOM 엘리먼트를 활용할 수 있다 */} );
이 주소값 은 컴포넌트가 재렌더링(re-render)되더라도 바뀌지 않는다
→ 이 특성을 이용하여 제한된 상황에서 useRef 를 활용할 수 있다
// 특정 상황(제한된 상황)에서 useRef를 활용하는 예시 function TextInputWithFocusButton() { const inputEl = useRef(null); // 그냥 값이 없다(null)라는 뜻 undefined랑은 다름! const onButtonClick = () => { inputEl.current.focus(); }; return ( <> Focus the input ); }
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 ( 타자연습 각 단어를 바르게 입력하고 엔터를 누르세요. hello {/* ref 속성에 firstRef라는 주소값을 담는 변수를 값으로 할당함*/} world codingtest ); }; 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 ( Play Pause {/* video 엘리먼트(태그?)에서 ref 속성을 지정*/} ); }
동영상 소스 따는 방법
해당 동영상을 켠 페이지에서 개발자 도구(f12)의 Elements 탭을 킨 상태에서 동영상 클릭 후 shift+ctrl+c 누르거나 화면 상단의 화살표 모양(Elements 탭의 왼쪽 근처에 있는)을 누르면 현재 활성화된 엘리먼트로 이동한다는 점!
from http://lesil.tistory.com/58 by ccl(A) rewrite - 2021-10-28 23:01:43