리액트는 제공해주지 않지만, 개발을 진행할 때 개인적으로 'hook'으로 만들면 더 편하겠다고 느낄 때, 자신만의 hook으로 만드는 것을 의미한다.
즉, 같은 로직이 반복될 때 두 로직을 나누어서 작성하는 게 가능할지라도, 굉장히 비효율적이다. 이러한 로직을 하나의 커스텀훅 으로 따로 정의하여 비효율성을 낮출 수 있다.
커스텀 훅은 컴포넌트 분할과는 달리, 컴포넌트 로직 자체를 분할 + 재사용 할 수 있게 하는 것이다.
use로 시작해야 한다.
리액트에서 hook들은 모두 use로 시작하는 것이 원칙이고, custom hook 또한 예외가 될 수 없다. use뒤 대문자로 시작하는 것 또한 지켜주어야 한다.
따라서 커스텀훅이 아닌 일반 함수를 정의할 때에는 use로 시작하는 것을 지양해주어야 한다.
state 자체가 아닌 state 저장 논리를 공유하는 것
hook에 대한 각 호출은 동일한 hook에 대해 모든 호출이 독립적이다.
즉, 같은 hook이 연이어 호출되었다고 해도 서로의 기능에는 영향을 미치지 않는다.
한마디로 잘 작동한다. 모두 독립적인 로직 자체만을 공유하는 것이기 때문!
custom hook은 순수 함수로 기대된다.
custom hook은 컴포넌트가 리렌더링되면 함께 리렌더링 된다.
커스텀 훅은 늘 최신의 props와 state를 받기 때문이다.
최상위에서만, React 함수 내에서만 호출해야 한다.
리액트에서는 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장되어야 한다라고 말한다. 즉, hook을 조건문이나 일반 함수에서 사용하게 되면, 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 호출이 보장되는 것에 대한 약속이 깨지는 결과가 일어난다.
CustomHook에 함수 넘겨주기
함수를 custom hook에게 넘겨주는 형태로 변환할 수 있다.
모든 로직이 겹친다고 하나하나 커스텀 훅으로 뺄 필요는 없다.
그렇지만, Effect에 관한 부분이 있다면, 커스텀훅으로 감싸주는 것이 더 낫다.
Effect를 남용해서는 안되기 때문,
커스텀 훅으로 감쌀 때, 더욱 명확하게 결과를 보여주는 경우가 있다.
근데 나는 겹치면 일단 훅으로 추출 해버릴듯...?
import React, { useState } from 'react';
const initialState = {
todos: [
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Practice React', done: false }
]
};
function App() {
const [state, setState] = useState(initialState);
const toggleTodo = (id) => {
const updatedTodos = state.todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
);
setState({ ...state, todos: updatedTodos });
};
return (
<div>
{state.todos.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => toggleTodo(todo.id)}>
{todo.done ? 'Undo' : 'Complete'}
</button>
</div>
))}
</div>
);
}
export default App;
import React, { useState, useCallback } from 'react';
// 커스텀 훅 정의
const useCustomState = (initialState) => {
const [state, setState] = useState(initialState);
const updateState = useCallback((updater) => {
setState(prevState => {
const nextState = { ...prevState };
updater(nextState);
return nextState;
});
}, []);
return [state, updateState];
};
// App 컴포넌트
function App() {
const [state, updateState] = useCustomState({
todos: [
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Practice React', done: false }
]
});
const toggleTodo = (id) => {
updateState(nextState => {
const todo = nextState.todos.find(todo => todo.id === id);
if (todo) {
todo.done = !todo.done;
}
});
};
return (
<div>
{state.todos.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => toggleTodo(todo.id)}>
{todo.done ? 'Undo' : 'Complete'}
</button>
</div>
))}
</div>
);
}
export default App;
updateState 함수를 반환해줘서 state의 관리에 쓰는 걸 볼 수 이씀
import React, { useState, useEffect, useCallback } from 'react';
// API 호출 및 상태 관리를 위한 커스텀 훅
const useTodos = (initialTodos) => {
const [todos, setTodos] = useState(initialTodos);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchTodos = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/todos');
const data = await response.json();
setTodos(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, []);
const toggleTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}, []);
useEffect(() => {
fetchTodos();
}, [fetchTodos]);
return { todos, loading, error, toggleTodo, fetchTodos };
};
// App 컴포넌트
function App() {
const { todos, loading, error, toggleTodo, fetchTodos } = useTodos([]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<button onClick={fetchTodos}>Refresh Todos</button>
{todos.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => toggleTodo(todo.id)}>
{todo.done ? 'Undo' : 'Complete'}
</button>
</div>
))}
</div>
);
}
export default App;
한 hook에서 많은 비즈니스 로직들을 수행하고 있지만 예시를 위한 것이지 따로 추출해내도 된다.