커스텀 훅을 만들어서 반복되는 로직을 쉽게 재사용할 수 있다.
커스텀 훅은 파일을 따로 만들어서 그 안에 함수를 작성해서 사용한다.
useState, useEffect, useReducer, useCallback 등 Hooks 를 사용하여 원하는 기능을 구현하고, 컴포넌트에서 사용하고 싶은 값들을 반환한다.
예시
함수에서 관리할 form 에 대하여 초기값을 파라미터(initialForm)로 받아오고, form 을 통해 상태도 조회할 수 있다.
onChange 를 통해 변경되는 이벤트를 관리한다. 초기화하고 싶다면 reset 을 호출하면 된다.
useInputs.js
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm); // 1
const onChange = useCallback(e => { // 2
const { name, value } = e.target;
setForm(form => ({ ...form, [name]:value })); // 3
}, []) // 4
const reset = useCallback(() => setForm(initialForm), [initialForm]); // 5
return [form, onChange, reset]; // 6
}
export default useInputs;
1. form 이라는 새로운 상태를 선언하는데, 상태의 초기값 initialForm 을 useInputs 의 파라미터로 받아온다.
2. onChange 함수는 useCallback 을 이용해서 e 이벤트 파라미터를 가져온 뒤, name 과 value 를 e.target 에서 추출한다.
3. setForm 에서는 ...form 을 통해 기존의 form 을 복사해준 뒤 업데이트 해주고 [name]: value 로 설정해준다.
setForm(form => ({ ...form, [name]: value }));
** ES5 문법: 화살표 함수
객체 리터럴 표현을 반환하기 위해서는 함수 본문(body)을 괄호 속에 넣어준다.
params => ({ foo:bar });
따라서 위에 괄호 ( ) 로 감싸준 부분을 풀어서 쓰면 다음과 같이 이해할 수 있다.
setForm( (form) => function () { return {...form, [name]: value}; });
4. 그리고 전체 useInputs 의 deps 배열은 의존하는 상태가 없으므로 비워주었다.
5. form 을 초기화시켜주는 reset 함수를 생성한다. 초기값을 받아온 것으로 설정해준다. dpes 배열에는 파라미터로 받아온 initialForm 을 배열에 넣어준다.
6. 생성한 함수들을 return 반환을 통해 내보내줄 수 있도록 한다.
App 함수에서 useInputs 객체를 불러와서 초기값을 설정해주었다.
const [form, onChange, reset] = useInputs({
username: '',
email: '',
})
form 으로부터 username 과 email 을 추출해준다. (비구조화 할당)
const { usernmae, email } = form;
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) {
console.log('활성 사용자 수를 세는중...');
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
},
{
id: 3,
username: 'liz',
email: 'liz@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;
useReducer 로 구현해보기
기존 useInputs.js
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm);
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(form => ({ ...form, [name]:value }));
}, [])
const reset = useCallback(() => setForm(initialForm), [initialForm]);
return [form, onChange, reset];
}
export default useInputs;
useReducer로 구현한 useInputs.js
import { useCallback, useReducer } from "react";
// useInputs의 set 함수를 useReducer 로 구현하기
// 1. reducer 생성하기 (일단 return state;)
// 2. useReducer 함수 작성하기
// 3. dispatch 액션 설정
// 4. reducer 함수에 액션 타입별 작동코드 설정
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;