카테고리 없음

[엘리스 12주차] useRef를 이용한 상태 업데이트

불곰자리 2023. 11. 27. 23:58

오늘은 리액트에서 제공하는 기본 훅(useState, useRef, useContext, useReducer)로 상태 관리를 하는 법에 대해 배웠다.

 

참고로 리액트에서 말하는 상태(state)는 컴포넌트 내에서 변하는 값이며, 수정 시 state의 값을 직접 바꾸기보단 set함수를 이용해 값을 변경한다. (이유: 리액트는 state가 변경될 때 화면을 리렌더링하는데, 이 state가 변한 것을 객체의 값이 아닌 객체의 주소 값이 변경된 것으로 감지하기 때문이다.)

 

그 중 useRef에 대해서, 처음엔 HTML dom element에 직접적으로 접근할 때 사용할 수 있다고 배웠었는데, 다른 용도(?)로도 쓸 수 있다고 들어서 그것에 대해 useState와 비교하며 정리해보려고 한다. 

 

1. useState

하나의 단순한 상태를 관리하는 데 사용할 수 있다. 

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span style={{ margin: "0px 16px" }}>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

 

2. useRef

상태가 바뀌어도 리렌더링 하지 않는 상태를 정의할 때 사용한다.

상태 변경 = UI 변경을 원치 않을 때 사용할 수 있다.

저번에 글을 썼을 때는 HTML의 DOM element에 직접 접근할 때 사용할 수 있다고 했었는데,

밑의 예시처럼 n초 후에 상태를 업데이트 할 때도 사용할 수 있다.

useState와 비교하여 사용법을 알아보자.

function App() {
  const [user, setUser] = useState({ name: "", age: 0 });
  
  return (
    <>
      <input defaultValue={user.name} onChange={(event) => setUser((user) => ({ ...user, name: event.target.value }))} />
      <input type="number" defaultValue={user.age} onChange={(event) => setUser((user) => ({ ...user, age: Number(event.target.value) }))} />
      <div>name: {user.name}</div>
      <div>age: {user.age}</div>
    </>
  );
}

 

두 개의 input에서 값을 입력 받아 업데이트 된 값을 화면에 보여준다.

useState를 사용하면 입력 값 변경 시마다 set 함수를 이용해 값을 변경해야 한다.

useState 사용 시 input 업데이트 때마다 재렌더링이 일어난다.

 

위의 코드를 실행하고 컴포넌트의 업데이트 과정을 살펴보면, input에 값이 바뀔 때마다 모든 컴포넌트가 재렌더링된다.

그러나 어떤 경우엔 입력 시마다가 아닌 몇 초마다 값을 업데이트 하고 싶은 경우도 생길 것이다. 

그럴 때 useRef를 사용할 수 있다.

function App() {
  const [user, setUser] = useState({ name: "", age: 0 });
  const nameRef = useRef(null);
  const ageRef = useRef(null);

  useEffect(() => {
    setInterval(() => {
      console.log("setInterval 실행...");
      const newName = nameRef.current.value;
      const newAge = Number(ageRef.current.value);
      setUser({ name: newName, age: newAge });
    }, 2000);
  }, []);

  return (
    <>
      <input ref={nameRef} defaultValue={user.name} />
      <input ref={ageRef} type="number" defaultValue={user.age} />
      <div>name: {user.name}</div>
      <div>age: {user.age}</div>
    </>
  );
}

 

useRef를 사용하면 n초 후에 값이 업데이트 된다.

 

그렇다면 반대로 이런 생각이 들 수도 있다. 왜 굳이 useRef를 써야하나?

그럼 setInterval 함수 내에 useState를 이용해 값을 변경해보자.

function App() {
  const [user, setUser] = useState({ name: "", age: 0 });
  const nameRef = useRef(null);
  const ageRef = useRef(null);

  useEffect(() => {
    setInterval(() => {
      console.log("setInterval 실행...");
      setUser({ name: user.name, age: user.age });
    }, 5000);
  }, []);

  return (
    <>
      <input ref={nameRef} defaultValue={user.name} onChange={(event) => setUser((user) => ({ ...user, name: event.target.value }))} />
      <input ref={ageRef} type="number" defaultValue={user.age} onChange={(event) => setUser((user) => ({ ...user, age: Number(event.target.value) }))} />
      <div>name: {user.name}</div>
      <div>age: {user.age}</div>
    </>
  );
}

 

useState를 사용하면 값이 사라진다.

 

왜 onChange로 입력 시마다 set함수를 실행시키면서, setInterval로 또 값을 업데이트 시키냐는 불필요한 질문은 접어두고 일단 결과부터보면 setInterval 함수 실행 시 user의 값을 state에 저장된 값으로 업데이트하면 값이 제대로 업데이트되지 않는다.

 

이유는, setInterval의 내부에 있는 user는 초반 렌더 시 useState를 통해 초기화 한 값이고, 그 이후에 업데이트가 이루어지지 않기 때문이다. (자바스크립트의 클로저 개념에 대해 어느정도 알고 있으면 이해가 잘 될 것 같다.)

 

아무튼 이럴 때 useRef가 쓰일 수 있다.

 

#엘리스트랙 #엘리스트랙후기 #리액트네이티브강좌 #온라인코딩부트캠프 #온라인코딩학원 #프론트엔드학원 #개발자국비지원 #개발자부트캠프 #국비지원부트캠프 #프론트엔드국비지원 #React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript