- React Hooks 란?
Hook은 React 버전 16.8부터 React 요소로 새로 추가됨.
Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수.
Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있음.
(Class 안에서 동작 X)
React는 useState 같은 내장 Hook을 몇 가지 제공.
컴포넌트 간에 상태 관련 로직을 재사용하기 위해 Hook을 직접 만드는 것도 가능.
- 내장 HOOK ( useState / useEffect )
📌 STATE HOOK - useState
버튼을 클릭하면 값이 증가하는 간단한 카운터 예시
Hook을 호출해 함수 컴포넌트(function component) 안에 state를 추가
이 state는 컴포넌트가 다시 렌더링 되어도 그대로 유지될 것임
useState는 현재의 state 값과 이 값을 업데이트하는 함수를 쌍으로 제공.
우리는 이 함수를 이벤트 핸들러나 다른 곳에서 호출할 수 있음.
이것은 class의 this.setState와 거의 유사하지만, 이전 state와 새로운 state를 합치지 않는다는 차이점이 있음.
useState는 인자로 초기 state 값을 하나 받음. 카운터는 0부터 시작하기 때문에 위 예시에서는 초기값으로 0을 넣어준 것. this.state와는 달리 useState Hook의 state는 객체일 필요가 없음. (물론 필요에 따라 객체여도 됨.)
이 초기값은 첫 번째 렌더링에만 딱 한번 사용됨.
import React, { useState } from 'react';
function Example() {
// "count"라는 새로운 상태 값을 정의합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
※ 여러 state 변수 선언하기
하나의 컴포넌트 내에서 State Hook을 여러 개 사용할 수도 있음
function ExampleWithManyStates() {
// 상태 변수를 여러 개 선언했습니다!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
배열 구조 분해(destructuring) 문법은 useState로 호출된 state 변수들을 다른 변수명으로 할당할 수 있게 해줌.
React는 매번 렌더링할 때 useState가 사용된 순서대로 실행할 것.
📌 EFFECT HOOK - useEffect
React 컴포넌트 안에서 데이터를 가져오거나 DOM을 직접 조작하는 작업을 “side effects”(또는 짧게 “effects”)라고 함.
왜냐하면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문.
useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해줌.
React class의 componentDidMount 나 componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것.
예를 들어, 이 예시는 React가 DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트임.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷합니다
useEffect(() => {
// 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect를 사용하면, React는 DOM을 바꾼 뒤에 “effect” 함수를 실행.
Effects는 컴포넌트 안에 선언되어있기 때문에 props와 state에 접근할 수 있음.
기본적으로 React는 매 렌더링 이후에 effects를 실행. 첫 번째 렌더링도 포함해서.
Effect를 “해제”할 필요가 있다면, 해제하는 함수를 반환해주면 됨. 이는 선택적(optional)인 것.
예를 들어, 이 컴포넌트는 친구의 접속 상태를 구독하는 effect를 사용했고, 구독을 해지함으로써 해제해줌.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
컴포넌트가 unmount될 때 React는 ChatAPI에서 구독을 해지할 것임.
또한 재 렌더링이 일어나 effect를 재실행하기 전에도 마찬가지로 구독을 해지함.
useState와 마찬가지로 컴포넌트 내에서 여러 개의 effect를 사용할 수 있음.
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
Hook을 사용하면 구독을 추가하고 제거하는 로직과 같이 서로 관련 있는 코드들을 한군데에 모아서 작성할 수 있음.
반면 class 컴포넌트에서는 생명주기 메서드(lifecycle methods) 각각에 쪼개서 넣어야만 했음.
✌️ Hook 사용 규칙
Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 함.
1. 최상위(at the top level)에서만 Hook을 호출해야 함. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행 X.
2. React 함수 컴포넌트 내에서만 Hook을 호출해야 함. 일반 JavaScript 함수에서는 Hook을 호출해서는 안 됨.
Hook을 호출할 수 있는 곳이 딱 한 군데 더 있음. 바로 직접 작성한 custom Hook 내에서도 Hook 호출 가능.
이 규칙들을 강제하기 위해서 linter plugin을 제공하고 있음.
❓ hook을 사용하기 위해서는?
setState, setStateComponent, componentDidMount 등을 알아야 함 (이러한 것들을 라이프사이클이라고 함)
리액트 컴포넌트에는 라이프사이클(생명주기)이 존재.
컴포넌트의 수명은 페이지에 렌더링되기 전인 준비과정에서 시작하여 페이지에서 사라질 때 끝남.
컴포넌트를 처음으로 렌더링 할 때, 어떤 작업을 처리해야하거나 컴포넌트를 업데이트하기 전후로
어떤 작업을 처리해야 할 수도 있고, 불필요한 업데이트를 방지해야 할 수도 있음.
이러한 경우들에 컴포넌트의 라이프사이클 메서드를 사용.
라이프사이클 메서드는 클래스 컴포넌트에서만 사용할 수 있음.
라이프사이클 메서드 종류는 총 9가지
- will 접두사가 붙은 메서드 👉 어떤 작업을 작동하기 전에 실행
- Did 접두사가 붙은 메서드 👉 어떤 작업을 작동한 후에 실행
라이프사이클은 총 3가지, Mount, Update, Unmount 카테고리로 나눔.
※ 마운트(Mount)
DOM이 생성되고 웹 브라우저상에 나타나는 것을 Mount라고 함.
🔍 마운트시 호출되는 메서드
- constructor : 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성사 메서드
- getDerivedStateFromProps : props 에 있는 값을 state 에 넣을 때 사용하는 메서드
- render : 준비한 UI를 렌더링하는 메서드
- componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드
※ 업데이트(Update)
컴포넌트는 다음 4가지 경우에 업데이트
- props가 바뀔 때
- state가 바뀔 때
- 부모 컴포넌트가 리렌더링될 때
- this.foceUpdate로 강제로 렌더링 트리거할 때
🔍 업데이트에 호출되는 메서드
- getDerivedStateFromProps : 앞서 Mount 과정에서도 호출되고, props 변화에 따라 state 값에도 변화를 주고 싶을 때 사용
- shouldComponentUpdate : 컴포넌트가 리렌더링을 해야 할지 말아야 할지를 결정, true 를 반환하면 다음 라이프사이클 메서드를 계속 실행, false 를 반환하면 작업을 중지(리렌더링 X)함.
- render : 컴포넌트를 리렌더링함.
- getSnapshotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출
- componentDidUpdate : 컴포넌트의 업데이트 작업이 다 끝난 후 호출
※ 언마운트(Unmount)
Mount 의 반대 과정, 즉 컴포넌트를 DOM에서 제거하는 것을 Unmount 라고 함.
🔍 언마운트시 호출하는 메서드
- componentWillUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출
자세한 라이프사이클은 다음에 알아보도록 하고,
componentDidMount, componentDidUpdate, componentWillUnmount 세 가지를 알아보도록 하겠음
1. componentDidMount - 컴포넌트 탄생(마운트 될 때)할 때 빈 배열을 전달
useEffect(()=>{
console.log("Mount")
}, []); // 빈 배열 전달. 컴포넌트가 마운트될 때만 실행됨
2. componentDidUpdate - 컴포넌트가 업데이트될 때 리렌더링 된다는 말은, 컴포넌트가 업데이트된다는 말과 같음
컴포넌트는 언제 리렌더링 되는가?
1. state가 변경될 때
2. 부모에게서 내려받는 props가 바뀔 때
3. 부모 컴포넌트가 리렌더링 되면 자식요소도 리렌더링 됨
업데이트 되는 순간에 사용하는 방법 : 배열을 전달 X
useEffect(()=>{
console.log("Update")
}, ); // 빈 배열을 전달하지 않는다. 컴포넌트가 업데이트될 때만 실행됨
!! 감지하고 싶은 가밧만 감지해서, 그 값이 변화하는 순간에만, 콜백함수를 실행할 수 있음
useEffect(()=>{
console.log("Update")
},[count]); // count라는 state가 변화하는 순간에만 콘솔로그에 Update라고 찍힌다
3. componentWillUnmount - 언마운트. 컴포넌트가 화면에서 사라지는 순간을 제어
1. useEffect에 전달되는 콜백함수가 함수를 리턴하게 하면 됨
2. 리턴되는 함수는 언마운트될 때 실행됨
import React, [useEffect, useState] from 'react';
const UnmountTest = () => {
useEffect(() => {
console.log('Mount');
return() => {
// Unmount 시점에 실행된다
console.log('unmount')
};
}, []);
return <div> unmount Testing component </div>
};
const Lifecycle = () => {
const [isVisible, setIsVisible] = useState(false);
const toggle = () => setIsVisible(!isVisible);
return(
<div style = {{ padding : 20 }}>
<button onClick = {toggle} ON/OFF </button>
{isVisible && <UnmountTest/>}
</div>
);
};
export default Lifecycle;
📌 다른 내장 Hook
보편적이지는 않지만 유용하다고 느낄만한 내장 Hook이 몇 가지 더 있음.
1. useContext
useContext는 컴포넌트를 중첩하지 않고도 React context를 구독할 수 있게 해줌.
function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}
2. useReducer
useReducer는 복잡한 컴포넌트들의 state를 reducer로 관리할 수 있게 해줌.
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
컴포넌트 안에서 이 Hook을 사용하여 reducer가 state 관리함.
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...
}
복잡한 컴포넌트에서 내에서 state를 reducer로 관리해야 하는 보편적 필요성을 고려하여
React에는 useReducer가 내장되어 있음.
useState의 대체 함수.
(state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 현재 state를 반환함.
(Redux에 익숙하다면 이것이 어떻게 동작하는지 이미 알고 있을 것임.)
다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우에 useReducer를 선호.또한, useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은 콜백 대신 dispatch를 전달 할 수 있기 때문.
아래는 useState 내용에 있던 카운터 예시인데 reducer를 사용해서 다시 작성한 것.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
- 자신만의 Hook 만들기
개발을 하다 보면 가끔 상태 관련 로직을 컴포넌트 간에 재사용하고 싶은 경우가 생김.
이 문제를 해결하기 위한 전통적인 방법이 두 가지 있었는데, higher-order components와 render props가 바로 그것. Custom Hook은 이들 둘과는 달리 컴포넌트 트리에 새 컴포넌트를 추가하지 않고도 이것을 가능하게 해줌.
친구의 접속 상태를 구독하기 위해서 useState와 useEffect Hook을 사용한 FriendStatus 컴포넌트 예시 다시 한번 보기.
이 로직을 다른 컴포넌트에서도 재사용하고 싶다고 가정해봄.
먼저, 이 로직을 useFriendStatus라는 custom Hook으로 뽑아냄.
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
이 Hook은 friendID를 인자로 받아서 친구의 접속 상태를 반환해줌.
이제 이것을 여러 컴포넌트에서 사용할 수 있음.
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
각 컴포넌트의 state는 완전히 독립적임. Hook은 state 그 자체가 아니라, 상태 관련 로직을 재사용하는 방법임.
실제로 각각의 Hook 호출은 완전히 독립된 state를 가짐. 그래서 심지어는 한 컴포넌트 안에서 같은 custom Hook을 두 번 쓸 수도 있음.
Custom Hook은 기능이라기보다는 컨벤션(convention - 관습)에 가까움.
이름이 ”use“로 시작하고, 안에서 다른 Hook을 호출한다면 그 함수를 custom Hook이라고 부를 수 있음.
useSomething이라는 네이밍 컨벤션은 linter 플러그인이 Hook을 인식하고 버그를 찾을 수 있게 해줌.
폼 핸들링, 애니메이션, 선언적 구독(declarative subscriptions), 타이머 등 많은 경우에 custom Hook을 사용할 수 있음.
useTitle - react document의 title의 몇 개의 hooks와 함께 바꾸는 것
useInput - input 역할
usePageLeave - 유저가 page를 벗어나는 시점을 발견하고 함수를 실행
useClick - 누군가 element를 클릭하는 시점을 발견
useFadeIn - 어떤 element던 상관없이 애니메이션을 element 안으로 서서히 사라지게 만듦
useFullscreen - 어떤 element던 풀스크린으로 만들거나 일반 화면으로 돌아가게 할 수 있음
useHover - 어떤 것에 마우스를 올렸을 때 감지
useNetwork - Online 또는 Offline 상태인지를 감지
useNotification - nofication API를 사용할 때 유저에게 알림을 보내줌
useScroll - 스크롤을 사용할 때를 감지해 알려줌
useTabs - 웹사이트에 메뉴 또는 무엇이든 간에 tab을 사용하기 매우 쉽게 만들어주는 것
usePreventLeave - 유저가 변경사항이나 무엇이든 간에 저장합지 않고 페이지를 벗어나길 원할 때 확인을 하는 것
useConfirm - usePreventLeave와 비슷한데 어떤 기능이 존재
useAxios - HTTP request client axios를 위한 wrapper 같은 것
'(심층)리액트' 카테고리의 다른 글
React Hooks - 03 / useState를 활용한 useTabs (0) | 2023.02.01 |
---|---|
React Hooks - 02 / useState를 활용한 useInput (0) | 2023.01.31 |
리액트 심화2 - Axios (0) | 2023.01.19 |
리액트 심화2 - json server (0) | 2023.01.18 |
리액트 심화1 - Redux Toolkit(2) - TodoList 리덕스 툴킷으로 변환 (0) | 2023.01.18 |
github : https://github.com/dnjfht
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!