하나의 함수, 변수, 클래스, 컴포넌트에 한번에 너무 많은 로직이 들어가 있으면 코드를 파악하기 어렵고 문제의 원인을 찾기 쉽지 않다.
그래서 우리는 코드가 단위별로 하나의 관심사만 갖도록 하고 그 관심사에 대해서만 충실히 동작하도록 만들어야 한다. 컴퓨터 공학에서는 이렇게 각각의 관심사에 따라 코드를 분리하는 기법을 관심사의 분리
라고 부른다.
관심사의 분리
를 하게 되면 어떤 문제가 생겼을 때 전체 기능을 파악하기 위해 읽어야 하는 코드의 단위가 줄어들게 되고, 코드에 대한 파악이 빨라지므로 문제를 효과적으로 해결할 수 있게 된다. 즉 Divide & Conquer
(분할 - 정복)이 보다 쉬워지는 것이다.
또한 하나의 수정사항으로 인해 전체가 수정되는 것이 아니라, 해당 사항이 있는 일부분만 변경시키기 때문에 변화에 대해서도 내구성을 갖추게 된다.
관심사의 분리
가 적절히 구현된 코드에서는 다음과 같은 특성을 발견할 수 있다.
다음 코드는 하나의 컴포넌트 안에 View와 Logic이 혼재하고 있다.
const UserList = ({ users }) => {
const [users, setUsers] = useState([])
useEffect(() => {
fetchUsers("/users")
.then(res => res.json())
.then(res => setUsers(res.users))
}, [])
return (
<ul>
{users.map(user => {
return <li key={user.id}>{user.name}</li>
})}
</ul>
)
}
Custom Hook을 이용하면 다음과 같이 View와 Logic을 분리할 수 있다.
const UserList = ({ users }) => {
// Logic
const users = useGetUserList()
// View
return (
<ul>
{users.map(user => {
return <li key={user.id}>{user.name}</li>
})}
</ul>
)
}
// Logic - Custom Hook
const useGetUserList = () => {
const [users, setUsers] = useState([])
useEffect(() => {
fetchUsers("/users")
.then(res => res.json())
.then(res => setUsers(res.users))
}, [])
return users;
}
use-
로 시작하는 자바스크립트 함수이다. 컴포넌트 바깥에서 선언한다.useState
, useEffect
등의 Hook들을 Custom Hook 함수 안에서 사용하는 것이다.return
한다.return
값을 해당 컴포넌트에서 불러와 View를 그리는 데 사용한다.같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다.
두 Custom Hook은 서로 호출되는 위치와 타이밍이 다르며, 애초에 서로 다른 스코프(유효범위)를 생성하기 때문에 컴포넌트를 여러번 호출하는 것처럼 완전히 독립적이다.
Custom Hook은 컴포넌트와는 별도의 파일로 분리해야 한다.
src/hooks/
폴더 안에 파일로 생성한다.use---.js
처럼 함수명으로 한다.다음은 <UserInput>
컴포넌트이다. Logic을 추출해내고 View만 남은 모습이다.
// UserInput.js
const UserInput = () => {
const { userInfo, handleUserInput } = useUserInput()
const { size, position } = useDocumentResize()
return (
<div>
<input name="username" onChange={handleUserInput} />
<input name="id" onChange={handleUserInput} />
<input name="password" onChange={handleUserInput} />
<input name="email" onChange={handleUserInput} />
<div/>
)
}
다음은 Custom Hooks로 분리해낸 Logic이다.
유저 정보와 관련된 useUserInput
과 화면 크기 조절 시마다 문서의 크기와 위치를 리턴하는 useDocumentResize
두 가지 Logic으로 분리되어 있다.
// useUserInput.js
const useUserInput = () => {
const [userInfo, setUserInfo] = useState({
username: "",
id: "",
password: "",
email: ""
})
const handleUserInput = (e) => {
const { name, value } = e.target
setUserInfo(prev => ({ ...prev, [name]: value }))
}
return { userInfo, handleUserInput }
}
// useDocumentResize.js
const useDocumentResize = () => {
const [size, setSize] = useState({ width: 0, height: 0 })
const [position, setPosition] = useState({ width: 0, height: 0 })
useEffect(() => {
const handleDocumentSize = () => { ... }
document.addEventListener("resize", handleDocumentSize)
return () => {
document.removeEventListener("resize", handleDocumentSize)
}
}, [])
useEffect(() => {
const handleDocumentPosition = () => { ... }
document.addEventListener("resize", handleDocumentPosition)
return () => {
document.removeEventListener("resize", handleDocumentPosition)
}
}, [])
return { size, position };
}