본문 바로가기

이것저것 스터디📚/React - 공식 문서

React 공식문서 -useState

- useState는 컴포넌트에 state 변수를 추가할 수 있게 해주는 React 훅이다.


Avoiding recreating the initial state(초기 state 다시 생성하지 않기)

- useState의 매개변수에는 초기 state 값으로 설정할 값이 들어가고, 값은 모든 데이터 타입이 허용된다.

- 초기 state 값으로 함수를 전달하면, 이는 초기화 함수로 취급되며 이 함수는 순수해야 하고 인자를 받지 않아야 하며 반드시 어떤 값을 반환해야 한다.

- 즉, React는 컴포넌트를 초기화할 때 초기화 함수를 호출하고, 그 반환값을 초기 state로 저장한다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
  // creacteInitialTodos 함수는 순수해야 하고 인자를 받지 않아야 하며 반드시 어떤 값을 반환해야 한다.

- 위 예제에서 createInitialTodos()의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 함수를 호출하기 때문에, 이는 불필요할 수 있다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  // 또는
    const [todos, setTodos] = useState(()=> createInitialTodos());
  // ...

- 대신 위의 예제처럼 함수 자체를 전달하게 되면, TodoList 컴포넌트가 처음 마운트될 때만 createInitialTodos 함수를 호출한다.

- 이는 리액트의 동작 방식과 관련이 있는데, 리액트 컴포넌트는 렌더링될 때 함수나 연산을 실행하는 것이 아닌, 렌더링 결과를 기반으로 가상 DOM을 구성하고 가상 DOM과 이전에 생성된 가상 DOM과 비교하여 변경된 부분만 실제 DOM에 적용되는 것이 리액트의 핵심 메커니즘이다.

- 즉, 컴포넌트가 마운트되었을 때 초기값으로 설정된 함수는 단 한번만 실행되고, 이후의 리렌더링에서는 초기값에 전달된 함수는 실행되지 않는다.

 

* 내 정리

-   const [todos, setTodos] = useState(createInitialTodos()); => 리액트가 기억하는 값은 렌더링 "결과"이기 때문에, creacteInitialTodos가 반환하는 결과 값을 항상 알아야함.

-   const [todos, setTodos] = useState(createInitialTodos); => 리액트가 기억하는 값은 createInitialTodos 함수이기 때문에, 컴포넌트가 최초로 마운트될 때, 상태값이나 프롭스(props) 등을 초기화해야 한다. 이를 위해서 리액트는 상태 초기화를 위해 useState 훅을 사용할 때 전달한 함수를 최초 마운트 시에 한 번 호출한다. 이 함수 호출 결과를 초기 상태로 사용하여 컴포넌트의 최초 상태를 설정한다. 그 후에는 상태를 업데이트하면 함수 호출이 아니라 이전에 저장된 상태값을 사용하여 리렌더링하게 된다.


I've updated the state, but the screen doesn't update(state를 업데이트해도 화면이 바뀌지 않습니다)

- React는 Object.is 비교 결과 다음 state가 이전 state와 같으면 업데이트를 무시한다.

// 공식문서 예시
obj.x = 10;  // 🚩 Wrong: mutating existing object
             // 🚩 잘못된 방법: 기존 객체를 변이
setObj(obj); // 🚩 Doesn't do anything
             // 🚩 아무것도 하지 않습니다.
             
// ✅ Correct: creating a new object
// ✅ 올바른 방법: 새로운 객체 생성
setObj({
  ...obj,
  x: 10
});


// 내가 만든 예시
import "./styles.css";
import { useState } from "react";

export default function App() {
  const object = { x: 1, y: 10 };

  const [obj, setObj] = useState(object);

  const updateBtnHandler = () => {
    // obj.x = 10;
    setObj({ ...obj, x: 10 });
  };

  return (
    <div className="App">
      <button onClick={updateBtnHandler}>update</button>
      <div> x = {obj.x}</div>
      <div> y = {obj.y}</div>
    </div>
  );
}

 

* Object.is를 사용해서 기존 객체를 변이했을 때의 모습

* Object.is() : https://pocoding.tistory.com/98


I'm trying to set state to a function, but it gets called instead(state의 값으로 함수를 설정하려고 하면 설정은 안되고 대신 호출됩니다)

- state에 함수를 넣을 수 없다.

const [fn, setFn] = useState(someFunction);

function handleClick() {
  setFn(someOtherFunction);
}

- 위 예제에서, React는 someFunction을 초기화 함수로 여기고 someOtherFunction을 업데이터 함수라고 받아들이기 때문에, 이들을 호출해서 그 결과를 저장하려고 시도한다

- 함수 자체를 저장하길 원하면 두 경우 모두 함수 앞에 () => 를 넣어야 한다.

const [fn, setFn] = useState(() => someFunction);

function handleClick() {
  setFn(() => someOtherFunction);
}

- 이렇게 하면 React는 전달한 함수를 값으로써 저장한다.


useState를 활용한 나만의 예제

- useState를 활용한 예제를 만들어보려고 한다. 나는 위의 "초기 state를 생성하지 않기" 챕터의 내용을 기반으로 예제를 만들어볼 생각이다.

- useState에 초기 값으로 함수를 전달했을 때, 과연 모든 리렌더링마다 함수를 호출하는지 확인해보는 예제를 만들어보겠다.

// 1️⃣ 함수가 호출되어 반환하는 값을 전달
import { useState } from "react";
const makeInitialName = () => {
  console.log("실행");
  return "poco";
};

export const Count = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState(makeInitialName());

  const countUpdateBtnHandler = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <button onClick={countUpdateBtnHandler}>update count</button>
      <div>{count}</div>
      <div>{name}</div>
    </>
  );
};

// 2️⃣ 함수 그 자체를 전달
import { useState } from "react";
const makeInitialName = () => {
  console.log("실행");
  return "poco";
};

export const Count = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState(makeInitialName);

  const countUpdateBtnHandler = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <button onClick={countUpdateBtnHandler}>update count</button>
      <div>{count}</div>
      <div>{name}</div>
    </>
  );
};

- 위 컴포넌트에서는 name과 count를 state 변수로 가지고 있습니다.

- count의 경우, setCount를 이용해서 이전 state에서 1이 증가된 값을 렌더링할 것입니다.

- name의 경우, state 변수이지만, 이 컴포넌트에서는 name을 변화시지키는 않겠습니다.

- 대신, name state의 초기값을 makeInitialName 이라는 함수로 전달하겠습니다.

- 1번 예제의 경우, update count 버튼을 클릭해서, setCount를 통해 Count 컴포넌트가 리렌더링이 발생하고, 리렌더링이 발생할 때마다, makeInitialName이 호출되어 console.log("실행") 확인됩니다. 즉, Count 컴포넌트가 최초 마운트되는 시점과 리렌더링이 발생하는 시점 모든 시점에서 makeinitialName 함수는 호출됩니다.

- 2번 예제의 경우, update count 버튼을 클릭해서, setCount를 통해 Count 컴포넌트가 리렌더링이 발생하더라도, makeInitialName 함수는 호출되지 않기 때문에, console.log("실행")은 최초 Count 컴포넌트의 마운트시에만 확인됩니다.

- 2번 예제의 경우, useState의 초기 설정자 함수 자체가 콜백으로 들어가있기 때문에 동일한 함수이기 때문에, 리렌더링 시에도 실행되지 않지만, 1번 예제의 경우, useState에 초기 설정자 함수 실행은 매번 실행한다.


* 참고 : React 공식문서(https://react-ko.dev/learn)