state는 리엑트의 핵심 개념이고, 왜 핵심개념으로 여겨지는지 useState에 대해 심층적으로 알아보면 더 이해가 될 것 같았다.
또한, 왜 let키워드가 아닌 const를 사용하는지에 대해서도 알아보자.
useState는 리엑트 라이브러리에서 제공해주는 훅이고, 'react'로 부터 임포트해와서 사용해야한다.
이는 컴포넌트 함수가 다시 호출되는 곳에서 변경된 값을 반영하기 위해 state로 값을 정의할 수 있게 해주는 함수이다.
리엑트에서 useState 훅을 사용하여 상태를 변경할때,
왜 새로운 값을 바로 재할당하는 로직을 쓰지않고 state를 갱신하는 함수로만 변경을 할 수 있는건가?
import { useState } from "react";
import Card from "../UI/Card";
import ExpenseDate from "./ExpenseDate";
import "./ExpenseItem.css";
function ExpenseItem(props) {
const [title, setTitle] = useState(props.title);
const handleClick = () => {
setTitle("upDated!!");
console.log(title);
};
return (
<Card className="expense-item">
<ExpenseDate date={props.date} />
<div className="expense-item__description">
<h2>{title}</h2>
<div className="expense-item__price">${props.amount}</div>
</div>
<button onClick={handleClick}>Change Title</button>
</Card>
);
}
export default ExpenseItem;
-> 왜냐하면 이 갱신함수를 호출하는 것은 어떤 변수에 새로운 값을 할당하는게 아니라 특별한 변수(setTitle())로 시작하기 때문이다.
그것은 메모리 어딘가에서 리엑트로 관리된다.
그리고 state를 업데이트하는 하는 함수를 호출할 때
이 특별한 변수(setTitle())는 단지 새로운 값만 받는것이 아니다.
중요한건
내가 호출한 컴포넌트 함수(ExpenseItem.js)는 컴포넌트 안에서 사용되는 해당 state를 업데이트하는 함수이다.
그리고 갱신함수를 통해 state가 변경이 시작되면, useState로 상태를 초기화 했던 곳에서 다시 실행 된다.
즉, state가 변할 때, 이 컴포넌트 함수를 다시 호출하고 싶다면
이 state를 업데이트 하는 함수(갱신함수)를 호출하면 되는것이다.
이 갱신함수를 호출해서 해당 state에 새로운 값을 할당하고 싶다고 리엑트에게 전달하고 그 다음 함수는 useState로 state가 등록된 컴포넌트는 재평가 되어야 한다고 리엑트에게 전달한다.
그러고나서 리엑트는 다시 컴포넌트를 실행하고 jsx코드도 다시 평가한다.
그리고 나서 변화가 감지된 코드를 반영하여 화면에 렌더링하게 되는것이다.
useState를 사용해서 보통 상태를 등록하게 된다.
여기서 상태인 state는 호출된 컴포넌트를 위한것으로, 해당 컴포넌트의 인스턴스를 위해서 등록하는 state이다.
function App() {
const expenses = [
{ date: new Date(2023, 5, 8), title: "Car Insurance", amount: 230.45 },
{ date: new Date(2022, 0, 12), title: "Oil price", amount: 630.44 },
{ date: new Date(2023, 9, 3), title: "car maintenance", amount: 110.45 },
];
return (
<div>
<NewExpense />
<Expenses expenses={expenses} />
</div>
);
}
export default App;
function Expenses({ expenses }) {
return (
<Card className="expenses">
{expenses.map((item, index) => {
return (
<ExpenseItem
key={index}
date={item.date}
title={item.title}
amount={item.amount}
/>
);
})}
</Card>
);
}
export default Expenses;
현재 위 두개의 코드예시에서
ExpenseItem컴포넌트가 Expenses컴포넌트에서 App.js에서 props로 내려준 데이터 갯수만큼 map으로 전달하여 사용되고 있다.
즉, 현재 3개의 특정 컴포넌트 인스턴스 가 사용되고 있다는 것인데
이들은 별도의 state를 갖고 있고 다른 상태와는 분리가 되어 있는걸 볼 수 있다.
쉽게말해, 컴포넌트 정의는 한번만 해놓고 매번 함수(컴포넌트)를 호출할 때마다 동일한 방법으로 새로운 별도의 state가 생성되지만 리엑트는 이를 독립적으로 관리한다.
각각 컴포넌트의 상태를 변경해도 다른 인스턴스 컴포넌트들은 영향을 받지 않는 것이다.
왜냐하면 자신들만의 state를 갖고 있기 때문이다.
한 개 이상의 컴포넌트를 생성하더라도, 컴포넌트별 인스턴스를 기반으로 한 독립적인 state를 갖는다.
따라서 한 요소에 이벤트(버튼클릭 같은)가 발생했을 때,
리엑트는 상태가 바뀌면서 컴포넌트형 함수와 해당 컴포넌트가 사용되는 곳에 있는 특정 인스턴스에서만 재평가 한다.
(위 ExpenseItem.js 예시에서 만약 다른 데이터는 변화없고 title상태만 클릭이벤트를 통해 업데이트 된 값을 재평가 받는것이다.)
State는 컴포넌트의 인스턴스별로 나누어져 있다.
그렇다면 새로운 값을 선언할 때 const키워드를 사용하고 있는걸까?
우선 먼저 기억해야할 부분은 등호를 사용하여 값을 할당하지 않고있다는 것이다.
이는 리엑트에서 잘못된 할당 방법이다.
대신 state를 업데이트하는 갱신함수를 호출하고, 구체적인 값은 리엑트에 의해 어딘가에서 관리되고있다.
useState를 호출하게되면 변수자체를 볼 수 없고 리엑트에게 관리 해야할 상태를 선언하는 것이다. 즉 함수만 호출하게 되는것이다.
절대로 등호 연산자로 새로운 값을 할당할 수 없기에 때문에 const 상수형 키워드를 사용해도 괜찮은 것이다.
리엑트는 useState가 반환하는 배열(구조분해할당)에서 가장 최신의 상태를 우리에게 제공해준다.
따라서 해당 컴포넌트가 재실행될 때마다 우린 항상 가장 최신상태의 화면을 볼 수 있는것이다.
state는 알아야할게 매우 많다.
어떻게 작동해야하는지 완벽하게 이해하지 못하면 더 많은 복잡한 리엑트 응용프로그램에서 기대한 값으로 업데이트되지않는 문제에 부딪힐 수 있다.
state를 사용하는것은 단순한데
useState를 사용해서 상태를 등록하면 항상 두개의 값(state와 state갱신함수)을 얻는데, 현재 상태값과 업데이트하는 함수를 가진다.
그리고 상태가 변할 때마다 업데이트 함수를 호출한다.
또한 jsx코드에 상태값을 출력하기위해서 등록한 첫번째 요소를 사용한다.
상태가 변한다? -> 컴포넌트형 함수 재실행 -> JSX코드 재평가