혹시나 잘못된 개념 전달이 있다면 댓글 부탁드립니다. 저의 성장의 도움이 됩니다
Component-Driven Development
컴포넌트 중심 개발로 조립/재사용할 수 있는 UI 컴포넌트를 만드는 개발 방식
-> CSS도 컴포넌트 방식으로 구성
CSS를 컴포넌트 기반 개발 장식에 맞게 JS로 컨트롤 하는 기술
ex.Styled-Components, Emotion, JSS 등
대규모 프로젝트, 협업, 디바이스에 따른 화면 구현(반응형 웹) 등으로 일관된 작성 패턴이 없는 기존의 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. 캡슐화 : 객체의 속성과 행위를 하나로 묶고, 구현 내용을 감추는 것
컴포넌트 기반으로 CSS를 다루는 CSS-in-JS의 대표적인 React 라이브러리
-> CDD 개발 도구 중 하나
// npm
npm install --save styled-components
// yarn
yarn add styled-components
// package.json에 코드 추가를 권장 -> 버전을 지정
{
"resolutions": {
"styled-components": "^5"
}
}
// JS파일에서 import 후 사용 가능
import styled from "styled-components";
import styled from "styled-components"
const 컴포넌트이름 = styled.태그`
css속성1: 속성값;
css속성2: 속성값;
`;
// 백틱 사용
// ES6의 Templete Literals 문법 사용
const 컴포넌트이름 = styled(재사용할컴포넌트이름)`
새로추가css속성1: 속성값;
새로추가css속성2: 속성값;
`;
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 작성하듯 입력
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;
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값으로 렌더링
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>
);
}
// 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>;
)
};
UI 컴포넌트를 카탈로그처럼 한눈에 볼 수 있고 관리할 수 있는 도구
Component Explorer(컴포넌트 탐색기)의 한 종류로 컴포넌트를 문서화함
-> 독립적인 개발 환경에서 실행하여 컴포넌트에 집중하여 개발 가능
ex. BBC, UN(storybook은 아니지만 예시로 첨부) 의 컴포넌트 모음
주요 기능
터미널로 리엑트 환경 폴더 내부에 storybook 설치
-> storybook 관련 폴더 생성
npx storybook init
storybook 실행
-> localhost:6006
로 조회 가능
-> 코드로 상태변경을 하지 않아도 GUI로 컴포넌트 변화를 확인 가능
(/src/stories
폴더 안에 있던, Storybook에서 만들어놓은 예시 스토리 조회 가능)
npm run storybook
/src/stories
에 react 컴포넌트 파일 만들기
// Title.js 파일
import React from "react";
const Title = ({title, textColor}) => (
<h1 style={{color: textColor}}>{title}</h1>
);
export default Title;
생성한 react 컴포넌트를 사용하는 story로 만들기
export default{
title: "카테고리최상단목록/세부목록",
component: 스토리로_만들_컴포넌트,
argTypes:{ //<--- 컴포넌트 전달인자로 Controls영역에서 조절가능
전달인자1 : 타입(컨트롤_방식),
전달인자2 : 타입(컨트롤_방식)
}
}
const Template = (args) => <Title {...args} />
// 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>
};
직접적인 DOM 조작을 지양하는 React 개발 환경에서 DOM 객체 주소가 필요한 경우 핸들링하기 위해 지정하는 Hook 메서드
: 값이 변경되어도 리렌더링이 발생하지 않고, 리렌더링이 되어도 값을 유지하는 특성(언마운트 되기 전까지)을 이용하여 이벤트에 의해 주소값을 할당하여 사용
-> DOM 노드, 엘리먼트, React 컴포넌트 주소값 참조 가능
cf. state
와 달리 값이 변경되어도 리렌더링을 발생시키지 않음
import { useRef } from "react";
useRef(initialValue);
// cf. 변수에 할당되는 값의 형태는 객체 형태 : { current: initialValue }
DOM 주소값이 필요한 경우
제시된 상황 제외한 대부분의 경우 기본 React 문법을 벗어나 useRef 를 남용하는 것은 부적절하고, React의 특징이자 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야 합니다.
대상 컴포넌트의 DOM 엘리먼트의 주소값을 추출
useRef()
을 할당한 변수 선언
주소값을 추출할 대상 컴포넌트의 ref속성값으로 1번 변수 적용
-> 이벤트에 의해 useRef()
의 current 속성값이 DOM 엘리먼트의 주소값으로 재할당
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>
);
}
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;
styled-component 라는 다른 도구가 존재하므로 s를 붙인 styled-components로 설치를 해야함
느낀점
오늘 복잡하다고 생각했는데 정리하면서 조금씩 머리속에도 안착되는 것같다. 블로깅에 시간을 많이 쓰지말자라고 다짐한 것과 달리 오늘은 블로깅이 유용했다. 오래 걸리긴 하지만 이해를 위해 이 방법도 나쁘진 않은 것같다.
오히려 오늘 실습, 따라하기가 많았는데 오류도 많아서... props는 꼭 객체인 걸 다시 생각할 기회였다. 그리고 특히 오타 주의하기!! 오타.. 그리고 괄호 조심!
이것과 별개로 헷갈리는 부분은 createGlobalStyle
의 위치. 최상위라는 설명이 예시를 보면 아닌 것 같고. 그렇다고 가장 먼저 사용되게 하는 것도 아닌거 같다. index.js에서 적용한 걸 보면 아래에 있으니까..
최상위라기보다는 가장 처음에 위치하도록 사용하는 것 같음
아직은 이렇게 생각하고 있긴한데, 월요일 세션에 물어봐야겠다.