0. 작성하는 이유?
- 공식문서 요약을 빌미로 Hook에 대해서 더 알고 싶어서
1. Hook의 등장하게 된 이유
-
컴포넌트 사이에서 상태 로직을 재사용하기 어려움 → Custom Hook 등장배경(1)
- Render Props와 HOC 같은 패턴은 코드를 파악하기 어려움(→ 유지보수하기 어렵다)
- 상태 관련 로직을 추상화하기 어려움
-
복잡한 컴포넌트들은 이해하기 어렵다 → Custom Hook 등장배경(2)
-
lifeCycle Method에는 관련없는 로직이 포함되어 컴포넌트를 재사용하기 어려움
componentDidUpdate(){ // 데이터 요청 // 이벤트 리스너 등록(해당 컴포넌트에서만 사용되기 떄문에 재사용되기 어렵다) } componentWillUnmount() { // 이벤트 리스터 해제(해당 컴포넌트에서만 사용되기 떄문에 재사용되기 어렵다) }
-
이러한 문제를 해결하기 위해 redux, mobx를 통해 전역에서 상태관리를 진행
-
-
Class는 사람과 기계를 혼동시킨다. → hook api 이용
- 다른언어와는 다르게 작동하는 this(Dynamic Scope)
2. State Hook
-
Class Component의 this.setState와 동일한 기능을 한다.
-
왜 state Hook인가요?
- React 16.8전까지도 함수형 컴포넌트가 있었지만, state를 사용할 수 없었고, 단순히 props를 받아서 컴포넌트를 보여주는 역할만 하였다. (state가 필요하면, class 컴포넌트로 작성)
- 함수안에서 state를 사용할 수 있게 해준다
-
사용법
-
렌더링 할때, 오직 한번만 생성된다(re-redner하기전까지 유지)
-
useState를 Destructuring 문법을 통해 컴포넌트에서 사용할 state명, update함수명으로 선언
-
React는 hook의 순서를 가지고 이전 state를 알 수 있다(hook의 규칙 참고)
import React, { useState } from 'react' function Example() { const [count, setCount] = useState(0) return ( <div> <p> {count}</p> <button onClick={() => setCount(count + 1)}>+버튼</button> </div> ) }
-
3. Effect Hook
-
componentDidMount + componentDidUpdate + componentWillUnmount와 동일한 기능을 한다.
-
왜 Effect Hook인가요?
- 네트워크 통신 및 Component Dom조작은 side Effects라고 칭한다
- sideEffect는 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서 구현 할 수 없다. (렌더링 이후)
- 이러한 side effect 작업을 할 수 있는 곳이기 때문에, effect hook이라고 한다.
-
사용법
-
렌더링 이후! useEffect안에 있는 콜백이 실행(Dom이 업데이트되었다고 보장)
-
렌더링 이후이기때문에, 컴포넌트 돔에 접근하거나, State에 접근 가능
-
컴포넌트가 마운트 해제되었거나,reRender시 clean-up을 실행한다
function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null) useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline) } ChatAPI.subscribeToFriendStatus( props.friend.id, handleStatusChange ) // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다. return function cleanup() { ChatAPI.unsubscribeFromFriendStatus( props.friend.id, handleStatusChange ) } }) if (isOnline === null) { return 'Loading...' } return isOnline ? 'Online' : 'Offline' }
-
-
Effect Hook의 장점
-
useEffect를 여러개 사용하여, 역할/관심 별로 나누어줄 수 있다.
function FriendStatusWithCounter(props) { const [count, setCount] = useState(0) useEffect(() => { document.title = `You clicked ${count} times` }) const [isOnline, setIsOnline] = useState(null) useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline) } ChatAPI.subscribeToFriendStatus( props.friend.id, handleStatusChange ) return () => { ChatAPI.unsubscribeFromFriendStatus( props.friend.id, handleStatusChange ) } }) }
-
같은 역할이지만, Class 컴포넌트보다 가독성이 높다.
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // 이전 friend.id에서 구독을 해지합니다. ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 다음 friend.id를 구독합니다. ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.
-
4. Hook 규칙
-
오직 React 함수 내에서만 Hook을 호출해야한다 (Custom Hook 포함)
-
Component의 Hook은 반복이나, 조건문에 따라 순서가 변경되면 안된다(호출 순서로 state 접근)
function Form() { const [name, setName] = useState('Mary') useEffect(function persistForm() { localStorage.setItem('formData', name) }) const [surname, setSurname] = useState('Poppins') useEffect(function updateTitle() { document.title = name + ' ' + surname }) } // ------------ // 첫 번째 렌더링 // ------------ useState('Mary') // 1. 'Mary'라는 name state 변수를 선언합니다. useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect를 추가합니다. useState('Poppins') // 3. 'Poppins'라는 surname state 변수를 선언합니다. useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect를 추가합니다. // ------------- // 두 번째 렌더링 // ------------- useState('Mary') // 1. name state 변수를 읽습니다.(인자는 무시됩니다) useEffect(persistForm) // 2. 폼 데이터를 저장하기 위한 effect가 대체됩니다. useState('Poppins') // 3. surname state 변수를 읽습니다.(인자는 무시됩니다) useEffect(updateTitle) // 4. 제목을 업데이트하기 위한 effect가 대체됩니다. // ...
-
만약 호출 순서가 변경된다면?
// 🔴 조건문에 Hook을 사용함으로써 첫 번째 규칙을 깼습니다 if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name) }) } useState('Mary') // 1. name state 변수를 읽습니다. (인자는 무시됩니다) // useEffect(persistForm) // 🔴 Hook을 건너뛰었습니다! useState('Poppins') // 🔴 2 (3이었던). surname state 변수를 읽는 데 실패했습니다. useEffect(updateTitle) // 🔴 3 (4였던). 제목을 업데이트하기 위한 effect가 대체되는 데 실패했습니다.
-
조건에 따라 Hook을 사용하고 싶다면! 함수바디에서 조건을 추가시킨다.
useEffect(function persistForm() { // 👍 더 이상 첫 번째 규칙을 어기지 않습니다 if (name !== '') { localStorage.setItem('formData', name) } })
5. useCallback
-
메모리제이션된 콜백을 반환한다!
-
불필요한 렌더링을 방지하기 위해 사용된다(shouldComponentUpdate 역할과 동일)
-
자식 컴포넌트에 콜백을 전달할 때 유용
-
함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 또다른 함수 등)도 매번 다시 선언되어 사용된다.
-
useCallback(fn, deps)은 useMemo(() => fn, deps)와 같습니다.
const memoizedCallback = useCallback(() => { doSomething(a, b) }, [a, b])
6. useMemo
-
메모리제이션된 값을 반환한다!
-
useMemo로 전달된 함수는 렌더링 중에 실행된다
-
렌더링 중에는 하지 않는 것을 useMemo 함수 내에서 구현하면 안되며, useEffect에서 구현해야 한다.
-
useMemo는 성능최적화를 위해 사용될 수 있지만, 보장할 수 없다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
7. useRef
-
useRef는 current 프로퍼티에 ref가 걸려진 Dom node의 속성을 설정한다.
-
userRef는 변경을 캐치할 수 없다. 즉 current가 변경한다고 렌더링 되지 않는다.
function TextInputWithFocusButton() { const inputEl = useRef(null) const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus() } return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ) }