오늘은 Hook에 대해 자세히 배울 수 있었다.
그 중 강의에서 나온 다섯 가지의 함수에 대해 간단하게 정리해보려고 한다.
우선, Hook은 무엇일까?
Hook은 React v16.8.0 부터 사용할 수 있게 된 기능이며, 클래스형 컴포넌트의 사용 없이 함수형 컴포넌트에서 상태와 라이프사이클을 관리할 수 있게 해준다. 이전에는 상태와 라이프사이클을 이용해야 할 때는 클래스형 컴포넌트를 사용해야 했다.
참고로 상태는 React 컴포넌트 내에서 변경 가능한 데이터를 위해 사용하는 객체이며, 라이프사이클은 컴포넌트의 수명 주기를 말한다.
- constructor
컴포넌트가 처음 생성될 때 호출. - getDerivedStateFromProps
props로 받아온 값을 클래스 내의 상태와 동기화 시킬 때, 컴포넌트가 마운트될 때와 업데이트 될 때 호출. - shouldComponentUpdate
props나 상태 값 변경 시 업데이트 할지 말지 결정. true 혹은 false를 반환 - render
실제로 컴포넌트가 렌더링되는 함수. 컴포넌트 로딩, 업데이트 시 호출. - getSnapshotBeforeUpdate
render에서 만들어진 컴포넌트가 실제 웹 상에 반영되기 직전 호출. 변화가 일어나기 직전의 DOM 상태를 가져와 특정 값을 반환하면 componentDidUpdate 함수에서 사용 - componentDidMount
컴포넌트가 초반에 마운트되고 딱 한 번 호출. - componentDidUpdate
props 또는 상태 값 변경 후 호출. - componentWillUnmount
컴포넌트를 DOM에서 제거할 때 호출.
기존 클래스형 컴포넌트에서는 상태가 변화하고, 컴포넌트가 생성되고 제거되는 등의 시점에 어떤 코드를 수행할 수 있었다. (물론 지금도 그렇다.) 그러나 함수형 컴포넌트에서는 사용할 수 없었던 것을, Hook의 업데이트로 인해 사용할 수 있게 됐다.
Hook을 호출할 때는 규칙이 있다.
아래의 내용은 공식 문서에서 확인이 가능하다 🔗(공식 문서)
최상위 레벨에서만 호출이 가능하다.
반복문, 조건문, 혹은 중첩된 함수 내에서 Hook을 호출하면 안된다.
function Form() {
// 1. name이라는 state 변수를 사용하세요.
const [name, setName] = useState('Mary');
// 2. Effect를 사용해 폼 데이터를 저장하세요.
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. surname이라는 state 변수를 사용하세요.
const [surname, setSurname] = useState('Poppins');
// 4. Effect를 사용해서 제목을 업데이트합니다.
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
해당 함수 내에선 Hook의 실행 순서가 보장된다.
// ------------
// 첫 번째 렌더링
// ------------
useState('Mary') // 1. 'Mary'라는 name state 변수를 선언합니다.
useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect를 추가합니다.
useState('Poppins') // 3. 'Poppins'라는 surname state 변수를 선언합니다.
useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect를 추가합니다.
// -------------
// 두 번째 렌더링
// -------------
useState('Mary') // 1. name state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect가 대체됩니다.
useState('Poppins') // 3. surname state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect가 대체됩니다.
// ...
그러나 조건문 내에 Hook을 사용하면 실행 순서가 달라질 수 있다.
// 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
처음엔 name이 'Mary'로 name이 빈 문자열이 아님을 알지만,
이후 name이 빈 문자열이 된다면 useEffect는 호출되지 않고,
React는 해당 이펙트 함수의 존재를 모르게 된다.
useState('Mary') // 1. name state 변수를 읽습니다. (인자는 무시됩니다)
// useEffect(persistForm) // 🔴 Hook을 건너뛰었습니다!
useState('Poppins') // 🔴 2 (3이었던). surname state 변수를 읽는 데 실패했습니다.
useEffect(updateTitle) // 🔴 3 (4였던). 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.
요컨데, Hook의 실행 순서가 보장되어야 하고,
React에선 매 렌더링마다 해당 컴포넌트의 실행 순서가 일치할 거라고 생각하는데,
조건문이나 반복문 안에서 선언되면 조건에 따라 실행되지 않을 수도 있다.
또한 함수 내에서 선언되는 것도, 해당 함수에 대한 의존성을 가지기 때문에 무조건 실행될 거란 보장을 가질 수 없다.
본문으로 넘어가서, React에서 기본적으로 제공하는 Hook에 대해, 강의에서 배운 것에 관해서만 정리해본다.
1. useState
- 컴포넌트 내 동적인 데이터 관리 가능
- 최초 useState 호출 시 초기 값으로 설정, 재 렌더링 시 무시
- state는 읽기 전용으로, 직접 수정 불가능해 setState를 호출해야 함
- setState 내의 인자로는 직접 값 입력 또는 콜백 함수를 넘길 수 있음
- state 변경 시 컴포넌트 재 렌더링
import { useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h2>카운트: {count}</h2>
<button
onClick={() => {
setCount(count + 1);
}}
>
카운트 증가
</button>
<button
onClick={() => {
setCount(count - 1);
}}
>
카운트 감소
</button>
</div>
);
}
export default App;
state를 변경하고 싶을 때는 setState(여기서는 setCount 함수)를 사용합니다.
직접 count 값을 변경하는 것은 count가 const로 선언되었기 때문에 렌더링 시점에서 오류가 나지만,
만약 let으로 선언하고 count를 직접 변경하는 코드로 변경해도 결국 화면에는 반영되지 않는다.
<button
onClick={() => {
count += 1;
console.log(count);
}}
>
카운트 증가
</button>
<button
onClick={() => {
count -= 1;
console.log(count);
}}
>
카운트 감소
</button>
2. useEffect
- 함수 컴포넌트에서 side effect 수행 가능
- 최초 렌더링 시, 지정한 state 또는 props가 변경 시 호출
- useEffect 내에서 함수를 return ➡️ state 변경으로 컴포넌트 재 렌더링 시, 컴포넌트가 없어질 때 호출 함수 지정
useEffect(() => {
console.log("컴포넌트가 렌더링 되었습니다.");
return () => {
console.log("컴포넌트가 제거 되었습니다.");
};
}, []);
useEffect 함수의 첫 번째 인자에는 함수,
두 번째 인자에는 상태 변화를 관찰할 state에 대한 배열이 들어간다.
만약 두 번째 인자에 아무것도 넣지 않는다면 매 렌더링 시마다 실행,
빈 배열이 들어가면 최초 렌더링 시에만 실행,
어떤 상태 값이 들어간다면 해당 상태 값이 변경될 때 실행된다.
3. useMemo
- 지정된 state 또는 props 변경 시 해당 값을 활용해 계산된 값을 메모이제이션
- 재 렌더링 시 불필요한 연산 줄임
- 렌더링 단계에서 연산이 이루어지므로, 오래 걸리는 로직은 작성하지 않는 것을 권장
const totalTodo = useMemo(() => {
return todoList.length;
}, [todoList]);
state의 변경으로 컴포넌트가 재 렌더링될 때, 함수 내에 선언된 변수나 함수가 또 다시 선언된다.
해당 변수는 코드의 일부만 봐도 알겠지만, totalTodo라는 변수는 todoList 배열의 길이 값을 가진다는 것을 알 수 있는데,
물론 length 값을 계산하는 것은 그렇게 시간이 많이 걸리지 않지만 만약 해당 변수 값을 계산하는 데에 시간이 많이 걸린다면 상관없는 state가 변경되었을 때마다 해당 변수를 계산하는 데에 시간을 쓰게 된다면 굉장한 메모리 낭비일 것이다.
그럴 때에 사용하는 것이 useMemo이다. todoList의 값이 변경될 때만 해당 변수를 다시 계산한다.
그러나 무조건 useMemo를 사용하는 것이 무조건 성능이 좋아진다는 것을 보장할 수 없으니,
경우에 따라 사용할 경우를 구분할 수 있는 것이 좋을 것 같다. (아마 계속 접해봐야 알 것 같다.)
4. useCallback
- 함수를 메모이제이션 하기 위함
- 컴포넌트 재 렌더링 시 불필요하게 함수가 다시 생성되는 것을 방지
const handleInsert = useCallback(
(value) => {
setTodoList((todoList) => [
...todoList,
{
key: new Date().getTime(),
value: value,
isCompleted: false,
},
]);
},
[todoList]
);
useCallback은 useMemo와 비슷하다. 차이점이 있다면 값을 저장하는 지와, 함수를 저장하는 지의 차이 정도인 것 같다.
5. useRef
- 컴포넌트 생애 주기 내에서 유지할 ref 객체 반환
- ref 객체는 .current 프로퍼티를 가지며, 자유롭게 값 변경 가능
- React에서 DOM Element에 접근 시 사용
- useRef에 의해 ref객체가 변경되어도 재 렌더링 되지 않음
const inputRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
onInsert(inputValue);
setInputValue("");
inputRef.current.focus();
};
return (
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
/>
<button type="submit">등록</button>
<button type="button">초기화</button>
</form>
);
만약 할 일을 등록하고, input 태그에 focus를 주고 싶을 때 사용할 수 있을 것이다.
(사실 아직은 잘 모르겠다... 나중에 쓸 일이 생기면 더 명확하게 알 수 있을 것 같다.)
#엘리스트랙 #엘리스트랙후기 #리액트네이티브강좌 #온라인코딩부트캠프 #온라인코딩학원 #프론트엔드학원 #개발자국비지원 #개발자부트캠프 #국비지원부트캠프 #프론트엔드국비지원 #React #Styledcomponent #React Router Dom #Redux #Typescript #Javascript