Hook
함수 컴포넌트에서 React state와 생명주기 기능을 "연동"할 수 있게 해주는 함수
- Hooks가 등장하기 이전에는 Class를 기반으로 하는 클래스형 컴포넌트를 사용
-> 클래스형 컴포넌트는 코드의 재사용이 어렵고 연관성이 없는 로직을 하나의 생명주기 메서드에서 구현해야 하는 등의 한계가 존재 - state를 제공함으로써 상태 관련 로직의 재사용을 이전보다 훨씬 쉽게 만들어줌
Hook 종류
useState
함수형 컴포넌트 내부에서 상태를 정의하고, 이 상태를 관리할 수 있게 해주는 훅
- 인자로 state의 초기 값을 넘겨줌
import React, { useState } from 'react'; // useState Hook을 React에서 가져옴
function Example() {
// "count", "fruit", "todos" 라는 상태 변수를 선언
const [count, setCount] = useState(0);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
return (
<div>
<p>You clicked {count} times</p> <!-- state 가져오기 -->
<button onClick={() => setCount(count + 1)}> <!-- state 갱신하기 -->
Click me
</button>
</div>
);
}
useEffect
컴포넌트의 상태 값 변화에 따라 컴포넌트 내부에서 변경이 이루어져야 되는 것들을 처리해주는 훅
- side effect 수행
- 컴포넌트가 렌더링 된 이후에 수행됨
- 이미 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>
);
}
💡 Dependency 배열
- 모든 렌더링 이후에 effect를 정리(clean-up)하거나 적용하는 것이 때때로 성능 저하를 발생
- 컴포넌트가 마운트될 때, dependency 배열에 상관없이 무조건 한 번 실행
- 마운트: 컴포넌트가 화면에 보이기 시작할 때
- DOM 객체가 렌더링을 통해 생성되어 브라우저에 나타나는 시점
- 컴포넌트의 생명주기 동안 단 한 번, 처음으로 DOM에 컴포넌트가 추가될 때 수행
- props나 state가 변경 되어 render될 때는 mount 되지 않음
- 마운트: 컴포넌트가 화면에 보이기 시작할 때
1. dependency 배열이 없는 경우
- 리렌더링 때마다 실행
- 리렌더링 되는 경우
- state가 바뀔 때
- props가 바뀔 때
- 부모 컴포넌트가 리렌더링될 때
- 리렌더링 되는 경우
useEffect(() => {
document.title = `You clicked ${count} times`;
}); // 리렌더링될 때마다 effect를 실행
2. dependency 배열이 빈 배열인 경우
- 컴포넌트가 마운트될 때 실행
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []); // 마운트될 때 1번만 effect를 실행
3. dependency 배열에 값이 있는 경우
- 해당 값이 바뀔 때마다 실행
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행
- count가 5이고 컴포넌트가 리렌더링된 이후에도 여전히 count는 변함없이 5라면, React는 이전 렌더링 시의 값 5와 그다음 렌더링 때의 5와 비교
- 배열 내의 모든 값이 같기 때문에(5 === 5) React는 effect를 건너뜀
- 이런 식으로 최적화가 가능
💡 cleanup 함수
useEffect Hook 내에서 return되는 함수
- 이벤트를 등록하고 지울 때 사용
- 새로운 값과 함께 렌더링된 뒤에 실행되지만 변경된 값을 읽는 것이아니라 함수가 정의 되었을 당시에 선언되었던 이전 값을 보고 실행
- return () 함수 사용
- 컴포넌트가 언마운트될 때, 특정 값이 변경되기 직전에 실행
- 언마운트: 컴포넌트가 화면에서 사라질 때
- DOM에서 컴포넌트가 사라질 때, 그 뒷정리를 담당하는 시점
- 보통 clearInterval, clearTimeout등 등록된 작업을 제거하는 함수를 호출하거나, 인스턴스를 제거하는 경우에 사용
- 언마운트: 컴포넌트가 화면에서 사라질 때
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); // mount
// effect 이후에 어떻게 정리(clean-up)할 것인지 표시
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // unmount
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
- ex)
- 컴포넌트가 마운트된 이후에 친구의 상태를 구독하며 컴포넌트가 언마운트를 될 때에 구독을 해지
- 컴포넌트가 화면에 표시되어있는 동안 상태(id)가 변함
// { friend: { id: 100 } } state을 사용하여 마운트
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 첫번째 effect가 작동
// { friend: { id: 200 } } state로 업데이트
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 다음 effect가 작동
// { friend: { id: 300 } } state로 업데이트
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 다음 effect가 작동
// 마운트를 해제
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)
=> 일관성을 유지, 버그 예방
useContext
전역적으로 사용되는 데이터를 여러 컴포넌트끼리 쉽게 공유할 수 있도록 하는 훅
- Props로 데이터를 일일이 전달해 주지 않아도 해당 데이터를 가지고 있는 상위 컴포넌트에 그 데이터가 필요한 하위 컴포넌트가 접근 가능
import { createContext } from "react"; // createContext를 import
// 기본값은 적절한 Provider를 찾지 못했을 때만 사용됨
// 만약 Provider를 통해 undefined로 보낸다 해도 값을 받은 컴포넌트들은 기본값으로 읽지 않음
exoport const ThemeContext = createContext(null); // 기본값으로는 null을 넣어줌
import { ThemeContext } from "./context/ThemeContext";
import Toobar from "./Compopnents/Toolbar";
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
function App() {
return (
<!-- ThemeContext가 감싸는 모든 하위 컴포넌트는 props를 사용하지 않고 value로 넣어준 값에 접근 -->
<ThemeContext.Provider value={themes.dark}>
<Toolbar/>
</ThemeContext.Provider>
);
}
import React, { useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext); // useContext 사용하여 데이터 접근
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
❓ context가 좋은데 Props를 사용하는 이유
- context를 사용하면 컴포넌트를 재사용하기 어려워질 수 있음
- context의 주된 목적은 다양한 레벨이 있는 많은 컴포넌트들에게 전역적인 데이터를 전달하기 위함
- 단지 prop drilling을 피하기 위한 목적으로 사용할 것이라면 컴포넌트 합성이 더 간단한 해결책
useRef
저장공간 또는 DOM요소에 접근하기 위해 사용되는 훅
- .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환
- 반환된 useRef 객체는 컴포넌트의 전 생애주기를 통해 유지됨
- = 컴포넌트가 계속해서 렌더링이 되어도 컴포넌트가 언마운트되기 전까지는 값을 그대로 유지
- = current 속성은 값을 변경해도 상태를 변경할 때 처럼 React 컴포넌트가 재렌더링 되지 않음
=> 렌더링과 상관없이, 마운트된 시점부터 언마운트된 시점까지 값을 유지
[사용하는 경우]
1. 저장공간
- 변경시 렌더링을 발생시키지 말아야하는 값을 다룰 때
- 변화는 감지해야하지만, 그 변화가 렌더링을 발생시키면 안될때
State의 변화 ➡️ 렌더링 ➡️ 컴포넌트 내부 변수들 초기화 |
Ref의 변화 ➡️ No 렌더링 ➡️ 변수들의 값이 유지됨 |
State의 변화 ➡️ 렌더링 ➡️ Ref의 값은 유지됨 |
2. DOM 요소에 접근
ex) Input 요소를 클릭하지 않아도 focus를 줄 때 사용
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
[장점]
값이 변경될때마다 렌더링이 일어나지 않음
=> 성능향상
Hook 사용 규칙
- 최상위에서만 Hook 호출
- 반복문, 조건문, 중첩된 함수 내에서 Hook 실행 금지
- React 함수 컴포넌트 내에서만 호출
- 일반 Javascript 함수에서 호출 X
- 직접 작성한 custom Hook 내에서는 호출 O
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;
}
- 여러 컴포넌트에서 재사용
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id); // custom Hook 사용
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id); // custom Hook 사용
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
728x90
반응형
'React' 카테고리의 다른 글
React Component (0) | 2024.03.30 |
---|---|
React란 (2) | 2024.03.28 |