본문 바로가기

엘리스_이론

23.11.20 (react스타일링)

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

 

'엘리스_이론' 카테고리의 다른 글

23.11.24 (react 비동기통신)  (0) 2023.11.24
23.11.22(spa, 라우팅)  (0) 2023.11.22
23.11.16(hooks)  (0) 2023.11.17
23.11.15 (props, state, 이벤트 처리)  (0) 2023.11.15
23.11.13 (react기초, jsx, 컴포넌트)  (1) 2023.11.13