React | useState 함수형 업데이트, 초기값 렌더링

하영·2024년 8월 22일
1

React

목록 보기
10/17

개인과제를 하던 도중 경고창을 만들었는데 자꾸 두번씩 실행됐다...
setState를 활용해서 경고창을 띄우는거였는데 이게 값이 변하면서 렌더링 때문에 2번 호출되나? 하는 생각이 들었다.
결과적으로는 그냥 해당 코드가 실행되는 시점의 문제였지만, 아직 useState에 대해 개념이 잘 안 잡혔다고 생각돼서 좀 더 공부를 하다가 콜백함수 형태의 작성법을 알게 되었다.


useState 더 알아보기 (콜백함수)

01. 이전 값과 연관있는 setState 값 변경

state의 값을 변경할 때 직접 count +1 이런식으로 줘도 되고 효율충 리액트를 위해 함수형태로 작성해서 넣는 방법이 있다는건 알고있었다.

👩🏻‍💻 살펴볼 코드 예시

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


export default function App() {
  const [names, setNames] = useState(["김누구", "이누구"]);
  const [input, setInput] = useState("");

  const inputHandler = (e) => {
    setInput(e.target.value);
  };

  const buttonInputHandler = () => {
    setNames((prev) => {
      // console.log("prev => ", prev);
      return [...prev, input];
    });
  };
  
  return (
    <div>
      <input type="text" value={input} onChange={inputHandler} />
      <button onClick={buttonInputHandler}>버튼</button>
      
      {names.map((name, idx) => {
        return <p key={idx}>{name}</p>;
      })}
    </div>
  );
}

위 코드는 inputbutton 만 있는 아주 기본형태의 UI이고 input에 값(이름)을 입력하고 버튼을 클릭하면 map 메소드에 의해 이름이 아래 보여지는 코드이다.

 결과 및 콘솔 확인

여기서 봐야할 부분은 바로 buttonInputHandler 이벤트이다.

const buttonInputHandler = () => {
    setNames((prev) => {
      // console.log("prev => ", prev);
      return [...prev, input];
    });
  };
// 

⭐️ setState를 변경할 때 이전 값과 연관되어 있는 경우 >> 콜백함수 형태로 return 하는 것이 좋다.

예시의 경우 초기값과 이전에 있던 이름 그 다음으로 사용자가 입력한 이름이 넣어져야 해서 콜백함수 형태로 작성하는 걸 권장한다.


02. 초기값이 엄청 무거운 작업이라면?

지금까지 연습했던 코드들도 그렇고 단순히 이름 몇개나 객체형태 몇개가 전부였는데 만약에 초기값이 무거운 작업일 때도 고려해봐야한다.
state의 특성은 값이 변경될 때마다 리렌더링이 된다는 것!

무거운 작업이 초기값으로 들어간 코드

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

// 🤯 무거운 작업
// 함수로 초기값 설정
const heavyWork = () => {
  console.log("렌더링 무거운 작업");
  return ["김누구", "이누구"];
};

export default function App() {
  const [names, setNames] = useState(heavyWork()); 
  //🚨 무거운 작업을 하는 함수를 초기값으로 바로 넣어버리기
  const [input, setInput] = useState("");

  const inputHandler = (e) => {
    setInput(e.target.value);
  };

  const buttonInputHandler = () => {
    setNames((prev) => {
      console.log("prev => ", prev);
      return [...prev, input];
    });
  };
  return (
    <div>
      <input type="text" value={input} onChange={inputHandler} />
      <button onClick={buttonInputHandler}>버튼</button>
      {names.map((name, idx) => {
        return <p key={idx}>{name}</p>;
      })}
    </div>
  );
}

✅ 결과 및 콘솔확인

input 창에 입력을 할 때마다, 버튼을 눌러서 추가할 때마다 리렌더링 되면서 무거운 작업이 반복 실행된다. > 😩😩 비효율적!


🔍 해결방법
초기값에 함수를 바로 넣어주는 것이 아니라 콜백함수로 넣어주면 간단하게 해결할 수 있다.

const [names, setNames] = useState(() => {
    return heavyWork();
  });

✅ 결과 및 콘솔확인


👩🏻‍💻 결론!

초기값을 지정할 때 무거운 작업을 해야한다면, 그 값을 바로 넣지 않고 우리가 원하는 값을 return 해주는 “콜백형태” 를 넣어주면 맨 처음 렌더링 될 때만 실행된다.


💡 정리

  1. state를 변경할 때 새로 변경할 값이 이전 값과 연관이 되어있다면 setState의 인자로 새로운 State를 return 하는 콜백함수를 넣어주는 것이 좋다.
setState((prevState) => {
    return [...prevState, newState];
  });
  1. useState를 활용해서 초기값을 받아올 때 ‘무거운 작업’을 해야한다면 useState 인자로 콜백함수로 받아온다면 state가 처음 만들어 질 때만 실행된다. 
    (이 후에 다시 리렌더링이 된다면, 이 함수의 실행은 무시된다.)

🧐 여기서 드는 의문점

초기에만 렌더링 되게 할거면 useEffect 의존성배열을 활용해서 쓰면 될텐데 굳이 함수로 초기값을 빼서 콜백함수로 넣어주어야 하는 이유가 뭘까?

useEffect의 목적은 외부에서 가져온 데이터를 동기화하기 위해 사용한다. 그래서 외부 API를 가져와서 fetch하거나 할 때 처음 렌더링 시에만 이 fetch 함수가 실행되게 하는게 일반적이다. 매번 리렌더링 될 때마다 이 외부데이터도 렌더링 시켜버리면 비용이 많이 들기 때문!

하지만 위 코드에서 초기값은 우리가 직접 만들었기 때문에 우리가 직접 관리할 수 있는 데이터이다. useEffect를 써도 되지만 useEffect 훅의 본 목적에 맞게 사용하는건 아니기에 안티패턴이 될 수 있다.

따라서 초기값이 외부에서 가져온 데이터로 렌더링하는것인지, 우리가 만든 데이터로 관리하는것인지를 판단하고 적절하게 사용해야한다.

profile
왕쪼랩 탈출 목표자의 코딩 공부기록

1개의 댓글

comment-user-thumbnail
2024년 8월 23일

당신은 Effect가 필요하지 않을 수 있습니다!

답글 달기