본문 바로가기
팀프로젝트_기록일지/React_이정환_useState,useRef

(3).이정환_react.js강의_ 여러 state 변수들을 하나의 객체로 묶기

by 코잼민 2025. 1. 14.

§1_ spread 문법 + 객체로 여러 useState 변수들을 간결화 정리

● 예시 : Register.js 코드 변경 전,

function Register() {
  const currentDate = formatDate(new Date());
  const [name, setName] = useState("이름");
  const [birth, setBirth] = useState(currentDate);
  const [country, setCountry] = useState();
  const [introduce, setIntroduce] = useState("");

  const onChangeNameInput = (event) => {
    setName(event.target.value);
  };

  const onChengeBirthInput = (event) => {
    setBirth(event.target.value);
  };

  const onCountrySelect = (event) => {
    setCountry(event.target.value);
  };

  const onChangeIntroduce = (event) => {
    setIntroduce(event.target.value);
  };

  return(...);

  }

Register.js 코드 변경 후,

import { useState } from "react";

function Register() {
  const currentDate = formatDate(new Date());

//하나의 state 변수 input + useState({}) 객체로 초기화
  const [input, setInput] = useState({
    name: "",
    birth: currentDate,
    country: "",
    Introduce: "",
  });

  const onChangeNameInput = (event) => {
    setInput({
      ...input,//spread 문법으로, 기존 속성값 유지
      name: event.target.value,//바꾸고 싶은 프로퍼티만 event.target.value 값 저장
    });
  };

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

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

  const onChangeIntroduce = (event) => {
    setInput({
      ...input,
      introduce: event.target.value,
    });
  };

  return (
    <>

  );
}

export default Register;

§★2_ 각 useState를 변경해주는 이벤트 핸들러 하나로 통합하기

  • 순서1_ 각 onChange를 담당하는 form 태그의 name속성에 useState의 변수이름과 동일하게 추가 설정
  • 순서2_ 하나의 핸들러로 통일 + setState({...useState 객체 ,[event.target.name] : event.target.value})
  • 순서3_ 모든 form 태그의 onChange 속성값을 onChange 핸들러로 통일
import { useState } from "react";

function formatDate(date) {
  const YYYY = String(date.getUTCFullYear());
  const MM = String(date.getUTCMonth() + 1).padStart(2, "0");
  const DD = String(date.getUTCDate()).padStart(2, "0");

  return `${YYYY}.${MM}.${DD}`;
}

function Register() {
  const currentDate = formatDate(new Date());

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

  const onChange = (event) => {
    setInput({
      ...input,
      [event.target.name]: event.target.value,//순서1
    });
  };

  return (
    <>
      <div>
        <input
          name="name"//순서2
          value={input.name}
          onChange={onChange}//순서3
          placeholder={"이름"}
        />
        <h3>{input.name}</h3>
      </div>
      <div>
        <input
          name="birth"
          type="date"
          value={input.birth}
          onChange={onChange}
          placeholder={new Date()}
        />
        <h3>{input.birth}</h3>
      </div>
      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value="kr">한국</option>
          <option value="un">미국</option>
          <option value="ut">영국</option>
        </select>
        <h3>{input.country}</h3>
      </div>
      <div>
        <textarea
          name="introduce"
          value={input.introduce}
          onChange={onChange}
        />
        <p>{input.introduce}</p>
      </div>
    </>
  );
}

export default Register;

§3_ useRef

알아야 할 개념 :

Test.js
import { useRef } from "react";

export function Test() {
  const refObj = useRef(0);

  console.log(`재랜더링 :${refObj.current}`); //출력1

  const onClickRefCuurentPlus = () => {
    refObj.current++;
    console.log(`핸들러 : ${refObj.current}`);//출력2
  };

  return (
    <>
      <button onClick={onClickRefCuurentPlus}>ref.current+1</button>
    </>
  );
}

각 출력위치에 대해서 알 필요가 있다.

  • 출력1 : 랜더링 할때 출력되는 출력 위치
  • 출력2 : 버튼을 클릭할때마다 출력되는 출력 위치

위의 내용을 통해 알 수 있는것 : useRef는 useState가 변할때, 랜더링되는 특성을 갖고있지 않다.

§2_ useRef의 객체를 이용해, 특정요소에 작동을 했을 시, event.target요소 외 다른 요소들에 대한 조건들 check하기 => useRef 객체 이용 :

예시 : 회원가입 페이지에서 '제출'버튼을 누를 시, 다른 input 요소 value가 비어있다면, 제출X => 그 해당 태그에 focus되도록 하기

  • 순서1_ 컴포넌트에 inputRef의 useRef 객체 초기화
  • 순서2 :제출 전 검사할 요소들에 ref속성에 useRef 객체 삽입
  • 순서3_ 핸들러에서 검사할 내용 구현

- Register.js

import { useRef, useState } from "react";

function formatDate(date) {
  const YYYY = String(date.getUTCFullYear());
  const MM = String(date.getUTCMonth() + 1).padStart(2, "0");
  const DD = String(date.getUTCDate()).padStart(2, "0");

  return `${YYYY}.${MM}.${DD}`;
}

function Register() {
  const currentDate = formatDate(new Date());

  const inputRef = useRef();

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

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

  const onSubmit = () => {
    if (input.name === "") {
      inputRef.current.focus();
    }
  };

  return (
    <>
      <div>
        <input
          ref={inputRef}
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
        <h3>{input.name}</h3>
      </div>
      <div>
        <input
          name="birth"
          type="date"
          value={input.birth}
          onChange={onChange}
          placeholder={new Date()}
        />
        <h3>{input.birth}</h3>
      </div>
      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value="kr">한국</option>
          <option value="un">미국</option>
          <option value="ut">영국</option>
        </select>
        <h3>{input.country}</h3>
      </div>
      <div>
        <textarea
          name="introduce"
          value={input.introduce}
          onChange={onChange}
        />
        <p>{input.introduce}</p>
      </div>
      <div>
        <button onClick={onSubmit}>제출</button>
      </div>
    </>
  );
}

export default Register;

§3_ useRef vs let 변수

예시코드1 :

import { useRef, useState } from "react";

let outVar = 0;

export function Test() {
  const exampleRef = useRef(0);
  const [useCnt, setUseCnt] = useState(0);
  let inVar = 0;

  const onClickBtn = () => {
    /*
    setUseCnt(useCnt +1);
    */
    exampleRef.current++;
    inVar++;
    outVar++;

    console.log(`ref.current : ${exampleRef.current}`);
    console.log(`outVar : ${outVar}`);
    console.log(`inVar : ${inVar}`);
  };

  return (
    <>
      <button onClick={onClickBtn}>+</button>
    </>
  );
}

의미 :
상태는 렌더링을 딱 한번만 한 상태라 버튼을 누를 시에만 Ref, inVar, outVar이 증가한다.

예시코드2 :

import { useRef, useState } from "react";

let outVar = 0;

export function Test() {
  const exampleRef = useRef(0);
  const [useCnt, setUseCnt] = useState(0);
  let inVar = 0;

  const onClickBtn = () => {
    setUseCnt(useCnt + 1);//state변수인, useCnt가 변하기에 , 버튼을 누를 시, 재랜더링 된다.
    exampleRef.current++;
    inVar++;
    outVar++;

    console.log(`ref.current : ${exampleRef.current}`);
    console.log(`outVar : ${outVar}`);
    console.log(`inVar : ${inVar}`);
  };

  return (
    <>
      <button onClick={onClickBtn}>+</button>
    </>
  );
}

의미 :
버튼을 누를 때마다, 재렌더링이 되는 데, 이때 컴포넌트 inVar은 0으로 다시 초기화 되면서, 증가하므로, inVar은 1로 유지, 나머지 변수들은 증가한다.

예시코드3 :

- App.js

import "./App.css";
import { Test } from "./components/Test";

function App() {
  return (
    <div>
      <div>
        <Test />//Test 컴포넌트 1
      </div>
      <div>
        <Test />//Test 컴포넌트 2
      </div>
    </div>
  );
}

export default App;
- Test.js 는 그대로,

의미 :
OutVar과 useRef를 제대로 보여주는 출력결과이다.
  • useRef : 컴포넌트 내에서 useRef 고유의 변수 역할을 해줄 수 있다.
  • outVar : 부모컴포넌트 => 자식2마리의 Test 컴포넌트 따로 있는것이 아닌 호출 관계이기에, 컴포넌트 여러개이더라도, 공동체 운명인 outVar 변수.
  • 결론 : useRef는 렌더링 되는 컴포넌트 1개당 하나의 고유 객체 변수 역할을 할 수 있는 독특한 새끼임