혹시나 잘못된 개념 전달이 있다면 댓글 부탁드립니다. 저의 성장의 도움이 됩니다

오늘의 Checkpoint

CCD

Component-Driven Development
컴포넌트 중심 개발로 조립/재사용할 수 있는 UI 컴포넌트를 만드는 개발 방식
-> CSS도 컴포넌트 방식으로 구성

  • 상향식 개발

CSS in JS

CSS를 컴포넌트 기반 개발 장식에 맞게 JS로 컨트롤 하는 기술
ex.Styled-Components, Emotion, JSS 등


CSS 구조화 비교

대규모 프로젝트, 협업, 디바이스에 따른 화면 구현(반응형 웹) 등으로 일관된 작성 패턴이 없는 기존의 CSS 작성방법으로는 비효율이 발생하여 다양한 방식으로 CSS를 구조화하는 방법이 등장
CSS 방법론의 특징과 장, 단점

  • CSS 전처리기( CSS Preprocessor )
    : 프로그래밍 언어처럼 변수 등을 활용할 수 있지만 별도의 컴파일 과정을 거쳐야함
    이외
    ex. Sass(CSS 확장 스크립팅 언어), Less, stylus 등

    // Sass를 이용하여 변수처럼 사용 
    // -> $변수명
    $base-color: rgba(198, 83, 140, 0.5);
    
    .alert{ border: 1px solid $base-color }
    .button{ color: $base-color }
  • CSS 방법론
    : 4가지 특징을 지향하며 CSS 작성에 규칙을 두는 방식
    ex. BEM, OOCSS, SMACSS 등

    • 코드 재사용
    • 쉬운 유지/보수(간결화)
    • 확장 가능
    • 클래스명으로 의미 예측
      // BEM : 클래스 이름을 3가지 포함하여 네이밍하는 방법론
      // 		- Block : 전체를 감싸고 있는 블럭 요소
      // 		- Element : 블럭이 포함하고 있는 한 조각
      // 		- Modifier : 블럭 또는 요소의 속성(블록이나 엘리먼트의 외관, 상태를 변하게 하는 부분)
      //         * Block, Element, Modifier 각각은 —와 __로 구분
      // -> 장황한 클래스명과 재사용성에 비효율 발생
      .header__navigation--navi-text{
      	color: violet;
      }

cf. SASS, BEM에서는 캡슐화 개념을 포함하지 못함
cf. 캡슐화 : 객체의 속성과 행위를 하나로 묶고, 구현 내용을 감추는 것


Styled-Components

컴포넌트 기반으로 CSS를 다루는 CSS-in-JS의 대표적인 React 라이브러리
-> CDD 개발 도구 중 하나

  • CSS 컴포넌트화

react+styled-components


라이브러리 설치

// npm
npm install --save styled-components

// yarn
yarn add styled-components


// package.json에 코드 추가를 권장 -> 버전을 지정
{
  "resolutions": {
    "styled-components": "^5"
  }
}
// JS파일에서 import 후 사용 가능
import styled from "styled-components";

문법 1. 컴포넌트 생성

import styled from "styled-components"

const 컴포넌트이름 = styled.태그` 
	css속성1: 속성값;
	css속성2: 속성값;
`;

// 백틱 사용
// ES6의 Templete Literals 문법 사용

문법 2. 컴포넌트 재사용

const 컴포넌트이름 = styled(재사용할컴포넌트이름)` 
	새로추가css속성1: 속성값;
	새로추가css속성2: 속성값;
`;

문법 3. 전역 스타일 설정

import 적용 후, createGlobalStyle 사용
cf. 열고닫는 컴포넌트가 있는 경우, 사이에 자식 컴포넌트가 있으면 오류가 발생하므로 주의

선언한 전역 스타일 컴포넌트의 위치
"Place it at the top of your React tree and the global styles will be injected when the component is "rendered".
공식문서

import { createGlobalStyle } from "styled-components";

const 컴포넌트이름 = createGlobalStyle` 
	CSS선택자{
		속성1: 속성값;
		속성2: 속성값;
	}
`;
// 전역스타일 컴포넌트의 백틱 안에는 CSS 작성하듯 입력

문법 1~3번 예시

import styled from 'styled-components';
import {createGlobalStyle} from 'styled-components';

// 전역 스타일 컴포넌트
const Global = createGlobalStyle`
    body{
      background-color : rgba(65, 63, 140, 1); 
    }
    button{
      font-size : 30px;
    }
`;

const PurpleButton = styled.button`
    background-color: rgba(154, 77, 255, 0.8);
    color: white;
    padding: 10px 15px;
    border: none;
    border-radius : 30px; 
`;

// 상속받은 컴포넌트 -> 속성 추가 or 오버라이딩
const BigPurpleButton = styled(PurpleButton)`
    padding: 15px 25px;  /* 오버라이딩 */
    margin-top: 20px; 
    border : 3px rgba(140, 237, 154, 1) solid;
`


const App = ()=>{
  return (
      <div>
        <Global />
        <PurpleButton>click me</PurpleButton>
        <br />
        <BigPurpleButton>click me!!</BigPurpleButton>
      </div>
  )};

export default App;

문법 1~3번 예시


문법 4. 컴포넌트 Props 적용

const 컴포넌트이름 = styled.태그` 
	css속성: ${(props)=> 함수코드};
`;
// ES6의 Templete Literals 문법의 ${ } 사용

props 조건부 렌더링

import styled from "styled-components";

//받아온 props에 따라 조건부 렌더링
const Button = styled.button`
  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;
`;

export default function App() {
  return (
    <div>
      <Button>Button</Button>
      <Button primary >Primary Button</Button>
      <br />
      <Button primary={false} >false Button</Button>
      <Button primary={true} >true Button</Button>
    </div>
  );
}

props 조건부 렌더링


props값으로 렌더링

  • 삼항연산자
  • A || B : A가 falsey한 값일때, B 적용
import styled from "styled-components";
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  button {
    font-size: 1em;
    padding : 5px;
    margin : 2px;
    border-radius : 5px;
  }
`;

// case 1
const Button1 = styled.button`
  background: ${(props) => (props.color ? props.color : "white")};
`;

// case 2
const Button2 = styled.button`
  background: ${(props) => props.color || "white"};
`;

export default function App() {
  return (
    <div>
      <GlobalStyle />
      <Button1>Button1</Button1>
      <Button1 color="orange">orange</Button1>
      <br />
      <Button2>Button2</Button2>
      <Button2 color="turquoise">turquoise</Button2>
    </div>
  );
}

props 값 적용 예시


hover 상태

hover 적용 예시

// Styled-Components 방식으로 리펙토링

/* styles.css 내용
* {
  margin: 0.5rem;
}

#practice {
  padding: 1rem;
  font-size: 2rem;
  background: powderblue;
  border-radius: 1rem;
  transition: 0.5s;
}

#practice:hover {
  background: cornflowerblue;
  color: white;
  transition: 0.5s;
}
*/

// import "./styles.css";
import styled, {createGlobalStyle} from 'styled-components'

const Global = createGlobalStyle`
    *{ margin: 0.5rem; }
`;
const Button = styled.button`
    padding: 1rem;
    font-size: 2rem;
    background: powderblue;
    border-radius: 1rem;
    transition: 0.5s;

    &:hover{
      background: cornflowerblue;
      color: white;
      transition: 0.5s;
  }
`;

export default function App() {
  return (
    <div>
      <Global />
      <Button id='practice'>practice!!</Button>
    </div>
  // return <button id="practice">Practice!</button>;
  )
};



storybook

스토리북 이미지

UI 컴포넌트를 카탈로그처럼 한눈에 볼 수 있고 관리할 수 있는 도구
Component Explorer(컴포넌트 탐색기)의 한 종류로 컴포넌트를 문서화함
-> 독립적인 개발 환경에서 실행하여 컴포넌트에 집중하여 개발 가능
ex. BBC, UN(storybook은 아니지만 예시로 첨부) 의 컴포넌트 모음

주요 기능

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

설치 및 실행

  1. 터미널로 리엑트 환경 폴더 내부에 storybook 설치
    -> storybook 관련 폴더 생성

    npx storybook init

    npx storybook init 처음 화면

    storybook 설치 후 폴더 구조

  2. storybook 실행
    -> localhost:6006 로 조회 가능
    -> 코드로 상태변경을 하지 않아도 GUI로 컴포넌트 변화를 확인 가능
    (/src/stories 폴더 안에 있던, Storybook에서 만들어놓은 예시 스토리 조회 가능)

    npm run storybook

    npm run storybook 실행화면

    localhost:6006


스토리 작성법

  1. /src/stories 에 react 컴포넌트 파일 만들기

    // Title.js 파일
    
    import React from "react";
    
    const Title = ({title, textColor}) => (
    <h1 style={{color: textColor}}>{title}</h1>
    );
    
    export default Title;
  2. 생성한 react 컴포넌트를 사용하는 story로 만들기

    • 컴포넌트 불러오기
    • storybook의 카테고리 설정, Controles 영역 설정
     export default{
         title: "카테고리최상단목록/세부목록",
         component: 스토리로_만들_컴포넌트,
         argTypes:{   //<--- 컴포넌트 전달인자로 Controls영역에서 조절가능
             전달인자1 : 타입(컨트롤_방식),
             전달인자2 : 타입(컨트롤_방식)
         }
     }

    • 템플릿 선언
      -> 전달인자를 props로 전달
    const Template = (args) => <Title {...args} />
    • 템플릿을 사용하여 스토리로 만들어고 내보내기
      • 새 스토리의 전달인자 정의
        -> Storybook에서 조회하려면 export const 키워드를 붙여서 작성
        (여러개 생성 가능)
        cf. 템플릿을 사용하지 않고 전달인자를 직접 받는 스토리도 만들 수 있음

// Title.stories.js 예시

// 컴포넌트 불러오기
import Title from "./Title";

// storybook의 카테고리 설정, Controles 영역 설정
export default {
    title: "Practice/Title", 
    component: Title,
    argTypes: {
        title: { control: "text" },
        textColor: { control: 'color' }
    }
}


// 템플릿 선언
const Template = (args) => <Title {...args} />

// Storybook에 넣어줄 스토리1
export const PurpleTitle = Template.bind({});
PurpleTitle.args= {
    title: "Purple Title",
    textColor: "#7800ff"
}

// Storybook에 넣어줄 스토리2
export const BlueTitle = Template.bind({});
BlueTitle.args= {
    title: "Blue Title",
    textColor: "blue"
}

// 전달인자를 직접 받은 스토리
export const StorybookTitle = (args) =>{
    return <Title {...args} />
}

button 예시

// Button.js 파일

import React from 'react';
import styled from 'styled-components';

const StyledButton = styled.button`
background: ${(props)=> props.background || "white" };
color: ${(props)=> props.color || "white" };
width: ${(props)=> (props.size === "big")? "150px" : "100px" };
height: ${(props)=> (props.size === "big")? "70px" : "40px" };
border-radius: 20px;
border-style: none;
`;

// size는 big, small 선택
export default function Button({ backgroundColor, fontColor, size, text}){
  return <StyledButton background={backgroundColor} color={fontColor} size={size}>{text}</StyledButton>;
};
//Button.stories.js 파일

import Button from './Button';

export default {
  title: 'practice/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control : "color" },
    fontColor:{ control : "color" },
    size: { control : { type : "radio", options : [ "big", "small" ]}},
    text: { control : "text" }
  }
};

export const StorybookButton = (args) =>{
  return <Button {...args}></Button>
};

storybook 버튼 예시 이미지




useRef

직접적인 DOM 조작을 지양하는 React 개발 환경에서 DOM 객체 주소가 필요한 경우 핸들링하기 위해 지정하는 Hook 메서드
: 값이 변경되어도 리렌더링이 발생하지 않고, 리렌더링이 되어도 값을 유지하는 특성(언마운트 되기 전까지)을 이용하여 이벤트에 의해 주소값을 할당하여 사용
-> DOM 노드, 엘리먼트, React 컴포넌트 주소값 참조 가능
cf. state 와 달리 값이 변경되어도 리렌더링을 발생시키지 않음

import { useRef } from "react";

useRef(initialValue);
// cf. 변수에 할당되는 값의 형태는 객체 형태 : { current: initialValue }

DOM 주소값이 필요한 경우

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

제시된 상황 제외한 대부분의 경우 기본 React 문법을 벗어나 useRef 를 남용하는 것은 부적절하고, React의 특징이자 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야 합니다.


사용법

대상 컴포넌트의 DOM 엘리먼트의 주소값을 추출

  1. useRef() 을 할당한 변수 선언

  2. 주소값을 추출할 대상 컴포넌트의 ref속성값으로 1번 변수 적용
    -> 이벤트에 의해 useRef() 의 current 속성값이 DOM 엘리먼트의 주소값으로 재할당

  3. DOM 엘리먼트 조작
    : 객체의 current 속성에 주소값이 담겨있으므로 값을 활용하여 DOM으로 조작하는 효과를 줄 수있음

    const refContainer = useRef(initialValue);
    // cf. 변수에 할당되는 값의 형태는 객체 형태 : { current: initialValue }
    
    return (
        <div>
          <input ref={refContainer} type="text" />
            {/* 이벤트가 발생하면 refContainer.current에 input DOM 엘리먼트의 주소가 담김 */}
            {/* -> 주소가 담긴 refContainer를 활용하여 DOM 조작 가능 */}
        </div>);
import { useRef } from "react";

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
};

media playback 예시

import { useRef } from "react";

export default function App() {
  const videoRef = useRef("초기값");
  // console.log(videoRef.current);

  const playVideo = () => {
    videoRef.current.play(); // 주소값을 활용하여 DOM 조작 가능
    // console.log(videoRef.current);
  };

  const pauseVideo = () => {
    videoRef.current.pause(); // 주소값을 활용하여 DOM 조작 가능
    // videoRef.current.remove();
  };

  return (
    <div className="App">
      <div>
        <button onClick={playVideo}>Play</button>
        <button onClick={pauseVideo}>Pause</button>
      </div>
      <video ref={videoRef} width="320" height="240" controls>
        <source
          type="video/mp4"
          src="https://player.vimeo.com/external/544643152.sd.mp4?s=7dbf132a4774254dde51f4f9baabbd92f6941282&profile_id=165"
        />
      </video>
    </div>
  );
}

console.log로 주소값 재할당 되는 과정 확인


focus 예시

import React, { useRef } from "react";

const Focus = () => {
  const firstRef = useRef(null);
  const secondRef = useRef(null);
  const thirdRef = useRef(null);

  const handleInput = (event) => {
    if (event.key === "Enter") { // enter를 누르면 이벤트 발생
      if (event.target === firstRef.current) {
        secondRef.current.focus(); // DOM 엘리먼트 지정하여 포커스 변경 -> 두번째 칸으로 이동
        event.target.value = "";
      } else if (event.target === secondRef.current) {
        thirdRef.current.focus(); // DOM 엘리먼트 지정하여 포커스 변경 -> 마지막 칸으로 이동
        event.target.value = "";
      } else if (event.target === thirdRef.current) {
        firstRef.current.focus(); // DOM 엘리먼트 지정하여 포커스 변경 -> 첫번째 칸으로 이동
        event.target.value = "";
      } else {
        return;
      }
    }
  };

  return (
    <div>
      <h1>focus test</h1>
      <h3>입력폼에 글자를 입력하고 엔터를 눌러 포커싱되는 위치를 확인해보세요.</h3>
      <div>
        <label>첫번째 입력란 </label>
        <input ref={firstRef} onKeyUp={handleInput} />
      </div>
      <div>
        <label>두번째 입력란 </label>
        <input ref={secondRef} onKeyUp={handleInput} />
      </div>
      <div>
        <label>마지막 입력란 </label>
        <input ref={thirdRef} onKeyUp={handleInput} />
      </div>
    </div>
  );
};

export default Focus;

focus 예시 참고사진



참고

npm으로 styled-components 설치시 주의사항

styled-component 라는 다른 도구가 존재하므로 s를 붙인 styled-components로 설치를 해야함
styled-component
styled-components




오늘의 나

느낀점
오늘 복잡하다고 생각했는데 정리하면서 조금씩 머리속에도 안착되는 것같다. 블로깅에 시간을 많이 쓰지말자라고 다짐한 것과 달리 오늘은 블로깅이 유용했다. 오래 걸리긴 하지만 이해를 위해 이 방법도 나쁘진 않은 것같다.
오히려 오늘 실습, 따라하기가 많았는데 오류도 많아서... props는 꼭 객체인 걸 다시 생각할 기회였다. 그리고 특히 오타 주의하기!! 오타.. 그리고 괄호 조심!

이것과 별개로 헷갈리는 부분은 createGlobalStyle 의 위치. 최상위라는 설명이 예시를 보면 아닌 것 같고. 그렇다고 가장 먼저 사용되게 하는 것도 아닌거 같다. index.js에서 적용한 걸 보면 아래에 있으니까..

최상위라기보다는 가장 처음에 위치하도록 사용하는 것 같음

아직은 이렇게 생각하고 있긴한데, 월요일 세션에 물어봐야겠다.

0개의 댓글