도찐개찐
[React] useState VS useEffect 본문
훅은 리액트 버전 ^16.8부터 사용할 수 있는데요
리액트 문서에서는 훅을
- "함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 연동, 연결 (hook into) 해주는 함수"
라고 이해하면 된다고 합니다!
클래스형 컴포넌트의 문제점들을 해결하기위해 나온 것이 바로 훅입니다.
훅은 함수형 컴포넌트에서 상탯값,생명주기,Ref 등 여러가지 기능들을 사용하게 해줍니다.
[리액트 공식문서 : hook의 장단점 & INTRO]를 보면 클래스형 컴포넌트가 리액트의 진입장벽을 높게 만든다고 합니다.
▼ 정리해보면 이렇습니다.
클래스형 컴포넌트의 단점
- 클래스형 컴포넌트에서 로직 재사용시에 사용하는 고차컴포넌트, 렌더속성값 패턴은 리액트 요소 트리를 깊게만든다. 따라서 성능에 부정적인영향과 개발 시 디버깅이 힘들어지는 문제점이 발생한다.
- 서로 연관성없는 로직들을 하나의 생명주기에서 작성해야 하는 경우가 발생한다.
- componentDidMount와 componentWillUnmount, comopnentDidMount와 componentDidUpdate처럼 반복해서 작성해야 하는 코드 발생
- 컴퓨터 입장에서도 클래스 사용시 코드압축이 잘 안되는 경우,핫 리로드에서 난해한 버그 발생, 컴파일 단계에서 코드 최적화 어려움 발생
- JS의 클래스문법, this 에 대한 이해가 필요해, React의 진입장벽을 높힌다.
훅의 장점
- 여러 훅들 끼리 재조립 가능하므로, 재사용 가능한 로직을 쉽게 만들 수 있다.
- 로직을 한 곳에 모을 수 있다.
- 훅은 단순한 함수이기 때문에 정적타입 언어에서도 타입을 쉽게 작성할 수 있다.
useState()
리액트 공식문서 : useState()
useState는 함수형 컴포넌트에서 상탯값을 관리하게 해줍니다.
기본구조
const [state, setState] = useState(initialState);
초기값을 매개변수로 useState를 호출하면 첫 번째, 두 번째 요소에 각각 state와 setState를 받을 수 있습니다.
배열 비구조화 문법을 이용해 받는 것이기 때문에, state와 setState의 이름은 임의로 정할 수 있습니다.
사용 예
import React, { useState } from 'react'
export default function Profile () {
const [name,setName] = useState('');
return (
<div>
<p>{`name is ${name}`}</p>
<input type='text' value={name} onChange={e=>setName(e.target.value)}></input>
</div>
)
}
클래스형 컴포넌트가 아니기 때문에 인스턴스가 없습니다.
this로 호출하는게 아닌 name, setName처럼 변수에 담긴 메서드와 값을 사용합니다.
여러개의 useState() 사용하기
import React, { useState } from 'react'
export default function Profile () {
const [name,setName] = useState('');
const [age,setAge] = useState(0);
return (
<div>
<p>{`name is ${name}`}</p>
<p>{`age is ${age}`}</p>
<input type='text' value={name} onChange={e=>setName(e.target.value)}></input>
<input type='text' value={age} onChange={e=>setAge(e.target.value)}></input>
</div>
)
}
여러개의 상탯값을 사용하는 코드입니다. 필요한 만큼 훅을 호출할 수 있습니다. 리액트가 훅이 호출된 순서를 기억해서 활용하므로 여러번 호출해도 문제되지 않습니다.
하나의 useState()로 여러 상탯값 관리하기 (객체)
import React, { useState } from 'react'
export default function Profile () {
const [state,setState] = useState({name:'',age:0})
return (
<div>
<p>{`name is ${state.name}`}</p>
<p>{`age is ${state.age}`}</p>
<input type='text' value={state.name}
onChange={e=>setState({...state,name:e.target.value})}/>
<input type='text' value={state.age}
onChange={e=>setState({...state,age:e.target.value})}/>
</div>
)
}
클래스형 컴포넌트에서 상탯값을 관리하는 것과 마찬가지로 객체형태로 관리할 수도 있습니다.
주의할 점은 클래스형 컴포넌트에서는 setState를 하면 병합되지만 함수형 컴포넌트에서는 이전 상탯값을 지웁니다.
따라서 다른 상탯값들이 지워지지 않도록, 펼침연산자(Spread Syntax)를 이용해 명시적으로 ...state를 사용해 펼쳐 넣어줘야 합니다.
useEffect()
useEffect는 어떤 Effect를 발생시키고 싶을 때 사용합니다.
여기서 말하는 effect는 명령형함수 또는 타이머, 로깅, 변형, sideEffect등 을 발생시키는 함수등을 말합니다.
useEffect(didUpdate);
useEffect에 전달된 함수는 렌더링 완료된 후에 실행되지만, 어떤 값이 변경됐을 경우에 실행되도록 할 수도 있습니다.
useEffect는 렌더링 결과가 실제 돔에 반영된 뒤에 호출됩니다.
하지만, 브라우저에 페인팅하는 시점, 실행되는 시점에 관해서 리액트 공식문서에 글이 있습니다.
useEffect의 지연타이밍
리액트 공식문서 : timing of effect 글
componentDidMount와 componentDidUpdate와는 다르게, useEffect로 전달된 함수는 지연 이벤트 동안에 레이아웃 배치와 그리기를 완료한 후 발생합니다. 그렇지만, 모든 effect가 지연될 수는 없습니다. 예를 들어 사용자에게 노출되는 DOM 변경은 사용자가 노출된 내용의 불일치를 경험하지 않도록 다음 화면을 다 그리기 이전에 동기화 되어야 합니다. useEffect는 브라우저 화면이 다 그려질 때까지 지연됩니다만, 다음 어떤 새로운 렌더링이 발생하기 이전에 발생하는 것도 보장합니다. React는 새로운 갱신을 시작하기 전에 이전 렌더링을 항상 완료하게 됩니다. ... 생략 ...
정리하면 useEffect로 전달된 함수는 레이아웃배치,화면그리기가 완료 될 때까지 지연된 뒤 실행됩니다. (화면 그리는걸 막으면 안되기 때문에)
그리고 그린 뒤 다음 렌더링이 발생하기 전에 실행되는 것도 보장됩니다.
물론 대부분의 작업이 렌더링을 차단해서는 안되겠지만, 사용자가 UI데이터와 화면의 내용 불일치를 경험하지 않도록 동기적으로 Effect를 발생시키고 싶다면 useLayoutEffect함수를 이용하면됩니다.
useLayoutEffect의 내부에 예정된 갱신은 브라우저가 화면을 그리기 이전 시점에 동기적으로 수행됩니다.
- useEffect는 화면이 다 그려진 뒤 실행된다.
- useLayoutEffect는 화면이 그려지기 이전시점에 실행된다.
useEffect 사용 예
import React, { useState, useEffect } from 'react'
export default function MyComponent() {
const [count, setCount] = useState(0);
useEffect(()=>{
document.title = `업데이트 횟수 : ${count}`
})
return <button onClick={()=>setCount(count+1)}>increase</button>
}
버튼을 클릭시에 상탯값을 증가시키는 간단한 코드입니다.
버튼을 클릭하면 다시 렌더링되고, useEffect훅에 입력된 함수가 호출됩니다.
useEffect에서 API호출
getUserAPI 라는 비동기통신 메서드를 이용하는 예제입니다.
import React, { useState, useEffect } from 'react'
function getUserAPI(userID) {
// .... 비동기통신
return new Promise(resolve=>{
resolve({
name : 'jack',
age : '30'
})
})
}
export default function Profile({userID}) {
const [user,setUser] = useState(null);
useEffect(()=>{
getUserAPI(userID).then(data=>setUser(data))
},[userID]);
return (
<div>
{!user && <p>사용자 정보를 가져오는중...</p>}
{user && (
<>
<p>{`name is ${user.name}`}</p>
<p>{`age is ${user.age}`}</p>
</>
)}
</div>
);
}
useEffect함수는 두 번째 매개변수로 의존성배열을 받습니다.
useEffect훅에서 API통신을 하면 렌더링할 때마다 호출되기 때문에 불필요하게 많이 동작하게 됩니다. useEffect함수의 두 번째 매개변수로 배열을 입력하면, 배열의 값이 변경되는 경우에만 함수가 호출됩니다. 여기에서는 userID가 변경되는 경우에만 호출됩니다.
이벤트처리 등록과 해제
클래스형 컴포넌트에서 이벤트처리 등록과 해제를 작성하는 경우, componentDidMount에서 등록한 뒤 componentWillUnMount에서 해제하는 코드를 많이 이용했습니다. 등록 하고나서 해제를 깜빡하는 실수도 발생하기도 합니다.
useEffect훅을 이용하면 등록과 해제를 모두 같이 관리할 수 있습니다.
바로 return에 해제하는 함수를 등록하는 것입니다.
window에 resize이벤트를 등록하는 예제입니다.
import React, { useState, useEffect } from 'react'
export default function WidthPrinter() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(()=>{
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize',onResize); // 등록
return ()=> {
window.removeEventListener('resize',onResize); // 해제
}
})
return (
<div>{`width is ${width}`}</div>
)
}
return하는 함수는 언마운트되거나 첫 번째 매개변수로 입력된 함수가 호출되기 직전에 호출됩니다.
따라서, 첫 번째 매개변수로 입력된 함수가 반환한 함수는 반드시 호출될 것이 보장됩니다.
useEffect훅의 두 번째 매개변수에 빈 배열을 넣을 경우
컴포넌트가 마운트 될 때만, 첫 번째 매개변수로 입력된 함수가 호출되고, 컴포넌트가 언마운트 될 때만 반환된 함수가 호출됩니다.
즉, componentDidMount와 componentWillUnmount에서만 실행되는 것과 같은 효과가 있습니다.
리액트 공식문서 : Hooks API Reference와 실전리액트 프로그래밍 : 이재승님 지음 을 참고해 작성했습니다.
여기까지 기본적인 useState와 useEffect에 대한 글이었습니다!
읽어주셔서 감사합니다.
다음 포스팅에서 커스텀(custom)훅을 이용하는 법에대해 알아보겠습니다.
'React' 카테고리의 다른 글
[React] 중첩 객체(Nested Object)의 수정 (0) | 2022.04.29 |
---|---|
[React] Fragment 사용하기 (0) | 2022.04.28 |
[React] PureComponent VS Component (0) | 2022.04.28 |
[React] 클래스형 컴포넌트와 함수형 컴포넌트의 차이 (0) | 2022.04.28 |
[React] forceUpdate 함수 활용 하기 (0) | 2022.04.28 |