오늘은 리액트에서 제공하는 기본 훅(useState, useRef, useContext, useReducer)로 상태 관리를 하는 법에 대해 배웠다.
참고로 리액트에서 말하는 상태(state)는 컴포넌트 내에서 변하는 값이며, 수정 시 state의 값을 직접 바꾸기보단 set함수를 이용해 값을 변경한다. (이유: 리액트는 state가 변경될 때 화면을 리렌더링하는데, 이 state가 변한 것을 객체의 값이 아닌 객체의 주소 값이 변경된 것으로 감지하기 때문이다.)
그 중 useRef에 대해서, 처음엔 HTML dom element에 직접적으로 접근할 때 사용할 수 있다고 배웠었는데, 다른 용도(?)로도 쓸 수 있다고 들어서 그것에 대해 useState와 비교하며 정리해보려고 한다.
1. useState
하나의 단순한 상태를 관리하는 데 사용할 수 있다.
function App() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count - 1)}>-</button>
<span style={{ margin: "0px 16px" }}>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
2. useRef
상태가 바뀌어도 리렌더링 하지 않는 상태를 정의할 때 사용한다.
상태 변경 = UI 변경을 원치 않을 때 사용할 수 있다.
저번에 글을 썼을 때는 HTML의 DOM element에 직접 접근할 때 사용할 수 있다고 했었는데,
밑의 예시처럼 n초 후에 상태를 업데이트 할 때도 사용할 수 있다.
useState와 비교하여 사용법을 알아보자.
function App() {
const [user, setUser] = useState({ name: "", age: 0 });
return (
<>
<input defaultValue={user.name} onChange={(event) => setUser((user) => ({ ...user, name: event.target.value }))} />
<input type="number" defaultValue={user.age} onChange={(event) => setUser((user) => ({ ...user, age: Number(event.target.value) }))} />
<div>name: {user.name}</div>
<div>age: {user.age}</div>
</>
);
}
두 개의 input에서 값을 입력 받아 업데이트 된 값을 화면에 보여준다.
useState를 사용하면 입력 값 변경 시마다 set 함수를 이용해 값을 변경해야 한다.
위의 코드를 실행하고 컴포넌트의 업데이트 과정을 살펴보면, input에 값이 바뀔 때마다 모든 컴포넌트가 재렌더링된다.
그러나 어떤 경우엔 입력 시마다가 아닌 몇 초마다 값을 업데이트 하고 싶은 경우도 생길 것이다.
그럴 때 useRef를 사용할 수 있다.
function App() {
const [user, setUser] = useState({ name: "", age: 0 });
const nameRef = useRef(null);
const ageRef = useRef(null);
useEffect(() => {
setInterval(() => {
console.log("setInterval 실행...");
const newName = nameRef.current.value;
const newAge = Number(ageRef.current.value);
setUser({ name: newName, age: newAge });
}, 2000);
}, []);
return (
<>
<input ref={nameRef} defaultValue={user.name} />
<input ref={ageRef} type="number" defaultValue={user.age} />
<div>name: {user.name}</div>
<div>age: {user.age}</div>
</>
);
}
그렇다면 반대로 이런 생각이 들 수도 있다. 왜 굳이 useRef를 써야하나?
그럼 setInterval 함수 내에 useState를 이용해 값을 변경해보자.
function App() {
const [user, setUser] = useState({ name: "", age: 0 });
const nameRef = useRef(null);
const ageRef = useRef(null);
useEffect(() => {
setInterval(() => {
console.log("setInterval 실행...");
setUser({ name: user.name, age: user.age });
}, 5000);
}, []);
return (
<>
<input ref={nameRef} defaultValue={user.name} onChange={(event) => setUser((user) => ({ ...user, name: event.target.value }))} />
<input ref={ageRef} type="number" defaultValue={user.age} onChange={(event) => setUser((user) => ({ ...user, age: Number(event.target.value) }))} />
<div>name: {user.name}</div>
<div>age: {user.age}</div>
</>
);
}
왜 onChange로 입력 시마다 set함수를 실행시키면서, setInterval로 또 값을 업데이트 시키냐는 불필요한 질문은 접어두고 일단 결과부터보면 setInterval 함수 실행 시 user의 값을 state에 저장된 값으로 업데이트하면 값이 제대로 업데이트되지 않는다.
이유는, setInterval의 내부에 있는 user는 초반 렌더 시 useState를 통해 초기화 한 값이고, 그 이후에 업데이트가 이루어지지 않기 때문이다. (자바스크립트의 클로저 개념에 대해 어느정도 알고 있으면 이해가 잘 될 것 같다.)
아무튼 이럴 때 useRef가 쓰일 수 있다.
#엘리스트랙 #엘리스트랙후기 #리액트네이티브강좌 #온라인코딩부트캠프 #온라인코딩학원 #프론트엔드학원 #개발자국비지원 #개발자부트캠프 #국비지원부트캠프 #프론트엔드국비지원 #React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript