909 Devlog

[React] state, useState 본문

React/개념

[React] state, useState

구공구 2023. 9. 3. 21:38
728x90

👋 state, useState를 알아보기 전에, 간단한 예제를 한번 봅시다.


 

const Example = () => {
    return (
        <div>
            <span>0</span>
            <button>버튼</button>
        </div>
    );
};

export default Example;

컴포넌트에 span과 button이 존재합니다.

 

버튼을 클릭하면 span에 있는 숫자를 1씩 커지게 하고 싶은데, 어떻게 하면 좋을까요?

일단 막 생각나는 대로 한번 작성해 보겠습니다.

 

const Example = () => {
    let index = 0;
    return (
        <div>
            <span>{index}</span>
            <button onClick={() => index++}>버튼</button>
        </div>
    );
};

export default Example;

index를 let 변수로 선언하고 0으로 초기화했고

span에는 index를 렌더링 하도록 {} (중괄호)에 씌워서 표시했으며

button이 클릭됐을 때는 index를 1 증가시키는 함수를 작성했습니다.

 

이렇게 하면 될 것 같지 않나요?

한번 버튼을 눌러보겠습니다 👌

숫자가 생각대로 증가되지 않네요 😅

📌 state란?


컴포넌트에서는 상호 작용의 결과로 화면의 내용을 변경해야 하는 경우가 있습니다.

input에 값을 입력한다던지, 버튼을 누르는 경우 등을 말합니다.

React에서는 이런 종류의 컴포넌트별 메모리를 state라고 부릅니다.

 

간단히 말해 state는 변수처럼 사용할 수 있는데요, 리액트에게 state에 할당한 데이터를 기억하라고 말하는 겁니다.

개념을 읽는다고 해서 이해가 되는건 아니니, 그냥 한번 써 봅시다.

👨‍💻 state 사용 방법


state는 react 모듈에서 useState를 불러와서 사용합니다.

import { useState } from "react";

useState를 불러왔다면, 바로 사용해 봅시다!

일반 변수처럼 할당하고, 괄호 안에 초기값을 넣어줬습니다.

위 예시와 같은 변수명인 index를 사용할 거예요.

그리고 button에 있는 onClick을 잠시 지워주도록 하겠습니다.

const index = useState(0);
import { useState } from "react";

const Example = () => {
    const index = useState(0);
    return (
        <div>
            <span>{index}</span>
            <button>버튼</button> // onClick을 지웠습니다.
        </div>
    );
};

export default Example;

위와 같은 코드를 사용해서 렌더링 했더니, 콘솔에 오류가 떴습니다.

흠.. 어디서 오류가 났을까요? 🤔

 

사실 state는 배열입니다.

따라서 console.log(index)를 해보면 다음과 같이 배열이 뜨게 됩니다.

배열을 그냥 화면에 렌더링 하려고 했으니 리액트에서 경고를 띄운 것 이죠.

 

배열 내부에는 2가지 요소가 존재하는데,

  • 첫 번째로는 담고 있는 데이터, 
  • 두 번째로는 해당 데이터를 변경시키기 위한 설정자(setter) 함수

를 담고 있습니다.

 

따라서 아래 코드와 같이 index [0]을 사용하면 오류 없이 데이터를 띄울 수 있습니다!

import { useState } from "react";

const Example = () => {
    const index = useState(0);
    return (
        <div>
            <span>{index[0]}</span>
            <button>버튼</button>
        </div>
    );
};

export default Example;

그런데, state를 사용하기 위해 매번 뒤에 [0]를 붙여줘야 할까요? 너무 귀찮고 비효율적이지 않나요?

이럴 때를 위해, 자바스크립트에는 구조 분해 할당이란 게 존재합니다. 왼쪽 링크는 MDN 문서 링크이니 한 번 보고 오시는 것을 추천드립니다.

 

그럼 이제 state를 아래와 같이 사용할 수 있습니다.

const [index, setIndex] = useState(0);

첫 번째 요소인 데이터를 index라고 선언하고, 두 번째 요소인 설정자(setter) 함수를 setIndex라고 선언했습니다.

함수의 이름은 첫 번째 요소의 이름과 관계없이 마음대로 하셔도 상관없지만, "set(데이터 이름)"이라고 이름을 짓는 게 관습입니다.

 

그럼 제 목표인 "버튼을 누르면 span에 있는 숫자를 증가시키고 싶어"를 달성해 봅시다.

 

잠시 지웠었던 onClick을 다시 작성해 봅시다.

import { useState } from "react";

const Example = () => {
    const [index, setIndex] = useState(0);
    return (
        <div>
            <span>{index}</span>
            <button onClick={() => index++}>버튼</button>
        </div>
    );
};

export default Example;

이제 제대로 작동할까요?

또 에러가 생겼습니다... 😅

근데 에러 문구가 익숙하지 않나요?

"Assignment to constant variable"

일반 자바스크립트를 사용할 때 한번 보셨지 않나요?

 

맞습니다.

const로 선언된 변수를 변경하려고 할 때 생기는 에러입니다.

우리가 구조 분해 할당으로 state의 데이터를 const로 선언했기 때문에 에러가 발생한 것입니다.

 

그럼 let으로 선언해야 할까요? 🤔

아니요. ❌

 

제가 배열에 요소가 2가지 존재한다고 말했던 것 기억나시나요?

두 번째 요소는 첫 번째 요소를 변경시키기 위한 함수라고 했었습니다.

 

state는 index = 100과 같은 변수 형식의 할당을 막기 위해 const로 선언하며, 그 const 변수를 바꿀 수 있는 state의 두 번째 요소, 설정자 함수가 존재합니다.

 

바로 사용해 보러 갑시다.

*주의* 설정자 함수의 괄호 내부에 값을 할당(index++, index += 1 등)하려고 하면 좀 전에 봤던 오류가 계속 뜰 것입니다.

대신, index + 1과 같이 변경하려고 하는 값을 넣어주세요.

값을 할당하기 위해 설정자 함수가 존재하는겁니다.

import { useState } from "react";

const Example = () => {
    const [index, setIndex] = useState(0);
    return (
        <div>
            <span>{index}</span>
            <button onClick={() => setIndex(index + 1)}>버튼</button> // ++가 아닌 + 1임을 유의
        </div>
    );
};

export default Example;

드디어 성공했습니다!

📌 state를 사용할 때 알아야 할 것, 유의해야 할 


🎯 설정자 함수를 사용하는 두 가지 방법

setIndex(index + 1);

setIndex((current) => {
    return current + 1
})

첫 번째 방법인 설정자 함수의 괄호에 직접 변경할 값을 넣는 방법과, 두 번째 방법인 함수를 넣는 방법이 있습니다.

현재 값을 기반으로 state를 변경하고자 하는 경우에는 두 번째 방법인, 함수를 넣는 방법을 권장합니다.

 

🎯 useState 배열의 두 번째 요소인 설정자 함수를 호출하면 해당 함수가 있는 컴포넌트가 다시 렌더링 됩니다.

예시를 통해 알아봅시다.

import { useState } from "react";

const Example = () => {
    const [index, setIndex] = useState(0);
    console.log("렌더링"); // 추가
    return (
        <div>
            <span>{index}</span>
            <button onClick={() => setIndex(index + 1)}>버튼</button>
        </div>
    );
};

export default Example;

이전 코드에서 console.log("렌더링"); 만 추가했습니다.

페이지를 새로고침 하면 컴포넌트가 렌더링 되고, 컴포넌트 내부에 기본적으로 작성되어 있는 함수는 바로 실행되니 콘솔에 "렌더링"이 뜨게 됩니다.

첫 렌더링이 완료된 후 버튼을 누르면 useState의 설정자 함수가 호출되어 state의 값을 변경하게 됩니다.

state의 값이 변경되면 컴포넌트는 바뀐 state 값을 표시하기 위해 다시 렌더링 될 것입니다. 따라서 페이지를 처음 렌더링한 것과 같이 콘솔에 "렌더링"이 뜨게 됩니다.

 

🎯 다른 컴포넌트에서 같은 이름의 state가 존재해도 state는 지역 변수이기 때문에 충돌하지 않습니다.

import Example from "./test copy";

function App() {
    return (
        <div className="App">
            <Example />
        </div>
    );
}

export default App;

이전에는 위 코드와 같이 Example 컴포넌트를 하나만 사용했었습니다.

import Example from "./test copy";

function App() {
    return (
        <div className="App">
            <Example />
            <Example />
            <Example />
        </div>
    );
}

export default App;

이 코드처럼 컴포넌트를 3개로 늘리면 어떻게 될까요?

내부의 index가 모두 같은 값을 갖게 될까요?

 

같은 이름의 state라도, state는 지역 변수이기 때문에 모두 개별 값을 갖게 됩니다.

 

🎯 state는 다양한 type의 데이터를 가질 수 있습니다.

const [index, setIndex] = useState(0);
const [imString, setImString] = useState("문자열입니다");
const [imBoolean, setImBoolean] = useState(true);
const [imAry, setImAry] = useState(["후라이드", "양념"]);
const [imObj, setImObj] = useState({
    치킨: "맛있다",
    가지무침: "싫어요",
});

 

🎯 배열, 객체 state의 설정자 함수 사용 방법

위에서 배열과 객체를 state에 할당했었습니다.

배열과 객체의 요소를 변경하기 위해서는 

<button onClick={() => {
	setImAry(imAry[0] = "간장");
	}}
>배열 버튼
</button>

이렇게 하면 안 되고

<button
    onClick={() => {
        const tempAry = [...imAry];
        tempAry[0] = "간장";
        setImAry(tempAry);
    }}
>배열 버튼
</button>

이렇게 해야 합니다.

 

위에서 말했듯이, 설정자 함수의 괄호 내부에 값을 할당하는 할당문을 넣으면 안됩니다.

그 대신 변경하고 싶은 값을 넣으라고 했었죠?

imAry, imObj 자체가 변해야 리액트에서 감지할 수 있기 때문에 위 코드처럼 임시 변수를 만들어서 임시 변수의 요소를 바꾼 뒤 설정자 함수에 변수를 전달해야 데이터를 모두 변경합니다.

코딩애플 강좌에서 좀 더 자세한 설명을 보실 수 있습니다.

 

🎯 리액트는 가상돔을 사용해 요소를 변경합니다.

위 GIF를 보면 후라이드 -> 간장, 맛있다 -> 너무너무 맛있다 부분만 부분적으로 변경되는 것을 볼 수 있습니다.

 

state가 변경되면 해당 컴포넌트가 다시 렌더링 되긴 하지만, 모든 요소가 다 다시 렌더링 되는 것은 아닙니다.

리액트는 가상돔을 사용해서 변경된 부분만 따로 렌더링해 성능을 향상합니다.


본 포스팅에 사용한 코드입니다.

import { useState } from "react";

const Example = () => {
    const [imAry, setImAry] = useState(["후라이드", "양념"]);
    const [imObj, setImObj] = useState({
        치킨: "맛있다",
        가지무침: "싫어요",
    });
    return (
        <div>
            <ul>
                <li>{imAry[0]}</li>
                <li>{imAry[1]}</li>
            </ul>
            <h1>{imObj.치킨}</h1>
            <h1>{imObj.가지무침}</h1>
            <button
                onClick={() => {
                    const tempAry = [...imAry];
                    tempAry[0] = "간장";
                    setImAry(tempAry);
                }}
            >
                배열 버튼
            </button>
            <button
                onClick={() => {
                    const tempObj = { ...imObj };
                    tempObj.치킨 = "너무너무 맛있다";
                    setImObj(tempObj);
                }}
            >
                객체 버튼
            </button>
        </div>
    );
};

export default Example;
728x90

'React > 개념' 카테고리의 다른 글

[Redux] subscribe  (0) 2023.09.07
[React] Reducer  (1) 2023.09.06