[React 숙련] React Hooks(1)

Habin Lee·2023년 11월 8일
1

요약

  • React Hooks의 종류를 알고 적절하게 사용할 수 있다.
    : useState, useEffect, useRef

React Hooks

useState

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

함수형 업데이트

import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State : {number}</div>
      <button onClick={()=>{
        // 입문주차에서 배웠던 기존 업데이트 방법
        // 버튼을 누르면 숫자가 1씩 올라간다.
        setNumber(number + 1);
        // 함수형 업데이트를 적용한 방법
        // 버튼을 누르면 숫자가 1씩 올라간다.
        setNumber((currentNumber)=>{
          return currentNumber + 1
        });
      }}>누르면 UP</button>
    </>
  );
}

export default App;
  • 위의 코드로 기존 업데이트 방법과 함수형 업데이트 방법의 결과값은 같으나 차이점이 있다.
    -> 아래의 코드를 한번 더 보자
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State : {number}</div>
      <button onClick={()=>{
        // 버튼을 누르면 숫자가 1씩 올라간다.
        /* 배치성으로 처리 -> 배치 업데이트
           리액트는 아래의 내용을 한꺼번에 가져와 처리를 하는데 내용이 중복되니 한번만 처리함 */
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);

        // 버튼을 누르면 숫자가 3씩 올라간다.
        /* currentNumber => 현재 상태의 State가 들어옴
           마찬가지로 아래 내용을 한꺼번에 가져오지만 현재 상태의 State가 들어오기 때문에
           각 함수마다 모두 다른 값을 받으므로 세번을 순차적으로 실행시킨다.*/ 
        setNumber((currentNumber)=>currentNumber + 1);
        setNumber((currentNumber)=>currentNumber + 1);
        setNumber((currentNumber)=>currentNumber + 1);
      }}>누르면 UP</button>
    </>
  );
}

export default App;
  • 리액트 환경에서 배치성 업데이트를 하는 이유
    : 렌더링이 잦다는 것은 성능에 이슈가 있는 것으로 간주하기 때문에 요청사항을 한꺼번에 모아서 한 번만 처리하여 불필요한 렌더링을 줄이기 위해 배치성 업데이트를 사용함
  • 예를 들어 손님이 피자, 콜라, 피클을 한꺼번에 주문할 때, 웨이터는 주문한 내용을 따로따로 주방에 요청하는 것이 아니라 동선, 비용 등을 줄이기 위해 한꺼번에 주문을 받고 주방에 요청하는 것과 같다고 생각하면 됨

useEffect

  • 렌더링 될 때마다 특정한 작업을 수행해야할 때 설정하는 훅
  • useEffect는 매개변수로 콜백함수가 들어간다.

컴포넌트가 화면에서 보여졌을 때

import { useEffect } from "react";
import { useState } from "react";

function App() {
  const [value, setValue] = useState('')
  useEffect(()=>{
    console.log('hi')
  });
  return <div>
    <input type="text" value={value}
    onChange={(e)=>{
      setValue(e.target.value);
    }}/>
  </div>;
}

export default App;
  • 위 코드의 흐름
  1. input에 값을 입력
  2. value, 즉 state가 변경됨
  3. state가 바뀌었기 때문에 -> App 컴포넌트가 리렌더링
  4. 리렌더링 -> useEffect() 실행
  5. 1~4번 반복
  • => 리렌더링이 될 때마다 계속 실행되기 때문에 의존성 배열(Dependency Array)이 필요함
    이 배열에 값을 넣으면, 그 값이 바뀔 때만 useEffect를 실행한다.
useEffect(()=>{console.log('hi')}, []);
  • useEffect의 함수가 끝나는 뒷쪽에 두 번째 인자로 넣어주면 된다.
    : Dependency Array이기 때문에 배열 [ ] 형태가 들어간다.
    -> 어떤 값이 바뀌면 출력이 되게 할지 정하면 된다.
  • 위 예시처럼 빈 배열을 넣으면 처음 렌더링될 때만 콘솔에 hi가 찍히게 된다.
    -> 값이 없기 때문에 어떠한 값으로 계속 바뀌더라도(리렌더링 되더라도) 콘솔에 찍히지 않게 된다.
useEffect(()=>{console.log(`${value}`)}, [value]);
  • 만약 value가 바뀔때마다 value의 값이 변하는 것을 보고 싶다.
    -> 위 코드처럼 적어주면 input에 값을 적을 때마다 콘솔에 바뀐 값이 찍히게 된다.
  • (추가) 현재 첫 예시를 새로고침하면 첫 렌더링으로 콘솔에 hi가 2번 찍혀 나온다.
    -> index.js에서 React.StrictMode를 지워주면 hi가 한번씩 찍혀 나온다.

컴포넌트가 화면에서 사라졌을 때(return)

  • clean up : useEffect의 main body 부분에 return문을 함수 형태로 만들어주면 안에 있는 로직이 컴포넌트가 사라질 때 동작을 하게 된다.
useEffect(()=>{
  console.log('hi');
  // 이 아래쪽에 return문 작성
  return () => {/* 이 부분에 반환하고 싶은 내용을 적어준다. */}
}, []);

useRef

  • useRef는 DOM 요소에 접근할 수 있도록 하는 React Hook이다.
  • ref : reference(어떠한 것에 대한 참조 정도로 기억해두자)
import { useRef } from "react";

function App() {
  // 객체 형태의 {current: "초기값"} 으로 콘솔에 찍힌다.
  const ref = useRef('초기값');
  console.log('ref1', ref);
  // 이런식으로 ref로 설정해놓은 곳에 접근해서 값을 변경할 수도 있다.
  ref.current = '변경값';
  console.log('ref2', ref);
  
  return <div></div>;
}

export default App;

이렇게 설정된 ref 값은 컴포넌트가 계속해서 렌더링 되어도 unmount 전까지 값을 유지한다.

  • 이러한 특징때문에 useRef 두 가지 용도로 사용된다.
  1. 저장공간으로서의 useRef의 활용방법
  2. DOM요소 접근 방법으로서의 useRef의 활용방법

저장공간

  1. state와 비슷한 역할을 하지만 state는 변화가 일어나면서 다시 렌더링이 일어나며 내주 변수들은 초기화가 되는데
  2. ref에 저장한 값은 렌더링을 일으키지 않아서 ref의 값 변화가 계속 일어나도 렌더링으로 인해 내부 변수들이 초기화 되는 것을 막을 수 있다.
  3. state는 리렌더링이 꼭 필요한 값을 다룰 때 쓰고, ref는 리렌더링을 발생시키지 않는 값을 저장할 때 사용해주면 된다.

state와 ref의 차이 예시

import { useRef, useState } from "react";

const style = {
  width: '300px',
  border: '2px solid skyblue',
  margin: '10px',
  padding: '10px',
}

function App() {

  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  const plusState = () => {
    setCount(count + 1);
  }

  const plusRef = () => {
    countRef.current++;
    console.log(countRef.current)
  }
  
  return (
    <>
      <div style={style}>
        state 영역입니다. {count} <br />
        <button onClick={plusState}>state 증가</button>
      </div>
      <div style={style}>
        ref 영역입니다. {countRef.current} <br />
        <button onClick={plusRef}>ref 증가</button>
      </div>
    </>
  )
}

export default App;

  • state는 값이 변경될 때마다 리렌더링되어 값이 올라가지만 ref는 콘솔에만 찍히는 것을 볼 수 있다.

DOM

  • 화면이 렌더링 되자마자 특정 input 태그가 focusing이 돼야 하는 경우에 사용
// useEffect, useRef 모두 렌더링
import React, { useEffect, useRef } from 'react'

function App() {
  // idRef로 변수를 지정
  const idRef = useRef('');

  // 화면이 렌더링 될 때마다 어떠한 작업을 하고 싶은 경우 -> useEffect 사용
  useEffect(() => {
    // 아이디 input을 연결시킨 idRef의 current에 focus를 두도록 작성
    idRef.current.focus();
  }, []);

  return (
    <>
      <div>
        /* 아이디의 input을 지정해줘야하므로 input에 대한 레퍼런스를 갖고 있어야한다.
           ref={idRef}라는 속성을 넣어서 ref와 연결시켜준다.*/
        아이디 : <input type='text' ref={idRef} /> 
      </div>
      <div>
        비밀번호 : <input type='password' />
      </div>
    </>
  )
}

export default App

  • 비밀번호 칸을 눌러도 바탕을 눌러 포커싱을 없애도 새로고침을 하면 다시 아이디 칸으로 포커스가 옮겨가는게 보인다.

추가 예시

  • 요구사항: 아이디의 입력 값이 10자리 이상일 때, 비밀번호 칸이 포커스가 되도록 만들기
// 아래 React Hooks을 사용할 때는 꼭 import 해줘야 동작한다.
import React, { useEffect, useRef, useState } from 'react'

function App() {

  const idRef = useRef('');
  const pwRef = useRef('');
  // id input에 들어오는 값들을 tracking하기 위해 useState 사용
  const [id, setId] = useState('');
  // onChange 속성을 사용하여 입력 값 받아오기
  const onChangeId = (e) => {
    setId(e.target.value);
  }

  useEffect(() => {
    idRef.current.focus();
  }, []);
  
  // useEffect 한번 더 사용
  useEffect(() => {
    // 아이디 input의 value값인 id의 길이가 10이상일 때
    if(id.length >= 10) {
      // 비밀번호 칸이 포커스 되도록
      pwRef.current.focus();
    }
  // useEffect가 발동되려면 id 값이 변화해야하므로 의존성 배열에 id를 넣어준다.
  }, [id])

  return (
    <div style={{margin: '30px'}}>
      <div>
        아이디 : <input type='text' ref={idRef} value={id} onChange={onChangeId} />
      </div>
      <div>
        비밀번호 : <input type='password' ref={pwRef} />
      </div>
    </div>
  )
}

export default App


오.. 아주 잘 작동한다.

추가 예시에서 알게 된 것

  • 위 요구사항을 처리할 때, useEffect가 아니라 onChange 함수로 if문을 그대로 사용하여 만들어도 되지 않을까하여 처음에 실제로 그렇게 만들어봤다.
import React, { useEffect, useRef, useState } from 'react'

function App() {

  const idRef = useRef('');
  const pwRef = useRef('');
  const [id, setId] = useState('');

  const onChangeId = (e) => {
    setId(e.target.value);
    // useEffect를 없애고 이 부분에 조건 추가!
    if(id.length >= 10) {
      pwRef.current.focus();
    }
  }

  useEffect(() => {
    idRef.current.focus();
  }, []);

  return (
    <div style={{margin: '30px'}}>
      <div>
        아이디 : <input type='text' ref={idRef} value={id} onChange={onChangeId} />
      </div>
      <div>
        비밀번호 : <input type='password' ref={pwRef} />
      </div>
    </div>
  )
}

export default App


..?

11개를 입력해야만 포커스가 넘어간다.
이해가 되지 않아 뭔가 문제인지 고민하다가 문제 풀이영상을 보고 알게 되었다.
id의 console.log를 찍어보자.

const onChangeId = (e) => {
  setId(e.target.value);
  // 이 부분에 console.log 찍기
  console.log('id', id);
  if(id.length >= 10) {
    pwRef.current.focus();
  }
}

  • 1을 쳤는데 아무 값이 없는 콘솔이 처음 찍히고 2를 쳤을 때 1이 콘솔에 찍혀나왔다.
    : 이 이유는 앞서 얘기했던 배치성 업데이트 때문이다.
    -> set을 했다고 해서 id가 바로 10자리로 업데이트 되는 것이 아니라 console에 찍을 때는 마지막 set이 반영되기 직전이기 때문에 11자리를 적어야만 포커스가 넘어가게 되는 것이다.
  • 이러한 속성 때문에 우리는 코드를 작성할 때 골머리를 앓게 될 것이다...

느낀 점

아직 React Hooks의 종류를 3가지 밖에 익히지 못했는데 아직 종류의 절반도 본게 아니라니.. 갈 길이 멀다😂

0개의 댓글