프론트엔드(Web)/React

React 작동 원리 및 특징, JSX 문법, 커스텀 훅 작성 요령

만능 엔터테이너 2024. 9. 29. 15:01
728x90
반응형
SMALL

 

[리액트의 특징]

  1. 컴포넌트를 기반으로 UI를 표현한다.
  2. 화면 업데이트 구현이 쉽다.
  3. 화면 업데이트가 빠르게 처리된다.

[컴포넌트를 기반으로 UI를 표현한다.]

컴포넌트 - 화면을 구성하는 요소, UI를 구성하는 요소

 

각각의 JAVASCRIPT 파일로 모듈화하여 레고 블록처럼 만들 수 있음 ⇒ 중복 코드를 재사용 가능!

페이지의 모든 요소를 컴포넌트화하여 생성 → 이후 불러와서 사용


[화면 업데이트 구현이 용이]

업데이트란? 사용자의 행동(클릭, 드래그)에 따라 웹 페이지가 스스로 모습을 바꿔 상호작용 하는 것

렌더링? UI 요소를 화면에 그려내는 것을 말함

 

선언형 프로그래밍 : 과정은 생략하고 목적만 간결히 명시하는 방법 ex) 식당에 가서 주문하는 것처럼 “토마토 파스타 하나 주세요” 처럼 말하고 토마토 파스타 만드는 방법은 알 필요가 없음

명령형 프로그래밍 : 목적을 이루기 위한 모든 일련의 과정을 설명하는 방식

ex) 마치 진상 손님처럼 주문하고 토마토 파스타 만드는 방법을 일일히 명시하여야 하기 때문


[화면 업데이트가 빠르게 처리된다]

가상의 DOM의 각각의 업데이트를 처리하고 1번에 실제 DOM에 반영을 하기에 렌더링이 빠름!


React App 생성하기

기본설정이 이미 완료된 React App 생성 툴

React 공식 문서에서도 권장되고 있음

[Vite 설치 순서]

  1. npm create vite@latest
  2. Project name 입력
  3. 진행할 프레임워크 설치 → React
  4. 사용할 언어 선택
  5. 이후 생성한 project 폴더 명이 Root 폴더가 되도록 설정
  6. 이후 npm i로 터미널 창에 입력하고 필요한 모듈을 설치
  • public 폴더 내부
    • svg, png, jpg 와 같은 이미지 파일이거나 font 혹은 동영상 등 코드가 아닌 정적인 파일 저장소
  • src 폴더 내부
    • 실제 react 및 javscript 코드를 작성할 폴더
  • assets 폴더 내부
    • svg, png, jpg 와 같은 이미지 파일 저장소
  • index.html 파일
    • 리액트 앱의 기본 틀을 역할을 하는 html 코드 정보

[npm run dev]


"no-unused-var": "off",
"react/prop-types": "off",

"no-unused-var": "off":
설명: JavaScript에서 사용되지 않은 변수를 경고하는 규칙인 no-unused-var를 끕니다. 즉, 사용하지 않은 변수가 있어도 경고하지 않도록 설정한 것입니다.

"react/prop-types": "off":
설명: React 컴포넌트에서 prop-types로 전달되는 props의 타입을 명시하지 않아도 경고하지 않도록 하는 설정입니다. 보통 prop-types는 props의 타입을 명확하게 하기 위해 사용되지만, 이 규칙을 끄면 타입을 명시하지 않아도 문제가 되지 않습니다.
따라서 이 두 설정은 ESLint가 사용하지 않은 변수를 허용하고, React에서 prop-types 사용에 대한 경고를 무시하도록 만든 것입니다.

---eslint.config.js----

import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    settings: { react: { version: '18.3' } },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      ...reactHooks.configs.recommended.rules,
      'react/jsx-no-target-blank': 'off',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      //아래 코드 복사!
      "no-unused-var": "off",
      "react/prop-types": "off",
    },
  },
]

TailwindCSS 설치 방법 in Vite : https://tailwindcss.com/docs/guides/vite

[React App의 구동 원리]

http://localhost:5173 : 자신의 컴퓨터를 의미(포트번호 :5173)

→ localhost는 자신의 컴퓨터에만 작동하게 되므로 다른 사람의 컴퓨터에는 작성되지 않습니다!

createRoot(document.getElementById('root')).render(
index.html에 나와 있는 내용을 불러오는 역할
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'  ->  Root 파일
import './index.css' -> css 파일 속성  

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
-- App.jsx --
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

-- index.html --

😎[React 컴포넌트] - 반드시 대문자로 생성

App.jsx 컴포넌트 내부에 components를 불러 오게 되면

App 컴포넌트가 부모(조상) 컴포넌트 (즉, Root 컴포넌트 - 관례상 App 사용!)

App 컴포넌트에 import하는 컴포넌트는 자식 컴포넌트 (함수 컴포넌트의 return 값을 설정)

 

만약, 이렇게 main.jsx 에서 <App />을 <Hello />로 변경하게 되면 루트 컴포넌트는 Hello가 되는 것!

📍여기서 한 파일명.jsx 에 전부 작성하기 보다는 컴포넌트별로 파일을 만들어 import 하여 불러오는 형식으로 작성하는 것을 권장합니다.

 

컴포넌트를 만들 때, src 하위 "component" 파일 내부에 파일명.jsx를 작성!


[JSX란?] - 리액트에서 사용하는 확장된 자바스크립트 문법

[JSX 주의 사항]

숫자 및 문자열 값이면 { } 객체 데이터 내부에 만들어서 렌더링 하도록 만들 수 있음

  1. 중괄호 { } 내부에는 자바스크립트 표현식만 넣을 수 있다. 

 → 한 줄로 표현이 가능한 식들 가능 / 😎if문 혹은 for문은 1줄로 표현이 되지 않기에 중괄호 내부에 들어갈 수 없음

   2. 숫자, 문자열, 배열 값만 렌더링 된다.

ex) bolean , undefined, null 값은 화면에 렌더링 되지 않습니다. ex) true, undefined,null,obj... 등

😎여기서 객체 값은 렌더링 되지 않아 {obj.a} 등 객체 데이터 내부에 문자 및 숫자값으로 변경하여야 합니다.

코드를 입력하다 렌더링되는 브라우저화면이 백지로 나타나게 되는 것은 오류가 발생한 것!

3. 모든 태그는 닫혀 있어야 한다.

4. 최상위 태그는 반드시 하나여야만 한다. ( <> ~ </> [Tip, 빈태그로 묶고 시작]

이렇듯, 최상위 태그가 <main> ~ </main> 태그와 <div> ~ </div> 태그 이렇게 2가지가 존재할 경우 오류가 발생하게 됩니다.

따라서, 최상위 태그는 <main> ~ </main>로 작성하여야 합니다.

만약, 최상위 태그가 필요없을 경우 <> ~ </> 처럼 빈태그로 반환하면 됩니다.

const App = () => {
  const user = {
    name: "전용학",
    isLogin: true,
  };
  if (user.isLogin) {
    return <div>로그아웃</div>;
  } else {
    <div>로그아웃</div>;
  }
  // return <>{user.isLogin ? <div>로그인</div> : <div>로그아웃</div>}</>;
};
export default App;

객체 내부에 로그인을 구현하도록 설정!

[JSX에서 style을 설정하는 방법 - 2가지 방법이 존재]

style={{
backgroundColor: “red”,
borderBottom: "5px solid blue",
}}
  • style속성을 지정할 때, 기존에 작성한 css 형식인 background-color가 아닌 backgroundColor 이렇게 카멜 케이스로 작성해야 함! [반드시 {{}} 이렇게 중첩하여 작성해야 함!]

  • 별도의 css 파일을 만들어 css 작성하기
.logout {
background-color: red;
border-bottom: 5px solid green;
}

Main.css(임의의 파일)을 작성하고 App.jsx에서 import “./Main.css”를 입력하고 나서

태그 내부 속성에 className=”logout” 이라고 작성하면 됩니다.


[Props - 컴포넌트에 값 전달하기]

 

이렇게 하나의 Button 컴포넌트를 만들어서 image와 text만 바꾸어서 반복 렌더링하여 구성하는 기능

😎props란 부모 컴포넌트로부터 자식 컴포넌트에 데이터를 보낼 수 있게 해주는 방법이다.

 

const Button = (props) => {
  console.log(props);
  return (
    <>
      <button style={{ color: props.color }}>
        {props.text} - {props.color.toUpperCase()}
      </button>
    </>
  );
};

Button.defaultProps = {
  color: "black",
};
export default Button;
--Button.jsx---
여기서 Button 컴포넌트에 text와 color 
속성을 지정하고 나서 1번째 버튼요소를 제외한 2,3번째는 
color 속성이 없기 때문에 이럴때는, Button.defaultProps = 
{ } 내부에 color의 기본값을 지정하여 오류가 발생하지 않
도록 방지함!

😎 defaultProps에서는 부모 컴포넌트로부터 props를 전달하려고 할 때, 전달하고자 하는 속성이 undefined일 때를 방지하여 전달하고자 하는 컴포넌트명.defaultProps로 지정하고 = { 내부에 undefined 대신 작성할 속성을 정의하는 형태}

 

 

언제 따옴표를 사용해야 하나?

  1. 문자열 값: color, fontFamily, backgroundColor 등과 같이 색상, 글꼴, 단위가 있는 값은 문자열로 작성해야 합니다. 예를 들어 color: "black", fontSize: "16px", fontFamily: "Arial"처럼 문자열로 작성합니다.
  2. 숫자 값: opacity, zIndex, flex 등 단위가 없는 숫자형 값은 문자열로 작성하지 않아도 됩니다. 예를 들어, opacity: 0.5, zIndex: 10, flex: 1처럼 숫자로 바로 작성할 수 있습니다.

 

import Header from "./components/Header";
import Main from "./components/Main";
import Footer from "./components/Footer";
import Button from "./components/Button";

const App = () => {
  return (
    <>
      <Header />
      <Main />
      <Footer />
      <Button text={"메일"} color={"red"} />
      <Button text={"카페"} />
      <Button text={"블로그"} />
    </>
  );
};
export default App;
---App.jsx---

props - 객체 형태 { “” }로 전달 받는다는 것을 앎 - 비구조화 할당 방법을 통한 Props 전달

import Header from "./components/Header";
import Main from "./components/Main";
import Footer from "./components/Footer";
import Button from "./components/Button";

const App = () => {
  const buttonProps = {
    a: 1,
    b: 2,
    c: 3,
  };
  return (
    <>
      <Header />
      <Main />
      <Footer />
      <Button {...buttonProps} text={"메일"} color={"red"} />
      <Button text={"카페"} />
      <Button text={"블로그"} />
    </>
  );
};
export default App;
다음과 같이 값을 전달할 내용이 여러 개일 경우 배열로 만들
어 a:1, b:2, c:3 이렇게 작성하고 ... 전개연산자로 객체 형식
으로 불러오면 됩니다.

😎 props에서 값은 부모 컴포넌트에서 자식 컴포넌트로만 전달 가능! / 반대는 React에서 불가능함

import "./App.css";
import Header from "./components/Header";
//파일의 확장자는 Header.jsx를 작성하지 않아도 vite가 알아서 찾아가도록 설정해줌!
import Button from "./components/Button";

function App() {
  const buttonProps = {
    text: "메일",
    color: "red",
    a: 1,
    b: 2,
    c: 3,
  };

  return (
    <>
      <Button {...buttonProps} />
      <Button text={"카페"} />
      <Button text={"블로그"}>
        <div>자식요소</div> => 이렇게 prps로 전달할 컴포넌트에 자식 컴포넌트가 존재할 경우 children
      </Button>
      <h1>안녕 리액트!</h1>
    </>
  );
}

export default App;

😎children부모 컴포넌트에서 정의된 컴포넌트 내부에 또 다른 자식 요소를 중첩하고 싶을 때 사용됩니다.


[이벤트 핸들링]

onClick = {onClickHandler} 이렇게 속성을 작성합니다.

/* eslint-disable react/prop-types */
const Button = ({ text, color, children }) => {
  const onClickHandler = () => {
    console.log({ text });
  };
  return (
    <>
      <button onClick={onClickHandler} onMouseEnter={onClickHandler}>
        {text} - {color.toUpperCase()}
        {children}
      </button>
    </>
  );
};

Button.defaultProps = {
  color: "black",
};
export default Button;**

[이벤트 객체] (e) ⇒ { }  / 공식 문서: https://react.dev/learn/responding-to-events

합성 이벤트 - 모든 웹 브라우저의 이벤트 객체를 하나로 통일한 형태

이벤트 사용 시 주의사항

1. 이벤트 이름은 😎카멜표기법으로 작성

     HTML에서는 onclick으로 작성하지만 리액트에서는 카멜 표기법으로 onClick으로 작성해야한다
2. 이벤트에는 😎함수 형태의 값을 전달

    HTML에서 이벤트를 설정할 때 ""안에 실행할 코드를 넣었지만, 리액트에서는 함수 형태의 객체를 전달한다. 애로우(화살표)함수 문법을 사용하거나 혹은 외부에 미리 함수를 만들어서 전달하기도한다.

3. 😎DOM요소에만 이벤트를 설정할 수 있다.

    div, button, input, form, span 등의 DOM요소에는 이벤트를 설정할 수 있지만, 직접만든 리액트 컴포넌트에는 이벤트를  자체적으로 설정할 수 없다.

이러한 Cross Browsing Issue를 해결하는 방법이 합성 이벤트


[State - 상태 관리하기]

이렇듯, 하나의 컴포넌트 안에 여러 개의 state를 작성할 수 있습니다.

  const [state, setState] = useState();
const [현재 값, 현재 값을 변경시키는 함수] = useState(초기값);

또한, const [ state, setState] = useState(" ")에서 userState값에 “ ” 혹은 0 등 기본값을 작성하지 않으면 화면에 렌더링이 되지 않을 수 있습니다. 따라서, 상태 초기값을 명시적으로 설정하는 것을 권장합니다.


리액트가 Re-rendering 되는 3가지 상황

  1. 자신이 관리하는 state의 값이 변경되었을 때
  2. 자신이 제공받은 props의 값이 변경되었을 때
  3. 부모 컴포넌트가 리렌더링 되었을 때, 자식 컴포넌트도 리렌더링 됨
import { useState } from "react";

const Bulb = () => {
  const [light, setLight] = useState("");
  return (
    <>
      <div>
        {light === "ON" ? (
          <h1 style={{ backgroundColor: "orange" }}>ON</h1>
        ) : (
          <h1 style={{ backgroundColor: "gray" }}>OFF</h1>
        )}

        <button
          onClick={() => {
            setLight(light === "ON" ? "OFF" : "ON");
          }}
        >
          {light === "ON" ? "끄기" : "켜기"}
        </button>
      </div>
    </>
  );
};

const Counter = () => {
  const [count, setCount] = useState(0); // 초기값 설정
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};

const App = () => {
  return (
    <>
      <Bulb />
      <Counter />
    </>
  );
};
export default App;

이렇게 컴포넌트를 나누게 되면 화면에는 리렌더링 되는 상황을 방지하여 더 빠르게 상태를 업데이트 할 수 있습니다.

→ 이 컴포넌트를 Bulb.jsx 와 Counter.jsx로 파일로 분리하여 모듈화하여 사용하는 것을 권장합니다.


[State로 사용자 입력 관리하기]

 

📍이벤트 함수를 사용할 때 반드시 사용해야 할 것

(e) ⇒ set함수명(e.target.value)

onChange={onChangeCountry} 와 value={country} 반드시 2개의 필수 요소를 작성해야 합니다.

 

onChange 이벤트 함수 : onchange 이벤트는 HTML 요소의 값이 변경될 때 발생하는 이벤트입니다. 이 이벤트는 주로 <input>, <select>, <textarea>와 같은 폼 요소에서 많이 사용됩니다. 값이 변경되면 이벤트 핸들러를 호출하여 특정 작업을 수행할 수 있습니다.

e.preventDefault()

기본(default)를 막다(prevent)라는 단어에서 유추할 수 있듯이 html에서 표준으로 제공하는 태그의 기본 이벤트 발생을 막는 메서드입니다. 

  e.preventDefault(); // 폼 제출 시 페이지 리로드를 방지

사용 예시:

  • 링크 클릭 시 페이지 이동을 막고, 대신 자바스크립트로 다른 동작을 수행하고 싶을 때.
  • 폼 제출 시 페이지가 새로고침되지 않도록 하고, AJAX로 데이터를 처리하고 싶을 때.

e.stopPropagation()

전파(Propagation)를 중지한다(stop)라는 의미처럼 상위 엘리먼트로의 이벤트 전파(이벤트 버블링)을 막기 위한 메서드입니다.

 

사용 예시:

  • 중첩된 요소에서 하위 요소의 이벤트가 상위 요소에 전달되지 않도록 하고 싶을 때.
  • 버튼 클릭 시 하위 요소에서만 특정 동작을 하고, 상위 요소는 무시하고 싶을 때
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {
  const [name, setName] = useState("이름");

  const onChangeName = (e) => {
    setName(e.target.value);
    // onChangeName의 인자로 이벤트를 전달하면 전달한 이벤트
    의 값으로 상태를 변경토록 해주는 로직
  };
  return (
    <div>
      <input onChange={onChangeName} type="text" placeholder="이름" />
      {name} // props로 사용자의 입력값에 따라 변동
    </div>
  );
};
export default Register;

이벤트 부분에서 (e) ⇒ setName(e.target.value)에서 작성하게 되면 div 태그 내부에서 input

리액트의 useState를 이용하여 사용자의 입력 값을 저장하고 처리할 때에는 onChange 속성과 더불어 value={ } 속성까지 사용하여야 합니다.

// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {
  const [name, setName] = useState("이름");
  const [birth, setBirth] = useState("");

  const onChangeBirth = (e) => {
    setBirth(e.target.value);
  };

  const onChangeName = (e) => {
    setName(e.target.value);
  };
  return (
    <div>
      <div>
        <input
          value={name}
          onChange={onChangeName}
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input value={birth} onChange={onChangeBirth} type="date" />
      </div>
    </div>
  );
};
export default Register;

이렇게 작성하게 되면 날짜를 선택하게 되면 상태 값이 저장 되는 것을 볼 수 있습니다. {birth} 값을 추가하여 나타나게 되면

이렇게 나타나게 할 수 있습니다.

// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {
  const [name, setName] = useState("이름");
  const [birth, setBirth] = useState("");
  const [country, setCountry] = useState("");

  const onChangeCountry = (e) => {
    setCountry(e.target.value);
  };

  const onChangeBirth = (e) => {
    setBirth(e.target.value);
  };

  const onChangeName = (e) => {
    setName(e.target.value);
  };
  return (
    <div>
      <div>
        <input
          value={name}
          onChange={onChangeName}
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input value={birth} onChange={onChangeBirth} type="date" />
      </div>
      {birth}
      <div onChange={onChangeCountry} value={country}>
        <select>
          {/* select 태그에 빈 태그를 추가하려면 바로 아래처럼 아무것도 입력하지 않으면 됩니다. */}
          <option value=""></option>
          {/* 이렇게 설정하게 되면 만약 한국을 선택할 때, 상태 값은 kr로 나오고 한국으로 나오지 않습니다. */}
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
          <option value="gr">독일</option>
          <option value="fr">프랑스</option>
          <option value="ita">이탈리아</option>
        </select>
        {country}
      </div>
    </div>
  );
};
export default Register;

여기서 option 태그에 value 값으로 kr, us 등 간격하게 지정하게 해 놓고 사용자 화면에는 한국이면 상태 값은 kr로 업데이트 되도록 하는 형식이 좋습니다.

// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {
  const [name, setName] = useState("이름");
  const [birth, setBirth] = useState("");
  const [country, setCountry] = useState("");
  const [bio, setBio] = useState("");

  const onChangeBio = (e) => {
    setBio(e.target.value);
  };

  const onChangeCountry = (e) => {
    setCountry(e.target.value);
  };

  const onChangeBirth = (e) => {
    setBirth(e.target.value);
  };

  const onChangeName = (e) => {
    setName(e.target.value);
  };
  return (
    <div>
      <div>
        <input
          value={name}
          onChange={onChangeName}
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input value={birth} onChange={onChangeBirth} type="date" />
      </div>
      {birth}
      <div onChange={onChangeCountry} value={country}>
        <select>
          {/* select 태그에 빈 태그를 추가하려면 바로 아래처럼 아무것도 입력하지 않으면 됩니다. */}
          <option value=""></option>
          {/* 이렇게 설정하게 되면 만약 한국을 선택할 때, 상태 값은 kr로 나오고 한국으로 나오지 않습니다. */}
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
          <option value="gr">독일</option>
          <option value="fr">프랑스</option>
          <option value="ita">이탈리아</option>
        </select>
        {country}
      </div>
      <div>
        <textarea onChange={onChangeBio} value={bio} placeholder="자기소개">
          {bio}
        </textarea>
        {bio}
      </div>
    </div>
  );
};
export default Register;

위에 작성한 내용이 동작하는 방식이 동일하여 1개의 state로 작성하도록 만들려면 아래와 같이 작성하여야 합니다.

**// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const onChangeBio = (e) => {
    setInput({
      ...input,
      bio: e.target.value,
    });
  };

  const onChangeCountry = (e) => {
    setInput({
      ...input,
      country: e.target.value,
    });
  };

  const onChangeBirth = (e) => {
    setInput({
      ...input,
      birth: e.target.value,
    });
  };

  const onChangeName = (e) => {
    setInput({
      ...input, // name값을 제외한 나머지 값들을 변경하지 않을 상태로 나타냄
      name: e.target.value, // 변경하고자하는 props의 값만 바꾸어야 됨
    });
  };
  return (
    <div>
      <div>
        <input
          value={input.name}
          onChange={onChangeName}
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input value={input.birth} onChange={onChangeBirth} type="date" />
      </div>
      {input.birth}
      <div onChange={onChangeCountry} value={input.country}>
        <select>
          {/* select 태그에 빈 태그를 추가하려면 바로 아래처럼 아무것도 입력하지 않으면 됩니다. */}
          <option value=""></option>
          {/* 이렇게 설정하게 되면 만약 한국을 선택할 때, 상태 값은 kr로 나오고 한국으로 나오지 않습니다. */}
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
          <option value="gr">독일</option>
          <option value="fr">프랑스</option>
          <option value="ita">이탈리아</option>
        </select>
        {input.country}
      </div>
      <div>
        <textarea
          onChange={onChangeBio}
          value={input.bio}
          placeholder="자기소개"
        >
          {input.bio}
        </textarea>
        {input.bio}
      </div>
    </div>
  );
};
export default Register;
// 리팩토링 결과, ...input을 선언하여야지 다른 값도 값이
없다고 상태가 업데이트 됨**

아래와 같이 통합 이벤트 핸들러로 설정하여 적용할 수 있음

// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";

const Register = () => {

  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const onChange = (e) => {
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <div>
        <input
          value={input.name}
          onChange={onChange}
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input value={input.birth} onChange={onChange} type="date" />
      </div>
      {input.birth}
      <div onChange={onChange} value={input.country}>
        <select>
          {/* select 태그에 빈 태그를 추가하려면 바로 아래처럼 아무것도 입력하지 않으면 됩니다. */}
          <option value=""></option>
          {/* 이렇게 설정하게 되면 만약 한국을 선택할 때, 상태 값은 kr로 나오고 한국으로 나오지 않습니다. */}
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
          <option value="gr">독일</option>
          <option value="fr">프랑스</option>
          <option value="ita">이탈리아</option>
        </select>
        {input.country}
      </div>
      <div>
        <textarea
          onChange={onChange}
          value={input.bio}
          placeholder="자기소개"
        >
          {input.bio}
        </textarea>
        {input.bio}
      </div>
    </div>
  );
};
export default Register;


[useRef - 컴포넌트의 변수 생성하기]

import { useState, useRef } from "react";

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const countRef = useRef(0);
  const inputRef = useRef(null);

  const onSubmit = () => {
    if (input.name === "") {
      // 이름을 입력하는 DOM 요소에 포커스를 맞춤
      inputRef.current.focus();
    }
  };

  const onChange = (e) => {
    countRef.current++;
    console.log(countRef.current);
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <div>
        <input
          ref={inputRef}
          value={input.name}
          onChange={onChange}
          name="name" // 이름을 입력하는 인풋의 name 속성 추가
          type="text"
          placeholder="이름"
        />
      </div>
      <div>
        <input
          value={input.birth}
          onChange={onChange}
          name="birth" // 생년월일 인풋의 name 속성 추가
          type="date"
        />
      </div>
      <div>
        <select
          onChange={onChange}
          value={input.country}
          name="country" // select의 name 속성 추가
        >
          <option value=""></option>
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
          <option value="gr">독일</option>
          <option value="fr">프랑스</option>
          <option value="ita">이탈리아</option>
        </select>
      </div>
      <div>
        <textarea
          onChange={onChange}
          value={input.bio}
          name="bio" // textarea의 name 속성 추가
          placeholder="자기소개"
        ></textarea>
      </div>
      <button onClick={onSubmit}>제출</button>
    </div>
  );
};

export default Register;


React Hooks : 클래스 컴포넌트의 기능을 함수 컴포넌트에서도 이용할 수 있도록 하는 메서드

class 컴포넌트 => 문법이 복잡함(사용 x)

{😎 리액트 훅은 이름 앞에 동일한 접두사 use가 붙음}

[3가지 hook 관련된 팁]

1. 반드시 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능

이렇게 컴포넌트 바깥에서 호출하게 되면 즉시 오류가 발생합니다.

   2.조건부로 호출될 수는 없다. (조건문과 반복문 내부에서 훅을 호출할 수 는 없는 것을 의미)

   3.커스텀 훅을 직접 만들 수 있다.

여기서 드래그한 부분들을 커스텀 훅을 작성하려고 하려면 드래그 한 내용을 복사하여 아래와 같이 getInput 을 만들어 사용하면 됩니다.

 

이때, 커스텀 훅을 만드는 방법은 함수의 이름 앞에 use를 붙여 만약에, getInput을 커스텀 훅으로 만들면 useInput 이렇게 작성하여 만들면 됩니다.

 

커스텀 훅을 사용할 때, 파일을 관리하는 방법 -> src 디렉터리 내부에 components와 동일하게 파일을 저장하지 않고 따로 hooks라는 파일로 훅의 이름으로 작성하는 것이 일반적

여기서는, useInput이 커스텀 훅이므로 hooks 디렉터리 내부에 useInput.jsx라는 파일로 생성하여  작성하는 것을 말함!

728x90
반응형
LIST