state
를 사용하기 위한 훅으로, 함수형 컴포넌트에서는 기본적으로 state라는 것을 제공하지 않기 때문에 클래스 컴포넌트처럼 state를 사용하려면 useState()
훅을 사용해야 한다.
state
값이 변경될 때 마다 리렌더링 된다.
state
를 직접 수정하면 리액트는 render
함수를 호출하지 않아서 렌더링이 일어나지 않는다. 그래서 setState
를 호출하여 state
를 변경해야 리액트 엔진이 render
함수를 이용해서 렌더링을 실행한다.
Hook이란?
- Hook은 React 16.8에 새로 추가된 기능으로 Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다.
- Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수이다.
- Hook은 최상위에서만 호출해야 한다. 반복문, 조건문, 중첨된 함수 내부에서 실행할 수 없다.
- Hook은 React 함수 컴포넌트 내부에서만 호출해야 한다.
import { useState } from 'react'
useState
를 컴포넌트 안에서 호출한다. useState
를 호출한다는 것은 state
라는 변수를 선언하는 것과 같으며, 이 변수의 이름은 아무 이름으로 지어도 된다.
일반적인 변수는 함수가 끝날 때 사라지지만, state
변수는 React에 의해 함수가 끝나도 사라지지 않는다.
기본 구문
const [변수명, set함수명] = useState(상태 초기값)
매개 변수
변수명
: 상태 값 저장 변수, 상태 초기값을 지정해주면 state에 그 값이 담긴다.
set함수명
: 상태 값 갱신 변수, 상태 변경 함수에 어떤 값을 부여하던 그 값으로 업데이트하고 리렌더링 한다.useState를 콘솔창에서 보면 순서대로 초기값과 state를 바꿀 때 사용하는 함수를 갖는다.
state
변수는 구조분해할당을 사용하여 선언한다.function CheckboxExample() {
// 새로운 state 변수를 선언하고, 여기서는 이것을 isChecked 라고 부른다.
const [isChecked, setIsChecked] = useState(false);
}
// 위의 구조분해할당을 풀어쓰면 아래와 같다.
const stateHookArray = useState(false);
const isChecked = stateHookArray[0];
const setIsChecked = stateHookArray[1];
아래는 버튼을 클릭할 때 마다 숫자가 1 증가하는 counter 예제다.
아래와 같이 카운트를 함수의 변수로 선언에서 사용하게 되면 버튼 클릭 시 카운트 값을 증가시킬 수는 있지만, 재렌더링이 일어나지 않아 새로운 값이 화면에 표시되지 않는다.
버튼을 클릭한 후 증가된 count 값을 확인하려면 콘솔창에서 확인해야 한다.
import React, { useState } from 'react';
function Counter() {
const count = 0;
return (
<div className='plus'>
<div>{count}번 클릭했습니다.</div>
<button onClick={() => count = count + 1}></button>
</div>
);
}
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increase = () => {
setCount(count + 1) // setCount 함수를 통해 count 값을 1씩 증가시킴
}
return (
<div className='plus'>
<div>{count}번 클릭했습니다.</div> // 현재 카운트 숫자 표시(초기값을 0으로 설정함)
<button onClick={increase}>plus</button>
</div>
);
}
아래의 코드에서 두 개의 titleChangeHandler는 대체로 모두 통할 것이다.
리액트는 상태 업데이트를 즉시 처리하지 않고 예약한다. 따라서 첫 번째 방법을 사용하여 다수의 업데이트를 동시에 예약할 경우 오래되었거나 잘못된 상태에 의존하게 될 수도 있다.
하지만 두 번째 방법의 경우 리액트가 제공하는 내부 함수에서 예약된 모든 상태 업데이트를 기억하고선 상태가 항상 최신 상태가 되도록 보장해 준다. 따라서 두 버째 방법이 항상 최신 상태로 작업할 수 있는 더 안전한 방법이다.
const [userInput, setUserInput] = useState({
enteredTitle: '',
enteredDate: '',
});
const titleChangeHandler = (e) => {
setUserInput({
...userInput,
enteredTitle: e.target.value,
});
};
// 이전의 상태에 의존해서 상태를 업데이트하는 올바른 방법
const titleChangeHandler = (e) => {
setUserInput((prevState) => {
return { ...prevState, enteredTitle: e.target.value };
});
};
setCount((currnetValue) => currentValue + 1)
와 같이 함수를 사용해야 한다.import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increase = () => {
setCount((prevState) => prevState + 1)
}
return (
<div className='plus'>
<div>{count}번 클릭했습니다.</div> // 현재 카운트 숫자 표시(초기값을 0으로 설정함)
<button onClick={increase}>plus</button>
</div>
);
}
참고
useState lazy initialization
(게으른 초기화)는 useState
초기값에 직접적인 값 대신에 함수를 넘기는 것을 말한다.
useState
의 인자에는 number
, string
, array
, object
어떤 것이든 상태 초기값으로 넣어 할당 할 수 있지만, 함수도 초기값으로 할당 할 수 있다.
useState lazy initialization
는 초기값이 복잡한 연산을 포함하고 있을 때 사용하며, useState lazy initialization
함수는 오직 state가 처음 만들어 질 때만 실행되고, 이후 다시 리렌더링이 된다면 이 함수의 실행은 무시된다.
- 초기값이 복잡한 연산의 예
localStorage
의 접근filert
,map
,find
등의 배열 조작new Date()
- 그 외 일반적인 함수를 통해서 값을 구해야 하는 경우
useState
는 그 함수가 처음 렌더링 될 때 작동하며, 이는 todoData
state의 초기값을 만든다. setTodoData
가 실행되면, 전체 함수가 다시 실행되며, todoData
의 값을 업데이트 한다. 이는 todoData
의 값이 변경될 때 마다 리렌더링을 발생시키며, 다시 말해 이 초기값은 다시 쓰일 일이 없게 된다.
localStorage
의 값은 최초 렌더링 시 한 번만 불러오면 된다. 그러나,setState
의 초기값에localStorage
의 불러오는 코드를 직접적인 값으로 넣게 된다면 리렌더링 될 때마다 불러오므로 이는 필요없는 계산을 계속해서 하게 되는 것이다.
- 그러나 화살표 함수로 넣게 된다면 처음 렌더링 될 때 한번만
localStorage
의 값을 불러오기 때문에 불필요한 계산을 막을 수 있다.// 초기값에 직접적인 값을 바로 넣은 상태 const [todoData, setTodoData] = useState(JSON.parse(window.localStorage.getItem('localData')) || dummyData); // 초기값에 즉시 실행 화살표 함수로 넣은 상태 const [todoData, setTodoData] = useState(() => JSON.parse(window.localStorage.getItem('localData')) || dummyData);