나의 개발일지

[React] React Hooks - useState / useEffect / useRef 본문

React

[React] React Hooks - useState / useEffect / useRef

heew0n 2023. 11. 9. 20:26

 

useState

 

리액트 훅에는 여러가지 종류가 있는데

우선 공부할 것은 이젠 나름 익숙한 useState 이다!

 

useState의 기본 형태는

const [state, setState] = useState(initialState);

 

 

❗ ⭐ useState에서 짚고 넘어가야 할 포인트

 

1️⃣state는 변수, setState를 이용해 state의 값을 수정한다!

그리고 state가 만약 원시데이터가 아닌 객체 데이터 타입인 경우

그토록 강조하는 불변성을 유지해야 한다!

 

 

// 가장 기본적인 Hook
// 함수형 컴포넌트 내에서 가변적인 상태를 갖게 함
// 카운터 , TodoList
// const[state, setState] = useState(초기값);

// 함수형 업데이트란?

import "./App.css";
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);

  return (
    <div>
      <div>Number State : {number}</div>
      <button
        onClick={() => {
          // 기존 업데이트 방법
          // --> 배치 업데이트
          //   setNumber(number + 1);
          //   setNumber(number + 1);
          //   setNumber(number + 1);
          // 1씩 찍힘
          // --> 렌더링이 잦다는 것은 성능에 이슈가 있는 것!

          // 함수형 업데이트 방법
          // --> 순차적으로 한 번씩 실행시킨다
          // ()안에는 현재 상태의 스테이트 값을 불러온다
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
          // 3씩 찍힘
        }}
      >
        누르면 UP{" "}
      </button>
    </div>
  );
}
export default App;

 

 

setNumber(number + 1);

setNumber(number + 1);

setNumber(number + 1);

 

일반 사용법으로 사용하여 버튼을 눌러보면 1씩 증가한다

반면 함수형 업데이트 방식은 3씩 증가한다.

 

2️⃣ 두 개의 방식에 차이점은 무엇일까?

일반 업데이트 방식은 배치(batch)로 처리가 된다

버튼을 세 번 누르면 setNumber이 세 번 실행되어 3을 출력해야 하는데

리액트는 똑같은 명령을 최종적으로 모아 한 번만 실행시킨다.

 

아무리 많은 명령을 하더라도 단 한 번만 출력될 것이다

 

하지만 함수형 업데이트 방식은 3번을 동시에 명령을 내리면

그 명령을 모아서 순차적으로 각각 한 번씩 실행시킨다

웨이터가 손님에게 주문을 받을 때 메뉴 하나마다 주문을 받아 주방에 전달한다면

동선이 길어진다. 그래서 메뉴를 한 번에 주문을 받아 주방에 전달하는 방법이 가장 효율적인 것이다!!

 

 

 


 

 

useEffect

 

❗ ⭐  useEffect 가 언제 사용될까?

 

useEffect 는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다

1. 어떤 컴포넌트가 화면에 보여졌을 때 무언갈 실행하고 싶다!

2. 어떤 컴포넌트가 화면에 사라졌을 때 무언갈 실행하고 싶다! (return, clean up)

 

 

 

useEffect 는 화면에 렌더링 될 때 useEffect 안에 있는 console.log가 실행된다

--> 핵심 기능!!

 

 

// src/App.js

import React, { useEffect } from "react";

const App = () => {

  useEffect(() => {
		// 이 부분이 실행된다.
    console.log("hello useEffect");
  });

  return <div>Home</div>;
}

export default App;

 

 

그런데, useEffect의 핵심기능인 렌더링 될 때마다 실행되는 것은

비효율적인 성격을 갖는다

 

 

<예시를 통해 살펴보기>

function App() {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log(`Hello : ${value}`);
  }, [value]);
  
   return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      ></input>
    </div>
  );
}

export default App;

 

input 창을 만들고 값을 입력하여 콘솔에 '반가워요'를 입력해 봤더니

입력할 때마다 찍히는 걸 볼 수 있다

state 값이 바뀌고 렌더링 되면서 바뀔 때마다 useEffect가 실행된 것이다

 

 

 

 

 

그래서 콘솔이 브라우저에 한 번만 찍히게 하려면 

의존성 배열을 이용하여 제어할 수 있다

 

의존성 배열(dependency array)란 무엇일까?

--> 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행하는 것! 

 

 

 

(1) 의존성 배열에 값이 없는 경우

 

import { useEffect, useState } from "react";
import "./App.css";

function App() {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log("Hello");
  }, []); //의존성 배열이기 때문에 배열의 형태로 나타낸다
  // input 창에 값을 입력해도 변하지 않고 처음 렌더링 한 상태로 유지된다
  
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      ></input>
    </div>
  );
}

export default App;

 

 

아까 코드에서 useEffect( ),[ ] 만 추가되었다!

 

 

 

 

결과는 값을 아무리 입력해도 "Hello"만 뜨는 걸 볼 수 있다

다른 값은 전혀 실행되지 않고 있다

--> 정리하자면 useEffect( ),[ ] 여기서 두 번째 인자에서 배열이 들어가는데

   어떤 함수를 컴포넌트가 렌더링 될 때 단 한 번만 실행하고 싶으면 [ ] 빈 배열을 쓰면 된다!

 

 

 

(2) 의존성 배열에 값이 있는 경우

 

import { useEffect, useState } from "react";
import "./App.css";

function App() {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log(`Hello : ${value}`);
    // return 안에 로직은 화면에서 사라졌을 때(컴포넌트가 죽을때) 실행되는 것이다
    return () => {
      console.log("사라집니다");
    };
  }, [value]);

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      ></input>
    </div>
  );
}

export default App;

 

 

 

 

 

useEffect( ),[value]  이번엔 두 번째 인자에 [value] 값을 넣었다

value는 state 이고, state 값이 변경되면 input을 입력할 때마다 그 값이 변할테니

useEffect가 실행이 될 것이다!

 

 

 


useRef ( 1 )

 

useRef는 DOM 요소에 접근할 수 있도록 하는 React Hook이다

 

 

import React, { useRef, useState } from "react";

const style = {
  border: "1px solid black",
  margin: "10px",
  padding: "10px",
};

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);
 
  const stateCountButtonHandler = () => {
    setCount(count + 1);
  };


  const refCountButtonHandler = () => {
    countRef.current++;
    console.log(countRef.current);
  };

  return (
    <>
      <div style={style}>
        state 영역 {count}
        <br />
        <button onClick={stateCountButtonHandler}>state 증가</button>
      </div>
      <div style={style}>
        ref 영역 {countRef.current}
        <br />
        <button onClick={refCountButtonHandler}>state 증가</button>
      </div>
    </>
  );
}

export default App;

 

 

 

 

state는 state 값이 변경됨에 따라 계속해서 렌더링이 된다

(화면이 카운트된 숫자가 나타날 때마다 렌더링이 되는 것임)

 

 

 

 

 

 

반면 useRef는 값이 증가해도 렌더링이 되지 않는다

둘 다 상태관리를 하지만 렌더링의 차이가 있다

 

 

 


useRef ( 2 )

 

 

렌더링 됐을 때 자동으로 아이디에 포커싱이 되도록 만들기

 

 

import React, { useEffect, useRef, useState } from "react";

function App() {
  const idRef = useRef("");
  const pwRef = useRef("");

  const [id, setId] = useState("");
  // id 포커스
  useEffect(() => {
    idRef.current.focus();
  }, []);

  // pw 포커스
  // useEffect(() => {
  //   pwRef.current.focus();
  // }, []);

  useEffect(() => {
    if (id.length >= 10) {
      pwRef.current.focus();
    }
  }, [id]);

  return (
    <>
      <div>
        {/* <input>에 ref라는 속성이 있다.(DOM에 접근가능) 위에 useRef랑 연결가능  */}
        아이디 :{" "}
        <input
          value={id}
          onChange={(e) => {
            setId(e.target.value);
          }}
          type="text"
          ref={idRef}
        />
      </div>
      <div>
        비밀번호 : <input type="password" ref={pwRef} />
      </div>
    </>
  );
}

export default App;

 

 

 

10자리를 입력하면 바로 비밀번호로 포커싱이 된다

 

 

useRef로 포커싱이 되게 하는 방법 말고

onChange에서 바로 하는 방법도 있다

 

 <input
          value={id}
          onChange={(e) => {
            setId(e.target.value);
            if (id.length >= 10) {  // state 가 업데이트 되기 직전!
              pwRef.current.focus();
            }
          }}
          type="text"
          ref={idRef}
        />

 

하지만 문제점이 있다

 

 

 

 

11자리를 입력해야 비밀번호로 포커싱이 이동한다

그 이유는 리액트의 배치업데이트 때문이다.