리액트 Hooks는 함수 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공해 기존의 함수 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해준다.
useState
useState는 함수 컴포넌트에서 상태를 관리할 수 있게 해주는 Hook이다. useState를 사용하면 함수 컴포넌트 내에서 상태를 선언하고, 이를 갱신할 수 있다.
useState 기능을 사용해 숫자 카운터를 구현해보자.
import React, { useState } from "react";
function Counter() {
const [value, setValue] = useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다.
</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
);
}
export default Counter;
useState 함수의 파라미터에는 상태의 기본값을 넣어준다. 현재 0을 넣었는데, 카운터의 기본값을 0으로 하겠다는 뜻이다. 이 함수가 호출되면 배열을 반환하는데, 그 배열의 첫 번째 원소는 상태 값, 두 번째 원소는 상태를 설정하는 함수이다. 이 함수에 파라미터를 넣어서 호출하면 전달받은 파라미터로 값이 바뀌고 컴포넌트가 정상적으로 리렌더링 된다.
useState 여러 번 사용하기
한 컴포넌트에서 관리할 대상이 여러 개라면 useState를 여러 번 사용하면 된다.
import { useState } from "react";
function Info() {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
function onChangeName(e: React.ChangeEvent<HTMLInputElement>) {
setName(e.target.value);
}
function onChangeNickname(e: React.ChangeEvent<HTMLInputElement>) {
setNickname(e.target.value);
}
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickname}
</div>
</div>
</div>
);
}
export default Info;
정상적으로 여러개의 상태를 업데이트 할 수 있다.
useEffect
useEffect는 함수형 컴포넌트에서 컴포넌트가 화면에 렌더링 될 때 마다 실행되는 효과(effect)를 구현할 수 있게 해준다. useEffect Hook을 사용하면, 컴포넌트가 렌더링될 때마다 원하는 효과를 발생시키고, 특정 값이 변경될 때만 효과를 발생시킬 수 있다.
다음 코드는 useEffect Hook으로 API를 호출하고, 이를 화면에 렌더링하는 예시이다.
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
const ExampleComponent: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const fetchUsers = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
setUsers(data);
};
fetchUsers();
}, []);
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
</div>
);
};
export default ExampleComponent;
이 코드에서는 useEffect를 사용해 컴포넌트가 처음 렌더링 될 때(fetchUsers) API를 호출하고, 이를 users 상태에 저장한다.(setUsers) 또한 useEffect의 두 번째 인자로 빈 배열을 전달해, 컴포넌트가 렌더링될 때마다 fetchUsers 함수가 호출되지 않고, 컴포넌트가 처음 마운트될 때만 호출하도록 한다.
또한, useEffect 내부에서 async 함수(fetchUsers)를 호출하고 있기 때문에, useEffect의 콜백 함수를 async 함수로 만들어주었다. 마지막으로, JSX에서는 users 상태를 출력하고, map 함수를 사용해 각 사용자의 이름과 이메일을 출력한다.
useEffect는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번째 파라미터에 무엇을 넣는지에 따라 실행되는 조건이 달라진다. 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 cleanup(뒷정리)하는 함수를 반환해주어야 한다.
useReducer
useReducer는 컴포넌트 상태를 관리하기 위한 함수이다. useState와 비슷하게 동작하지만, useReducer는 더 복잡한 상태 관리에 사용된다.
useReducer 함수는 두 개의 매개변수를 받는다. 첫 번째 매개변수는 reducer 함수이고, 두 번째 매개변수는 초기 상태이다. reducer함수는 이전 상태와 액션 객체를 인수로 받아 새로운 상태를 반환하는 함수이다.
다음은 useReducer의 사용 예시이다.
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error('Unexpected action');
}
}
이 코드에서는 count라는 숫자 상태를 가진 State 타입과 increment, decrement 두 가지 타입의 액션을 가진 Action 타입을 정의한다. reducer 함수는 이전 상태와 액션을 받아서 새로운 상태를 반환한다. 액션의 타입에 따라 새로운 상태를 계산한다.
import React, { useReducer } from 'react';
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
function handleIncrement() {
dispatch({ type: 'increment' });
}
function handleDecrement() {
dispatch({ type: 'decrement' });
}
return (
<div>
<p>Count: {state.count}</p>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
</div>
);
}
이제 useReducer 함수를 사용해 상태를 관래한다. useReducer는 초기 상태인 { count: 0 } 를 전달하고, 반환 값은 상태와 dispatch 함수를 포함하는 배열이다. dispatch 함수를 호출하면 reducer 함수가 실행되고, 새로운 상태가 반환된다.
useMemo
useMemo는 성능 최적화를 위해 사용된다. 이 Hook은 이전에 계산된 결과를 재사용하는 방식으로 작동하며, 이를 통해 렌더링 성능을 최적화할 수 있다.
예를 들어, 특정 계산 작업을 수행하는 함수가 있다고 가정한다면, 이 함수는 매번 호출될 때마다 계산 작업을 수행하며, 이로 인해 성능 문제가 발생할 수 있다. 하지만 useMemo를 사용하면, 함수의 결과를 캐싱하고 이전에 계산된 결과를 재사용할 수 있다. 이를 통해 불필요한 계산 작업을 줄이고 성능을 개선할 수 있다.
다음은 useMemo를 이용해 간단한 계산 작업을 수행하는 코드이다.
import React, { useMemo, useState } from "react";
function MyComponent(props: { value1: number; value2: number }) {
const [count, setCount] = useState(0);
const result = useMemo(() => {
console.log("Calculating result");
return props.value1 * props.value2 * count;
}, [props.value1, props.value2, count]);
return (
<div>
<p>Result: {result}</p>
<button onClick={() => setCount(count + 1)}>Increment count</button>
</div>
);
}
export default MyComponent;
이 코드에서는 useMemo를 사용해서 props.value1, props.value2, count가 변경될 때만 계산 작업을 수행하도록 설정했다. 따라서 setCount 함수가 호출될 때마다 계산 작업이 수행되지 않는다. 대신, 이전에 계산된 결과가 캐싱되어 재사용된다.
useMemo 함수는 두 개의 매개변수를 받는다. 첫 번째 매개변수는 계산 작업을 수행하는 함수이며, 두 번째 매개변수는 계산에 영향을 미치는 값의 배열이다. 이 배열은 모든 값이 일치해야 useMemo가 이전에 계산된 결과를 제사용하도록 허용한다. 만약 배열의 값이 변경된다면, useMemo는 다시 계산 작업을 수행하게 된다.
따라서 useMemo는 특정 계산 작업을 수행하는 함수를 최적화하고, 렌더링 성능을 개선하는데 사용될 수 있다.
useCallback
useCallback은 함수를 캐싱하고 재사용하는 방식으로 작동한다. 이 Hook을 사용하면 컴포넌트 내부에서 생성된 함수를 다시 만들지 않고, 이전에 생성된 함수를 재사용할 수 있으므로 성능을 개선할 수 있다.
예를 들어, 부모 컴포넌트에서 자식 컴포넌트로 함수를 전달하는 경우, 자식 컴포넌트에서 해당 함수를 실행하면 매번 새로운 함수가 생성되어 성능 문제가 발생할 수 있다. useCallback을 사용하면, 이전에 생성된 함수를 캐싱해서 재사용할 수 있으므로 성능 문제를 해결할 수 있다.
import React, { useCallback, useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Button clicked");
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment count</button>
</div>
);
}
export default MyComponent;
위 코드 예시는 useCallback 을 사용해서 handleClick 함수를 캐싱한다. 따라서 setCount 함수가 호출될 때마다 handleClick 함수가 다시 생성되지 않는다. 대신, 이전에 생성된 함수가 캐싱되어 재사용된다.
useCallback 함수는 두 개의 매개변수를 받는다. 첫 번째 매개변수는 캐싱할 함수이며, 두 번째 매개변수는 해당 함수를 재생성해야 하는 값의 배열이다. 이 배열은 모든 값이 일치해야 useCallback이 이전에 생성된 함수를 재사용하도록 허용한다. 만약 배열의 값이 변경된다면, useCallback은 다시 함수를 생성하게 된다.
useRef
useRef는 함수 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다. DOM요소나 컴포넌트 내에서 유지하고 싶은 변수를 생성할 때 사용한다. 이 변수는 컴포넌트가 다시 렌더링될 때마다 초기화되지 않고, 이전에 저장된 값을 유지한다.
예를 들어, useRef를 사용해 컴포넌트 내부에서 생성된 변수를 유지할 수 있다. 다음은 useRef를 사용해 특정 DOM 요소의 값을 변경하는 예시 코드이다.
import React, { useRef } from 'react';
function MyComponent() {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
inputRef.current?.focus();
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
이 예시에서는 useRef를 사용해서 inputRef 변수는 생성하고, 해당 변수를 input요소의 ref 속성에 할당한다. 따라서 input 요소에 대한 참조를 inputRef 변수에 저장할 수 있다.
handleClick 함수에서는 inputRef.current를 사용해 input 요소의 포커스를 설정한다. 이때, inputRef.current가 null일 수 있으므로 옵셔널 체이닝을 사용해 예외 상황을 방지한다.
useRef 함수는 초기값으로 null을 받을 수 있다. useRef는 항상 같은 객체를 반환한다. 따라서 컴포넌트가 다시 렌더링될 때마다 useRef를 호출해도 항상 같은 결과를 반환한다.
useRef는 컴포넌트 내부에서 유지해야하는 변수를 생성하고, 이전 값을 유지할 때 사용된다. 또한, useRef를 사용해 DOM 요소에 대한 참조를 저장하고, 해당 요소를 조작할 수 있다.
위 코드를 렌더링해보면, 값을 입력한 뒤 Focus Input 버튼을 눌렀을 때, 다시 커서가 입력칸 안을 가리키고 있는 것을 알 수 있다.
Custom Hooks
커스텀 Hooks은 여러 컴포넌트에서 공유될 수 있는 로직을 재사용할 수 있도록 도와주는 기능이다. 커스텀 Hooks를 사용하면 중복 코드를 제거하고, 코드를 보다 모듈화할 수 있다.
커스텀 Hooks sms "use"로 시작하는 함수명을 사용하여 정의한다.
import { useEffect, useState } from 'react';
function useTimer(interval: number): number {
const [time, setTime] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setTime((t) => t + 1);
}, interval);
return () => clearInterval(id);
}, [interval]);
return time;
}
위 예시는 커스텀 Hooks를 사용해서 타이머 값을 업데이트한다.
useEffect를 사용해서 setInterval을 설정하고, 해당 타이머 갓이 업데이트될 때마다 해당 값을 변경하는 로직을 작성한다. 또한, clearInterval을 사용하여 컴포넌트가 마운트 해제될 때 setInterval을 제거한다.
이 커스텀 Hooks를 사용해서 다음과 같은 방법으로 컴포넌트를 작성할 수 있다.
import React from 'react';
import useTimer from './useTimer';
function MyComponent() {
const timer = useTimer(1000);
return <div>Timer: {timer}</div>;
}
이 컴포넌트를 통해 useTimer Hooks를 사용해서 타이머 값을 설정하고, 해당 값을 출력해준다.
이처럼, 커스텀 Hook를 사용하면 컴포넌트의 로직을 모듈화해서 중복 코드를 제거할 수 있으며, 코드의 가독성을 높일 수 있다. 또한, Hooks를 사용해서 상태 관리, 이벤틀 처리 등의 로직을 작성할 수 있다.
마무리
리액트에서 Hooks 패턴을 사용하면 클래스형 컴포넌트를 작성하지 않아도 대부분의 기능을 구현할 수 있다. 클래스형 컴포넌트와 함수형 컴포넌트 중 어떤것을 사용하는지는 개발자의 선택이겠지만, 리액트 매뉴얼에서는 새로 작성하는 컴포넌트의 경우 함수형 컴포넌트와 Hooks를 사용할 것을 권장하고 있다. 따라서, 함수형 컴포넌트의 사용을 중점으로 하되, 꼭 필요할 경우에만 클래스형 컴포넌트를 구현하는것이 바람직하다고 생각된다.
'WEB > React' 카테고리의 다른 글
리액트 일정관리 웹 만들기 [react/typescript] (0) | 2023.03.17 |
---|---|
리액트 CSS 사용하기 [react/css/typescript] (0) | 2023.03.17 |
리액트 라이프사이클 메서드 사용하기 [react/life cycle/typescript] (0) | 2023.03.13 |
리액트 라이프사이클 이해하기 [react/life cycle/typescript] (0) | 2023.03.13 |
리액트 컴포넌트 반복 사용하기 [react/component/typescript] (0) | 2023.03.12 |