React Hooks은 use
로 시작되는 함수이다. React 16.8에서 추가되었으며, 클래스 컴포넌트에서 사용하던 여러 기능을 함수형 컴포넌트에서도 사용할 수 있게 만들어 준다.
Hook은 호출 위치에 제약이 있다.
return
문 이후에 호출하지 않는다.useMemo
, useReducer
, useEffect
에 전달된 함수 내부에서 호출하지 않는다.try
/catch
/finally
블록 내부에서 호출하지 않는다.// 아래는 불가능하다.
function setOnlineStatus() {
const [onlineStatus, setOnlineStatus] = useOnlineStatus();
}
useState
는 상태를 관리하는 훅이다. 초기 상태 값과 상태를 업데이트하는 함수를 반환한다. 업데이트를 통해 상태를 변경할 수 있으며, 이 변경은 컴포넌트를 재렌더링한다.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
// ...
setReducer
는 더 복잡한 상태 관리를 위해 사용되는 훅이다. useState
와 유사하게 상태와 상태 업데이트 함수를 반환하지만, 상태 업데이트 로직이 별도의 reducer
함수에서 처리되기 때문에 업데이트 로직이 복잡할 수록 유용하다.
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
return (
<>
<button onClick={() => {
dispatch({ type: 'incremented_age' })
}}>
Increment age
</button>
<p>Hello! You are {state.age}.</p>
</>
);
}
useContext
는 컴포넌트 트리 전체에 데이터를 제공하는 context를 사용하게 해 주는 훅이다. 전역적인 데이터를 전달하기 좋지만, 컴포넌트를 재사용하기 어려워지기 때문에 꼭 필요할 때만 사용하자.
const ThemeContext = createContext('light');
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<>
<ThemeContext.Provider value={theme}>
<Form />
</ThemeContext.Provider>
<Button onClick={() => {
setTheme(theme === 'dark' ? 'light' : 'dark');}} />
</>
)
}
function Form({ children }) {
const theme = useContext(ThemeContext);
return (
//...
useRef
는 렌더링에 필요하지 않은 값을 참조할 수 있는 훅이다. ref 변경은 재렌더링을 유발하지 않기 때문에, 시각적 출력에 영향을 미치지 않는 정보를 저장하는데 적합하다.
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
}
useId
는 고유한 ID를 생성하는 훅이다. React에서 ID를 직접 코드에 입력하는 건 좋은 사례가 아니다. 몇 번이고 렌더링되는 동안 ID는 고유해야 한다.
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Password:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
The password should contain at least 18 characters
</p>
</>
);
}
useEffect
는 컴포넌트가 리렌더링 될 때마다 특정 작업을 수행할 수 있게 하는 훅이다. 의존성 배열을 사용해 특정 객체의 상태 값이 변할 때마다 리렌더링 시킬 수 있다.
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
useMemo
는 재렌더링 사이에 계산 결과를 캐싱하는 훅이다. 성능 개선을 위해 사용한다. 비용이 많이 드는 계산을 수행하는 경우, 데이터가 변경되지 않았다면 계산을 생략하는 게 더 효율적이기 때문이다. 이것을 메모이제이션이라고 부른다.
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
() => filterTodos(todos, tab)
는 계산 함수이다.[todos, tab]
는 종속성 목록이다. 종속성이 변경되지 않았다면 이전에 이미 계산해둔 값을 반환하고, 변경되었다면 다시 연산한다.useCallback
은 재렌더링 사이에 함수를 캐싱하는 훅이다. 성능 개선을 위해 사용한다.
function ProductPage({ productId, referrer, theme }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
재렌더링 된 함수는 동일하지 않다. 같은 내용이라도 메모리 주소가 다르면 다른 함수로 간주하기 때문이다.
위의 코드의 경우, theme
가 변경되면 재렌더링 되어 handleSubmit
이 다른 함수가 된다. 때문에 <ShippingForm>
도 불필요한 재렌더링을 피할 수 없게 된다.
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
useCallback
을 이용해 함수를 캐싱하면 불필요한 재렌더링을 피할 수 있다. handleSubmit
이 변경되지 않기 때문에 <ShippingForm>
이 재랜더링되지 않는다.