이 글은 velopert님의 커스텀 Hooks 만들기 부분을 공부하고 정리한 글입니다.
컴포넌트를 만들다보면, 반복되는 로직이 자주 발생한다. 이때 커스텀 Hooks를 만들면 그러한 상황에서 반복되는 로직을 쉽게 재사용할 수 있다.
보통 use 라는 키워드로 시작하는 파일을 만들고 그 안에 함수를 작성한다. ( ex. useInputs.js )
그리고 그냥, 그 안에서 useState, useEffect, useReducer, useCallback 등 Hooks를 사용하여 원하는 기능을 구현해주고, 컴포넌트에서 사용하고 싶은 값들을 반환해주면 된다.
보통 src 폴더 안에 hooks 폴더를 따로 만들어 커스텀 훅만 정리한다.
예제1 (useState 사용)
input을 관리하는 코드는 다룰 때마다 꽤나 비슷한 코드가 반복된다. useInpusts.js라는 파일을 생성해 커스텀 훅을 만들어보자.
// useInputs.js
import {useState, useCallback} from 'react';
function useInputs(initialState){
const [form, setForm]=useState(initialState);
//change
const onChange=useCallback(e=>{
const {name, value}=e.target;
setForm(form=>({...form,[name]:value}));
},[])
const reset=useCallback(()=>setForm(initialState),[initialState]);
return [form, onChange, reset];
}
export default useInputs;
이제 useInputs 훅을 App.js에서 사용해보자. (🌈부분만 확인하면 된다.)
import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs'; //🌈
function countActiveUsers(users) {
return users.filter(user => user.active).length;
}
const initialState = {
users: [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
}
]
};
function reducer(state, action) {
switch (action.type) {
case 'CREATE_USER':
return {
users: state.users.concat(action.user)
};
case 'TOGGLE_USER':
return {
users: state.users.map(user =>
user.id === action.id ? { ...user, active: !user.active } : user
)
};
case 'REMOVE_USER':
return {
users: state.users.filter(user => user.id !== action.id)
};
default:
return state;
}
}
function App() {
const [{ username, email }, onChange, reset] = useInputs({
username: '',
email: ''
}); //🌈
const [state, dispatch] = useReducer(reducer, initialState);
const nextId = useRef(4);
const { users } = state;
const onCreate = useCallback(() => {
dispatch({
type: 'CREATE_USER',
user: {
id: nextId.current,
username,
email
}
});
reset();
nextId.current += 1;
}, [username, email, reset]);
const onToggle = useCallback(id => {
dispatch({
type: 'TOGGLE_USER',
id
});
}, []);
const onRemove = useCallback(id => {
dispatch({
type: 'REMOVE_USER',
id
});
}, []);
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange} //🌈
onCreate={onCreate}
/>
<UserList users={users} onToggle={onToggle} onRemove={onRemove} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;
//useInputs.js
import { useReducer, useCallback } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'CHANGE':
return {
...state,
[action.name]: action.value
};
case 'RESET':
return Object.keys(state).reduce((acc, current) => {
acc[current] = '';
return acc;
}, {});
default:
return state;
}
}
function useInputs(initialForm) {
const [form, dispatch] = useReducer(reducer, initialForm);
// change
const onChange = useCallback(e => {
const { name, value } = e.target;
dispatch({ type: 'CHANGE', name, value });
}, []);
const reset = useCallback(() => dispatch({ type: 'RESET' }), []);
return [form, onChange, reset];
}
export default useInputs;