리액트만의 특별한 개념, hook, 상태관리, 생명주기(Life Cycle) 에 대한 이해가 조금 부족한 것 같아서, useEffect와 useState를 공부하면서 해당 개념도 같이 이번 기회에 정리하고자 한다.
보통 유저들의 정보는 서버의 데이터베이스에 저장된다. 예를 들어, 유저의 아이디나, 비밀번호, 비밀번호를 찾기 위한 질문/답에 관한 정보는 서버의 데이터베이스에 저장되어 있다. 이런 정보들은 보통 클라이언트(프론트엔드) - 서버(백엔드) - 데이터베이스의 과정을 거쳐 전달된다.
그러나 확인 버튼을 누르기 전 댓글이나, 설문 참여를 누르기 전 버튼 선택과 같은 정보들은 언제든지 바뀔 수 있기에, 이런 정보들까지 클라이언트에서 서버까지 도달하는 과정을 거친다면 몹시 비효율적인 네트워크 비용을 치뤄야 한다.
따라서, 이렇게 임시적으로 저장되어도 무방한 정보들은 클라이언트 단에서 임시적으로 저장하는 것이 더 효율적이다. React에서 state는 바로 이렇게 임시적인 정보를 저장하기 위해 만들어진 것이다.
📢 Prop와 State는 어떻게 다른가?
Props가 특정 Component가 상위 Component로부터 내려받은 속성이라면,
State는 각각의 Component가 개별적으로 가지는 값이다.
Props는 상위 컴포넌트로부터 내려받은 값이므로 컴포넌트 내부에서 값을 임의로 변경할 수 없다.
리액트의 생명주기는 크게 생성(Mount), 업데이트(Update), 제거(UnMount) 로 구분지을 수 있다.
생성(Mount): 컴포넌트가 페이지에 나타날 때
업데이트(Update): 컴포넌트 내부의 값이 업데이트 될 때
제거(Unmount): 컴포넌트가 페이지에 사라질 때
각 주기는 Render -> Commit의 단계를 밟는다.
Render:
1) Component, State, Props를 로드한다.
2) Rendering
Commit
1) 기존의 값을 갖는 Virtual DOM과 업데이트 된 값을 갖는 Virtual DOM값을 서로 비교
2) 다른 점이 발견된다면 바뀐 부분만 실제 DOM에 업데이트
3) useEffect 실행
이전 함수형 컴포넌트에서는 기존 클래스형 컴포넌트에선 가능했던 상태 관리나, 생명 주기 사용을 못한다는 단점이 존재했다. 그러나 React가 업데이트 되면서 클래스형 컴포넌트에서만 가능했던 기능들을 함수형 컴포넌트에서도 사용할 수 있게 되었다.
useState
useState는 함수형 컴포넌트에서 상태 관리를 할 수 있도록 하는 hook이다.
import React, {useState} from 'react'
const Counter = ()=> {
const [value, setValue] =useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다
</p>
<button onClick={()=> setValue(value+1)}>+1</button>
<button onClick={()=> setValue(value-1)}>-1</button>
</div>
)
}
export default Counter
useEffect
useEffect는 함수형 컴포넌트에서 생명 주기(componentDidMount, componentDidUpdate, componentWillUnMount)를 사용할 수 있도록 하는 hook이다.
import { useEffect, useState } from "react"
const Info =()=>{
const [name, setName] = useState('')
const [nickname, setNickname] = useState('')
useEffect(()=>{
console.log('렌더링이 완료되었습니다.');
console.log({
name, nickname
})
})
const onChangeName =(e)=>{
setName(e.target.value)
}
const onChangeNickname =(e)=>{
setNickname(e.target.value)
}
return(
<div>
<>
<input value={name} onChange ={onChangeName}/>
<input value={nickname} onChange ={onChangeNickname}/>
</>
<div>
<b>이름 : </b> {name}
</div>
<div>
<b>닉네임 : </b> {nickname}
</div>
</div>
)
}
export default Info;
위 코드 실행시 component가 mount될때 (처음 웹페이지에 등장), component가 update될때 (input을 입력할 때)마다 console.log가 실행되는 모습을 확인 할 수 있다. (각 생명주기의 Commit과정에서 useEffect가 실행되기 때문)
useEffect(()=>{
console.log('렌더링이 완료되었습니다.');
console.log({
name, nickname
})
}, [])
useEffect(()=>{
console.log('렌더링이 완료되었습니다.');
console.log({
name, nickname
})
}, [name])
function startTimer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount(prevCount => prevCount + 1), 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
해당 컴포넌트는 의존성 배열(useEffect의 두번째 인자)로 빈 배열을 넣어주었으므로 mount될 때만 한 번 실행된다. setInterval에 의해 타이머가 실행된다. 이때, useEffect의 return에서 clearInterval을 확인 할 수 있는데, 해당 컴포넌트가 unmount되면 timer가 clear된다.
Unmount는 특정 동작을 한다기 보단, setInterval, setTimeout과 같은 함수를 써주고 난 후 생성된 특정 라이브러리 인스턴스를 제거하는 역할을 해준다고 생각하면 편하다 (C에서 malloc하고 free하듯이)