css를 이용해 스타일링 방법 중 틀린 것 > import하는 경우 컴포넌트가 많지 않아도 css파일이 여러개 있어야 함
styled-components에 대한 설명 중 틀린 것 > css파일과 함께 스타일 관리 시 유리함
>>별도로 파일을 만들지 않고 한 파일 안에서 스타일 관리 시 유리함
react 스타일링
좋은 앱 만드려면
- 번들 사이즈 고려 - css코드가 차지하는 사이즈 중요 요소
- 앱 성능에 대한 고려 - 유저와의 상호작용에서 스타일 코드 성능이 중요 요소
- 사용자에게 유리한 ui/ux 고려 - 고급테크닉 적용하면 더 나은 ui/ux 반영
- 자바스크립트 이용한 다양한 스타일 기법 - ui토글링, 애니메이션, 다크모드 등 복잡한 ui 구현 힘듦
- 유지보수 용이, 확장 가능한 코드 작성 - 스타일에 대한 코드를 어떻게 작성하고 관리에 대한 지식
react 앱에서 스타일링 방법
css import - css파일 import > 필요한 스타일을 하나의 파일에 작성해 코드 분리
장점 - 단순히 파일만 import해 사용 가능 / 컴포넌트가 많지 않을 경우 한 파일에 코드 관리 가능
단점 - css파일 분리는 되지만 namespace 나눌 수 없음 / 스타일이 겹칠 경우 마지막 룰로 덮어짐
css module - 한 css파일에서 작성한 스타일은 하나의 파일 namespace로 관리 / 스타일 겹치는 현상 해결
classname 뒤에 겹치지 않는 hash를 붙임 / 두 단어 이상의 경우 카멜케이스로 이름 지음
css in js - 별도로 css 파일 만들지 않고 하나의 컴포넌트 안에서 스타일 작성
js문법 그대로 코드 작성 > react 컴포넌트 사용하는 것 처럼 작성 / sass 문법 활용 가능
예시
css box model
css layout 기본이 되는 모델 > content-box, padding-box, border-box순으로 하나의 엘레먼트를 감싸고 있음
box 타입 - inline, block / display - inline, display:inline-block, display: bolck으로 서로 다른 box type 적용
box-sizing - width, height는 디폴트로 content-box 크기 정의
widht-100px > content크기 100px + padding, border크기는 100px에 추가됨 ( 별개로 취급)
box-sizing:border-box > 이해하기 쉬운 레이아웃 정의
css position
- static - position의 디폴트값 > 엘레먼트는 normal flow 따라 위치함
- relative - normal flow따라 위치, 자기 자신에 상대적으로 위치함
- absolute - normal flow에서 벗어나 가장 가까운 ancestor에 상대적으로 위치함
- fixed - normal flow에서 벗어나 viewport에 상대적으로 위치함
- sticky - normal flow에서 따라 위치, 가장 가까운 scrolling ancestor에 상대적으로 위치
+ancestor - 컨테이닝 블럭 > 어떤 엘레먼트를 감싸고 있는 블럭 (static제외)
css units
- px, pt, cm, in - 절대적인 길이 표현 유닛
- rem, em, % - 특정 값에 상대적인 길이 표현 유닛
- vw, vh, vmin, vmax - viewport에 상대적인 길이 표현 유닛
sass - syntactically awesome style sheets / css preprocessor
모듈, 믹스인, nested style, 변수, 조건문, 반복문 등의 기능으로 css프로그래밍 언어적으로 활용하도록 확장
styled-components는 sass 기본적으로 지원
sass &(ampersand) - 자기 자신을 나타내는 paceholder
기존 selector문법 응용해 복잡한 스타일 적용
variable 예시
sass variable - 믹스인, partial와 함께 제공되는 코드 관리 방법 중 하나
색상, 사이즈 등 자주 등장하는 값을 주로 변수로 사용함
sass nested style - 별도의 css 없이 하나의 블록 안에 여러 css 적용
css specificity가 그대로 적용되어 너무 깊게 nested되면 유지보수 힘듦
>> > button {}
css flexbox model
html element를 하나의 상자로 간주 그 안에서 내부 item 배열 방법을 정하는 모델
1차원 레이아웃 디자인에 사용, reponsive design에 유리, 가운데 정렬,비율로 정렬 등 처리 시 유리
flex properties - container
flex-direction - row, column등의 방향 결정
justify-content - main axis에서의 정렬 결정
align-items - cross axis에서의 정렬 결정
flex-wrap - flex container가 내부 item의 width를 합친것보다 작아질 때 정렬 방법 결정
flex properties - item
flex-grow - flex container가 커질 때 item이 얼마만큼 늘어날 건지 결정
flex-shrink - flex container가 줄어들 때 item이 얼마만큼 줄어들 건지 결정
flex-basis - 기준점이 되는 item 크기
justify-self - 한 아이템을 main-axis에 따라 정렬 방법 결정
align-self - 한 아이템을 cross-axis에 따라 정렬 방법 결정
order - flex container에서 item 순서 결정
css import로 수학문제 앱 구현하기
<mathproblem.jsx>
import React, { useState, useEffect } from "react";
import "./math-problem.css";
// GameStatus를 이용해 상태를 처리합니다.
const GameStatus = {
CORRECT: "correct",
INCORRECT: "incorrect",
READY: "ready",
};
const generateRandomNumber = () => {
return Math.floor(Math.random() * 100) + 1;
};
function MathProblem() {
// 필요한 숫자의 상태를 정의하세요.
const [n1, setN1] = useState(0);
const [n2, setN2] = useState(0);
const [gameStatus, setGameStatus] = useState(GameStatus.READY);
const [answer, setAnswer] = useState('')
const generateProblem = () => {
setGameStatus(GameStatus.READY);
// generateRandomNumber를 이용해 새로 숫자를 생성하세요.
setN1(generateRandomNumber());
setN2(generateRandomNumber());
};
const handleSubmit = () => {
// 제출시 정답 여부에 따라 GameStatus의 상태를 설정하세요.
const originalAnswer = n1 + n2;
setGameStatus( originalAnswer === Number(answer)
? GameStatus.CORRECT
: GameStatus.INCORRECT
);
};
const handleAnswerInput = (e) => {
setAnswer(e.target.value);
};
useEffect(() => {
generateProblem();
}, []);
// 실행 결과와 동일한 기능을 하도록 마크업을 작성하세요.
return (
<div>
<div className="problem">
<span>{n1}</span>
<span>+</span>
<span>{n2}</span>
</div>
<hr />
<div className="answer-sheet">
<div className="equal"> = </div>
<input
id="answer"
type="number"
name="answer"
value={answer}
onChange={handleAnswerInput}
/>
<button onClick={handleSubmit} className="submit-button">
제출
</button>
</div>
<div className="result">
{gameStatus === GameStatus.CORRECT ? "정답입니다"
: gameStatus.INCORRECT ? "오답입니다"
: "" /*ready면 노출할 필요 없음*/}
</div>
{gameStatus === GameStatus.CORRECT &&
(<div>
<button onClick={generateProblem} className="generateQ">문제 생성</button>
</div>)}
</div>
);
}
export default MathProblem;
<math-problem.css>
/* 자유롭게 CSS를 작성해보세요. */
.problem span:not(:first-of-type){
margin-left: 12px;
}
.answer-sheet {
display: flex;
}
.answer-input {
margin-left: 12px;
box-sizing: border-box;
font-size: 18px;
padding: 12px;
width: 150px;
height: 45px;
}
.answer-input::-webkit-outer-spin-button,
.answer-input::-webkit-inner-spin-button { /*input box옆 선택버튼*/
-webkit-appearance: none;
margin: 0;
}
.submit-button{
margin-left: 12px;
width: 80px;
height: 45px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 700;
color: #495057;
background: #d3f9d8;
}
.equal {
align-self: center; /*스스로 가운데정렬*/
}
.generateQ{
width: 100%;
background: #748ffc;
color: #fff;
font-size: 16px;
font-weight: 700;
border: none;
border-radius: 4px;
height: 50px;
}
.result {
font-size: 12px;
margin: 12px 0;
}
css import를 이용해 회원가입 폼 구현하기
<registerFrom.jsx>
import React, { useState } from 'react'
import './register-form.css'
const InputStatus = {
NORMAL: 'normal',
ERROR: 'error',
SUCCESS: 'success',
}
function RegisterForm() {
const [name, setName] = useState('')
const [age, setAge] = useState('')
const [nameInputStatus, setNameInputStatus] = useState(InputStatus.NORMAL)
const [ageInputStatus, setAgeInputStatus] = useState(InputStatus.NORMAL)
const handleChangeName = (e) => {
setName(e.target.value)
}
const handleChangeAge = (e) => {
setAge(e.target.value)
}
const validateName = (name) => {
if (name.length < 1 || name.length > 10) {
setNameInputStatus(InputStatus.ERROR)
return
}
setNameInputStatus(InputStatus.SUCCESS)
}
const validateAge = (age) => {
if (!(age >= 1 && age <= 100)) {
setAgeInputStatus(InputStatus.ERROR)
return
}
setAgeInputStatus(InputStatus.SUCCESS)
}
const submitForm = (e) => {
e.preventDefault() // 기본 동작인 폼 제출을 막기 위해 호출합니다.
validateName(name)
validateAge(age)
}
const getInputStatusStyle = (status) => {
if (status === InputStatus.ERROR) {
return 'input-invalid'
} else if (status === InputStatus.SUCCESS) {
return 'input-valid'
} else {
return 'input-default'
}
}
const getInputStatusTextStyle = (status) => {
if (status === InputStatus.ERROR) {
return 'text-error'
} else if (status === InputStatus.SUCCESS) {
return 'text-success'
} else {
return 'input-default'
}
}
const resetForm = () => {
setName('')
setAge('')
setNameInputStatus(InputStatus.NORMAL) // 폼을 초기화할 때 상태도 초기화합니다.
setAgeInputStatus(InputStatus.NORMAL)
}
return (
<form onSubmit={submitForm}>
<fieldset className="form-fieldset">
<label className={'form-label ' + getInputStatusTextStyle(nameInputStatus)} htmlFor="name">
이름
</label>
<input
value={name}
onChange={handleChangeName}
type="text"
name="name"
id="name"
placeholder="이름을 입력해 주세요."
className={'form-input ' + getInputStatusStyle(nameInputStatus)}
/>
<div className="form-error text-error">
{nameInputStatus === InputStatus.ERROR && '이름은 1글자 이상, 10글자 이하여야 합니다.'}
</div>
</fieldset>
<fieldset className="form-fieldset">
<label className={'form-label ' + getInputStatusTextStyle(ageInputStatus)} htmlFor="age">
나이
</label>
<input
className="form-input"
value={age}
onChange={handleChangeAge}
type="number"
name="age"
id="age"
placeholder="나이를 입력해 주세요."
/>
<div className="form-error text-error">
{ageInputStatus === InputStatus.ERROR && '나이는 1부터 100 사이여야 합니다.'}
</div>
</fieldset>
<div className="button-container">
<button className="form-button reset-button" onClick={resetForm} type="reset">
초기화
</button>
<button className="form-button submit-button" onClick={submitForm} type="submit">
제출
</button>
</div>
<pre className="debug">
<code>
{JSON.stringify(
{
name,
age,
nameInputStatus: nameInputStatus === InputStatus.ERROR ? 'error' : nameInputStatus,
ageInputStatus: ageInputStatus === InputStatus.ERROR ? 'error' : ageInputStatus,
},null,2)}
</code>
</pre>
</form>
)
}
export default RegisterForm
<register-form.css>
.debug {
font-size: 0.75rem;
}
.input-invalid{
border: 2px solid #ff6b6b;
}
.input-valid{
border: 2px solid #51cf66;
}
.form-label{
font-size: 12px;
margin-bottom: 8px;
color: gray;
}
.form-input {
display: block;
padding: 4px;
border: 2px solid #dee2e6;
border-radius: 3px;
}
.text-error{
color: #ff6b6b;
}
.form-error{
font-size: 12px;
margin-top: 4px;
}
.button-container{
display: flex;
margin-top: 8px
}
.form-button{
flex: 1;
border: none;
padding: 4px;
color: white;
font-weight: 700;
border-radius: 3px;
cursor: pointer;
}
.form-button:not(:first-of-type){
margin-left: 8px;
}
.reset-button{
background: #adb5bd;
}
.submit-button{
background: #37b24d;
}
.form-fieldset{
border: none;
padding: 12px 0;
}
css modules활용해 수학 문제 앱 구현하기
>> css파일 > camelCase로 바꾸기 // jsx파일 > className={styles.className}
Style components
js파일 안에 스타일 정의하고 react컴포넌트처렁 활용 > js와 긴밀히 연계해 다양한 코드 작성 가능
별도로 css파일 만들지 않고 하나의 파일 안에서 스타일 관리 > 스타일 코드와 컴포넌트 코드 간 결합 분리 시 유리
tagged template literal 문법 활용
css 코드에 post-css, minification, Sass 적용
css코드 겹치지 않게 관리 / 클래스 이름 자체가 hash
styled-components로 수학문제 앱 구현하기
import React, { useEffect, useState } from "react";
import "./math-problem.css";
import styled from 'styled-components';
const GameStatus = {
CORRECT: "correct",
INCORRECT: "incorrect",
READY: "ready",
};
const generateRandomNumber = () => {
return Math.floor(Math.random() * 100) + 1;
};
function MathProblem() {
const [n1, setN1] = useState(0);
const [n2, setN2] = useState(0);
const [answer, setAnswer] = useState(0);
const [gameStatus, setGameStatus] = useState(GameStatus.READY);
const generateProblem = () => {
setAnswer("");
setN1(generateRandomNumber());
setN2(generateRandomNumber());
setGameStatus(GameStatus.READY);
};
const handleSubmit = () => {
const numberedAnswer = Number(answer);
if (n1 + n2 === numberedAnswer) {
setGameStatus(GameStatus.CORRECT);
} else {
setGameStatus(GameStatus.INCORRECT);
}
};
const handleAnswerInput = (e) => {
setAnswer(e.target.value);
};
useEffect(() => {
generateProblem();
}, []);
return (
<div>
<Problem>
<span>{n1}</span>
<span>+</span>
<span>{n2}</span>
</Problem>
<Line />
<AnswerSheet>
<Equal> = </Equal>
<AnswerInput
id="answer"
type="number"
name="answer"
value={answer}
onChange={handleAnswerInput}
/>
<SubmitButton onClick={handleSubmit}>
제출
</SubmitButton>
</AnswerSheet>
<Result>
{gameStatus === GameStatus.CORRECT
? "정답입니다"
: gameStatus === GameStatus.INCORRECT
? "오답입니다"
: ""}
</Result>
{gameStatus === GameStatus.CORRECT && (
<ButtonController>
<GenerateProblemButton onClick={generateProblem}>
문제 생성
</GenerateProblemButton>
</ButtonController>
)}
</div>
);
}
export default MathProblem;
// styled-componets를 이용해 스타일을 적용하는 코드를 작성하세요.
const Problem = styled.div`
font-size: 2rem;
& span:not(:first-of-type){
margin-left:12px;
}
`
const AnswerSheet = styled.div`
display: flex;
`
const AnswerInput = styled.input`
margin-left: 12px;
padding: 12px;
box-sizing: border-box;
font-size: 1.4rem;
width: 150px;
height: 45px;
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
`
const Line = styled.hr`
margin: 12px 0;
`
const SubmitButton = styled.button`
width: 80px;
margin-left: 12px;
height: 45px;
background: #d3f9d8;
font-size: 0.9rem;
font-weight: 700;
border: none;
border-radius: 4px;
color: #495057;
`
const Equal = styled.div`
align-self: center;
font-size: 1.4rem;
`
const ButtonController = styled.div`
margin-top: 12px;
`
const GenerateProblemButton = styled.button`
width: 100%;
height: 50px;
font-weight: 700;
font-size: 1.1rem;
border: none;
border-radius: 4px;
background: #748ffc;
color: #fff;
`
const Result = styled.div`
padding: 12px 0;
font-size: 0.8rem;
min-height: 40px;
`
#React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript