단의 개발 블로그
Hooks 본문
Hooks란?
리액트에 도입된 기능으로 함수 컴포넌트에서 다양한 작업을 도와주는 기능이다.
useState
기본적인 훅으로 함수 컴포넌트에서 가변적인 상태를 지니게 해준다. 함수 컴포넌트의 상태를 관리할 때 사용된다.
import {useState} from "react";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<p>count의 값: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</>
)
}
export default Counter;
import './App.css';
import Counter from "./components/Counter";
function App() {
return (
<>
<Counter/>
</>
);
}
export default App;

useEffect
리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정하는 훅이다.
import {useEffect, useState} from "react";
const User = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
console.log({
name: name,
email: email,
});
});
const onChangeName = e => {
setName(e.target.value);
}
const onChangeEmail = e => {
setEmail(e.target.value);
}
return (
<>
<input
value={name}
onChange={onChangeName}
/>
<input
value={email}
onChange={onChangeEmail}
placeholder="email" />
</>
)
}
export default User;
import './App.css';
import User from "./components/User";
function App() {
return (
<>
<User/>
</>
);
}
export default App;

해당 함수가 첫 마운트 될 경우에만 실행하고 싶다면 두번째 파라미터로 빈 배열 객체를 넣는다. 만약에 특정 state 값만 변경됐을때만 실행하고 싶다면 배열 대신 해당 state를 배열 요소로 추가해서 전달하면 된다.
컴포넌트가 언마운트 되거나 업데이트 직전에 수행할 작업이 있다면 cleanup 함수를 반환해서 처리해야 한다.
import {useEffect, useState} from "react";
const User = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
console.log({
name: name,
email: email,
});
return() => {
console.log("Cleanup!");
}
}, [name]);
const onChangeName = e => {
setName(e.target.value);
}
const onChangeEmail = e => {
setEmail(e.target.value);
}
return (
<>
<input
value={name}
onChange={onChangeName}
/>
<input
value={email}
onChange={onChangeEmail}
placeholder="email" />
</>
)
}
export default User;
useReducer
useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 할 경우 사용한다. reducer는 현재 상태, 업데이트를 위해 필요한 정보를 담은 액션을 전달받아 새로운 상태를 반환한다. 새로운 상태 생성 시 반드시 불변성을 지켜야 한다. 위에서 작업했던 useState를 useReducer로 변경해보자.
import {useReducer} from "react";
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return {value: state.value + 1};
case "DECREMENT":
return {value: state.value - 1};
default:
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, {value: 0});
return (
<>
<p>count의 값: {state.value}</p>
<button onClick={() => dispatch({type:'INCREMENT'})}>+1</button>
<button onClick={() => dispatch({type:'DECREMENT'})}>-1</button>
</>
)
}
export default Counter;
import './App.css';
import Counter from "./components/Counter";
function App() {
return (
<>
<Counter/>
</>
);
}
export default App;

첫번째 파라미터에는 reducer 함수, 두번째는 reducer의 기본 값으로 전달한다. 해당 훅을 사용하면 state와 dispatch를 받아오는데, 각각 상태와 액션을 의미한다. 해당 훅의 가장 큰 장점은 컴포넌트 갱신 로직을 바깥으로 빼서 사용이 가능한 것이다.
useMemo
함수 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다. 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용할 수 있게 해준다.
import {useMemo, useState} from "react";
const getSum = number => {
if(number.length === 0) return 0;
const sum = number.reduce((a, b) => Number(a) + Number(b));
return sum;
}
const Calc = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState(0);
const onChange = (e) => {
setNumber(e.target.value);
}
const onInsert = () => {
const nextList = list.concat([number]);
setList(nextList);
setNumber(0);
}
const sum = useMemo(() => getSum(list), [list]);
return (
<>
<input value={number} onChange={onChange} type="number" />
<button onClick={onInsert}>+</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>총합: {sum}</b>
</div>
</>
)
}
export default Calc;
import './App.css';
import Calc from "./components/Calc";
function App() {
return (
<>
<Calc/>
</>
);
}
export default App;

useCallback
useCallback은 useMemo와 비슷한 함수다. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 해당 훅을 사용하면 만들어놨던 함수를 재 사용할 수 있다. 위에서 생성한 onChange와 onInsert 함수는 리렌더링 될 때마다 새로 만들어진 함수를 사용하게 된다. 컴포넌트 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트 개수가 많아지면 성능 이슈가 발생하게 된다. 그때 아래와 같이 사용하면 해결할 수 있다.
import {useCallback, useMemo, useState} from "react";
const getSum = number => {
if(number.length === 0) return 0;
const sum = number.reduce((a, b) => Number(a) + Number(b));
return sum;
}
const Calc = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState(0);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
const onInsert = useCallback(e => {
const nextList = list.concat([number]);
setList(nextList);
setNumber(0);
}, [number, list]);
const sum = useMemo(() => getSum(list), [list]);
return (
<>
<input value={number} onChange={onChange} type="number" />
<button onClick={onInsert}>+</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>총합: {sum}</b>
</div>
</>
)
}
export default Calc;
useRef
DOM을 직접 건드려야 할 때 사용하는 기능이다. 예를들어 input html 태그에 포커스를 줘야할 때 사용되는데 많이 사용하지 않는것이 좋다. 스파게티 코드가 될 수도 있다.
import {useCallback, useMemo, useRef, useState} from "react";
const getSum = number => {
if(number.length === 0) return 0;
const sum = number.reduce((a, b) => Number(a) + Number(b));
return sum;
}
const Calc = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState(0);
const inputRef = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
const onInsert = useCallback(e => {
const nextList = list.concat([number]);
setList(nextList);
setNumber(0);
inputRef.current.focus();
}, [number, list]);
const sum = useMemo(() => getSum(list), [list]);
return (
<>
<input value={number} onChange={onChange} type="number" ref={inputRef}/>
<button onClick={onInsert}>+</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>총합: {sum}</b>
</div>
</>
)
}
export default Calc;
Custom Hooks
가장 중요한 기능이다. 여러 컴포넌트에서 비슷한 기능을 공유할 경우 사용자가 커스텀해서 훅으로 로직을 재사용할 수 있다.
import {useReducer} from "react";
function reducer(state, action) {
return {
...state,
[action.name]: action.value
}
}
const useInput = (form) => {
const [state, dispatch] = useReducer(reducer, {form});
const onChange = e => {
dispatch(e.target);
};
return [state, onChange]
}
export default useInput;
import useInput from "./useInput";
const User = () => {
const [state, onChange] = useInput({
name: "username",
email: "user@example.com",
})
const {name, email} = state;
return (
<>
<input
value={name}
onChange={onChange}
name="name"
/>
<input
value={email}
name="email"
onChange={onChange}
placeholder="email" />
<div>이름 {name}</div>
<div>이메일 {email}</div>
</>
)
}
export default User;
import './App.css';
import User from "./components/User";
function App() {
return (
<>
<User/>
</>
);
}
export default App;
