본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 12주차 정리 -1 React(Hooks-useReducer, react-hook-form, styles(scss,sass), Router)

by 김선지 2024. 1. 9.

저번에 이어서 class형 component의 기능들을 함수형 component도 쓸 수 있게 하는 Hook들이다.

useReducer

현재 state와 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 state를 반환하는 함수

 

말이 조금 어려운데 그냥 state와 setState를 밖에다가 뺄 수 있는 hook이라고 생각하면 될 것 같다.

 

import { useReducer } from "react";
// useReducer는 useState의 대체로 나왔지만 복잡해질 때만 써야 좋음
// 간단하면 useState이용

const reducer = (prevNumber, action) => {
    // action 안에는 dispatch (setNumber)의 인자 자체가 들어감.
    // dispatch안의 인자가 객체형 {type: 'INCREMENT'}라서 return을 이렇게 줌
    switch (action.type) {
        case 'INCREMENT':
            return {value: prevNumber.value + 1};
        case 'DECREMENT':
            return {value: prevNumber.value - 1};
        case 'RESET':
            return {value: 0};
        default:
            return;
    }
}
const initNumber = { value: 0 };


function UserReducerEx() {
    
    const [number, dispatch] = useReducer(reducer, initNumber);
    
    const plus = () => {
        dispatch({type: 'INCREMENT'});
    }
    const minus = () => {
        dispatch({type : 'DECREMENT'});
    }
    const reset = () => {
        dispatch({type : 'RESET'});
    }

    return ( <>
    <h1>useReducer hooks test</h1>
    <h2>{number.value}</h2>
    <button onClick={plus}>Plus</button>
    <button onClick={minus}>Minus</button>
    <button onClick={reset}>Reset</button>
    </> );
}

export default UserReducerEx;

React-hook-form

react에서 제공하는 폼 관리 라이브러리로 폼의 state 관리와 유효성 검사를 간단하게 만들어준다.

 

resigter: 말 그대로 유효성 검사 규칙을 등록(설정)

handleSubmit:  폼 제출을 처리하기 위한 함수로 함수의 인자로 들어간 콜백함수는 유효성 검사를 통과한 데이터를 인자로 받아서 실행한다. (통과하지 못했을 때도 2번째 인자의 함수로 동작 수행 가능)

watch: 특정 폼 필드의 값을 실시간으로 관찰한함수

formState: 폼의 상태를 나타내는 객체 (errors, inValid, isDirty, isSubmitted)

 

// react-hook-form 인스톨 필요
import { useForm } from 'react-hook-form';

function Form() {
// state처럼 구조파괴를 통해 속성, 메소드 등을 정의
  const {
    register, // input 할당, value 변경 감지
    handleSubmit, // form submit 이벤트 시 호출
    formState: { errors }, // 폼 상태 객체 (그 안에 에러 객체)
    watch, // 특정 폼 필드의 값을 실시간으로 사용
  } = useForm();

  console.log('errors', errors);
  console.log('watch username', watch('username'));

  const onValid = (data) => {
    console.log('onValid', data);
  };

  const onInValid = (err) => {
    console.log('onInValid', err);
  };
  return (
    <>
      <h1>react-hook-form 라이브러리 데모</h1>
      {/* handleSubmit(func A [, func B]) : 두개의 함수를 받을 수 있음
      - func A: 필수, 유효할 때 실행
      - func B: 선택, 유효하지 않을 때 실행
       */}
       {/* 여기서는 () => handleSubmit(onValid, onInvalid)의 형태로 하면 일반 폼처럼 새로고침이 되니 아래 식으로 하자.) */}
      <form onSubmit={handleSubmit(onValid, onInValid)}>
        <input
          type="text"
          placeholder="username"
          
          // 원래 name="username"
          {...register('username', {
            required: '이름을 입력해주세요',
            // min-length: 2 + 오류 메시지
            minLength: {
              message: '이름은 최소 2글자 이상 작성해주세요',
              value: 2,
            },
          })}
        />
        {/* 에러 메시지 formState에 설정했던 errors객체가 있을 경우 */}
          {errors.username?.message}
        {/* 아래 식으로도 가능*/}
        {errors.username && (
          <div>{errors.username.message}</div>
        )}
      
        <br />

        <input
          type="email"
          placeholder="email (gmail)"
          {...register('email', {
            required: '이메일을 입력해주세요',
            validate: {
              useGmail: (value) => {
                return (
                  value.includes('gmail.com') || 'gmail로만 가입 가능합니다'
                );
              },
            },
          })}
        />
        {errors.email?.message}
        <br />

        <input type="text" placeholder="paassword" {...register('password')} />
        <br />

        <button type="submit">Submit</button>
      </form>
    </>
  );
}

export default Form;

 


Styles

Sass(Syntactically Awesome Style sheets):

 

문법적으로 어썸한 스타일시트다. 약간 초당옥수수 느낌의 작명센스

CSS전처리기를 이용해 가시성을 높여준다. (컴파일이 필요하다. Sass or Scss파일 -> 컴퓨터가 이해하는 CSS로 변환)

(CSS전처리기: 변수, 함수 개념 등의 특별한 문법을 가지고 CSS를 생성하는 프로그램)

*** Sass 의 경우 현재 최신 버전인 21 버전에서는 컴파일 에러가 뜬다. 그래서 현재 배포 버전인 20 버전 이하를 활용하면 해결된다.

이 문제를 해결하기 위해

1. 오타 확인

2. 패키지 충돌 문제인지 확인

3. vs code config 설정 확인

다해봤는데도 해결되지 않았는데, 아래 stackoverflow를 참고하고 해결했다.

https://stackoverflow.com/questions/77339459/react-module-error-sass-loader-and-type-error-resolve-url-loader

 

React Module Error sass-loader and type-error resolve-url-loader

I wanted to learn more React in general and because I need it for university project now. I got 2 projects from a friend who is a decent developer but I can not run them on my computer without any ...

stackoverflow.com

 

Styled Components:

 

JS안에서 CSS를 작성하도록 도와주는 라이브러리로 컴포넌트 기반의 설계 방식이다.

return이 아닌 컴포넌트 안에서 태그이름과 css를 정의하고 커스터마이징 된 태그를 이용하는 방식이다.

 

SCSS

// node-sass는 deprecated 되었으므로 아래 dart sass를 이용하자.
npm install sass

 

// 아래 변수를 다른 파일으로 보내고 import
@import './variables.scss';
@import './utils.scss';

// - 변수 선언: $ 키워드 사용
// $color-red: red;
// $color-orange: orange;
// $color-yellow: yellow;
// $animation-duration: 0.4s;

// mixin을 통해 함수처럼 사용 가능 (mixin, include가 한쌍)
// @mixin box($size) {
//   width: $size;
//   height: $size;
// }

.container {
  display: flex;

  // - 중첩: 부모 자식 표현 가능
  .box {
    @include box(150px);

    // &: 부모 선택자 참조해서 치환
    &.red {
      // &.red : .box.red
      background-color: $color-red;
    }

    &.orange {
      background-color: $color-orange;
    }

    &.yellow {
      background-color: $color-yellow;
    }

    &:hover {
      // - 연산 가능
      $box-animation: $animation-duration * 2;
      transform: translateY(-20px);
      transition: transform $box-animation;
    }
  }
}

.btn {
  padding: 10px;
  margin: 10px;
  border: 1px solid black;
}

.btn-primary {
  // - 확장: 기존 선택자 스타일을 다른 선택자에게 상속
  @extend .btn;
  background-color: cornflowerblue;
}

@media (max-width: $breakpoint-sm) {
  .container {
    flex-direction: column;
  }
}

 

Styled-Components

// npm install styled-components 필요
import styled from "styled-components";

// CSS in JS: css를 js 안에 작성함
// styled-components, emotion, styled-jsx, ....
// 각 컴포넌트(태그)마다 격리된 스타일 적용 가능

// ``백틱으로 css를 넣어준다.
const StyledContainer = styled.div`
    display: flex;
`

const StyledBox = styled.div`
    width: 100px;
    height: 100px;
    background-color: ${(props) => props.bgColor || 'blue'};

    &:hover {
        transform: translateY(-20px);
    }
`

function StyledComponent() {
    return ( <>
    <StyledContainer>
        <StyledBox bgColor='red'></StyledBox>
        <StyledBox bgColor='gold'></StyledBox>
    </StyledContainer>
    </> );
}

export default StyledComponent;

 


Router

react의 경우 여러개의 파일을 만드는 ejs나 기본 html과는 달리 하나의 html만 있으면 해결 된다.

그 이유는 우리가 바닐라 js를 이용해서 tag를 만들고 append 하는 작업을 react가 알아서 해주기 때문이다.

(뼈대만 있는 index.html에 react가 append를 해주는 구조)

 

다시 말하자면 a태그나 get 요청을 이용해서 다른 html을 render하는 것과는 다르게,

다른 url로 이동하면 그에 맞는 가상 DOM을 만들어서 html에 갖다 붙여준다. 그래서 render시에 새로고침이 따로 필요 없다. 

 

뭔가 백엔드 파일의 app.js같다.

 

Route 컴포넌트는 백엔드처럼 순서대로 요청에 따른 응답을 해주는 것 같다. 그래서 NotFound 컴포넌트가 제일 아래 가있다.

 

app.js

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./components/Header";
import MainPage from "./pages/MainPage";
import NotFound from "./pages/NotFound";
import ProductList from "./components/ProductList";
import ProductPage from "./pages/ProductPage";
import ProductDetailPage from "./pages/ProductDetailPage";

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Header></Header>
        <Routes>
          <Route path="/" element={<MainPage></MainPage>}></Route>
          <Route path="/products" element={<ProductPage></ProductPage>}></Route>
          <Route path="/products/:productId" element={<ProductDetailPage />} />
          <Route path="*" element={<NotFound></NotFound>}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

 

ProductDetailPage.js

 

useParams는 localhost/:id 처럼 req.params를 이용할 수 있는 기능이다.

navigate가 세션을 이용하는 기능과 같은 기능을 줘서 신기하다.

productInfos는 객체의 배열이 들어있는 더미데이터이다.

미들웨어를 따로 만들어야 할 기능을 알아서 해준다. 메타.... 좀 친다.

import { useNavigate, useParams } from "react-router-dom";
// 객체의 배열이 들어있는 더미데이터
import { productInfos } from "../components/ProductList";
 
function ProductDetailPage() {
// products:productId 에서 productId의 값
    console.log(useParams()); // {productId: '2'}
    
    const { productId } = useParams();
    const navigate = useNavigate();

    const targetProduct = productInfos[Number(productId) - 1];
    console.log(targetProduct);
    const { id, name, email, body} = targetProduct;
    
    return ( <>
    <h1>Product Detail Page</h1>
    <button onClick={() => navigate(-1)}>뒤로가기</button>
    <button onClick={() => navigate(1)}>앞으로 가기</button>
    <button onClick={() => navigate('/')}>홈으로 이동</button>
    
    <ul>
        <li>상품 번호 : {id}</li>
        <li>상품 명 : {name}</li>
        <li>판매자 : {email}</li>
        <li>상세정보 : {body}</li>
    </ul>
    </> );
}

export default ProductDetailPage;

 

 

MainPage.js

 

useSearchParams()는 쿼리스트링을 이용할 때 쓴다.

searchParams 값에 key= value 쌍이 들어가면, 들어가자 마자

url 창에 쿼리스트링 형태로 들어간다. (검색 기능에 쓸 수 있을 듯)

import { useSearchParams } from "react-router-dom";

function MainPage() {
    const [searchParams, setSearchParams] = useSearchParams();
    // searchParams는 객체값
    console.log(searchParams.get('mode')); // Dark or null
    // / => null
    // /?modoe=Dark => Dark

  return (
    <div className={['Main', searchParams.get('mode')].join(' ')}>
      <h1>Main Page Death!</h1>
      <button onClick={() => {
        // {mode: 'Dark'} 설정
        setSearchParams({mode: 'Dark'});
      }}>Dark Mode</button>
    </div>
  );
}

export default MainPage;