3. 리액트 훅 깊게 살펴보기
3.1. 리액트의 모든 훅 파헤치기
3.1.1. useState
함수형 컴포넌트 내부에서 상태를 정의하고, 관리할 수 있게 해주는 훅
const [state, setState] = useState(initialState)
useState
의 초깃값에 함수를 넣는 것 - 게으른 초기화 (lazy initialization)- 게으른 초기화 함수는 state가 처음 만들어질 때만 실행
- 초깃값이 복잡하거나 무거운 연산을 포함할 때 사용
3.1.2. useEffect
애플리케이션 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 매커니즘
function Component() {
useEffect(() => {
// code
}, [props, state])
첫 번째 인수 - 실행할 부수 효과가 포함된 함수
두 번째 인수 - 의존성 배열
렌더링 시점을 기준으로 의존성에 있는 값이 변경되면 부수 효과를 실행하는 원리
[Object.is](http://Object.is)
기반의 얕은 비교를 수행
클린업 함수
- 새로운 값을 기반으로 렌더링 뒤에 실행되지만, 함수가 정의됐을 당시에 선언됐던 이전 값을 보고 실행
- 즉, 렌더링 시 선언된 값을 보여줌
주의점
eslint-disable-line react-hooks/exhaustive-deps
주석 사용 지양- 의존성에 빈 배열이 들어가는 경우 eslint 경고가 뜨는데 이를 막아주는 주석
- 일반적으로 빈 배열을 넣는 상황은 만들지 않는것이 좋음
useEffect
의 첫 번째 인수에 함수명을 부여하기- 적절한 기명을 통해 쓰임을 알게하는것이 좋음
- 거대한
useEffect
만들기 지양- 작은 의존성 배열을 사용하는 여러개의
useEffect
로 분리 - 부득이하게 사용하게 된다면
useCallback
,useMemo
등으로 사전에 정제한 내용만 담는 것이 좋음
- 작은 의존성 배열을 사용하는 여러개의
- 불필요한 외부함수 만들기 지양
- 콜백 함수로 비동기 함수를 바로 넣지 않는 이유 -
useEffect
의 race condition 방지 - 내부에서 비동기 함수를 선언해 실행하거나, 즉시실행함수를 만드는 것은 가능
- 사용 시, 클린 업 함수에서 비동기 함수에 대한 추가적인 처리를 해주는 것이 좋음 (반복생성, 실행 방지)
- 콜백 함수로 비동기 함수를 바로 넣지 않는 이유 -
3.1.3. useMemo
비용이 큰 연산에 대한 결과를 저장해 두고, 저장된 값을 반환하는 훅
const memo = useMemo(() => expensiveComputation(a, b), [a, b])
의존성이 변경되지 않으면 함수를 재실행 하지 않음
단순한 값 뿐만 아니라, 컴포넌트도 가능 (굳이..)
3.1.4. useCallback
useMemo
는 값을 기억, useCallback
은 콜백 자체를 기억
의존성이 변경되었을 때만 함수가 재생성
3.1.5. useRef
useState
와 마찬가지로 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장
current
로 값에 접근 또는 변경할 수 있음
값이 변하더라도 렌더링이 발생하지 않음
일반적으로 DOM에 접근할 때 사용 (input, button 등 ..)
useMemo
를 통해 구현이 가능함
3.1.6. useContext
props drilling을 극복하기 위해 등장
<Provider>
에서 값을 가져오며, 여러개의 Provider가 있다면, 가장 가까운 Provider 값을 가져옴
컴포넌트 내부에서 사용 시, 컴포넌트 재활용이 어려워짐
Context는 상태관리용이 아닌 단순히 상태를 주입하는 기능만 있음 → 렌더링 최적화에는 별 도움이 되지 않음 → memo와 함께 사용하면 개선이 가능
3.1.7. useReducer
useState
의 심화 버전이라 볼 수 있음
const [state, dispatcher] = useReducer(reducer, initialState, init)
// state - 상태값
// dispatcher - state 업데이트에 사용되는 함수
// reducer - state가 어떻게 변경될지 정의
// initialState - 초깃값
// init - initialState 초기화 함수
복잡한 형태의 state를 사전에 정의된 dispatcher로만 수정이 가능하도록 만듦
내부적으로 useState
는 useReducer
로 구현돼 있음
3.1.8. useImperativeHandle
부모에게 넘겨 받은 ref
에 추가적인 동작을 정의할 수 있는 훅
3.1.9. useLayoutEffect
모든 DOM의 변경 후(렌더링)에 동기적으로 발생하는 훅
useEffect
와 시그니처가 동일함 (훅의 형태)
실행 순서는 다음과 같다
- DOM 업데이트
useLayoutEffect
실행- 브라우저에 변경 사항 반영
useEffect
실행
동기적으로 진행되기 때문에 컴포넌트가 중지되는것과 같은 일이 발생할 수 있음
따라서 DOM은 계산됐지만 화면이 반영되기 전에 하고 싶은 작업이 있을 때 사용해야 함
- 애니메이션
- 스크롤 제어
이를 통해 자연스러운 사용자 경험을 제공할 수 있음
3.1.10. useDebugValue
디버깅에 사용
훅 내부에서만 실행 가능하며 두 번째 인수로 넘어간 포매팅 함수는 첫 번쨰 인자가 변경되었을때 실행되어 값을 노출함
3.1.11. 훅의 규칙
- 최상위에서만 훅을 호출해야 함. 반복문, 조건문, 중첩된 함수 내에서 실행할 수 없음
- 훅은 리액트 어딘가에 있는 index와 같은 키 기반으로 구현돼 있음 (객체 기반 링크드 리스트에 더 가까움) → 파이버 객체의 링크드 리스트 호출 순서에 따라 저장됨
- 순서를 보장 받을 수 없는 상황이 온다면 에러가 발생
- 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 혹은 사용자 정의 훅 두가지 뿐
3.2. 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
재사용 로직을 관리할 수 있는 두 가지 방법
3.2.1. 사용자 정의 훅
서로 다른 컴포넌트 내부에서 같은 로직을 공유하고자 할 때 사용
use
를 접두사로 사용해야 훅이라 인식
3.2.2. 고차 컴포넌트
Higher Order Component / 고차 컴포넌트 기법으로 최적화, 중복 로직 관리에 사용
React.memo - 가장 널리 쓰이는 고차 컴포넌트
props가 변경되었을 때만 실행되도록 하는 역할 → 불필요한 렌더링 생략
with
를 접두사로 사용해야 고차 컴포넌트라 인식
부수효과를 최소화로 해야 하며 중첩해 사용하는 것 또한 최소화 해야 함
3.2.3. 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
사용자 정의 훅이 필요한 경우
- 단순히 리액트 훅으로만 공통 로직을 분리할 수 있는 경우
- 사용자 정의 훅 자체로는 렌더링에 영향을 미치지 못하기 때문에, 컴포넌트 내부에 미치는 영향을 최소화해 개발자가 훅을 원하는 방향으로 사용할 수 있음
- 부수효과가 비교적 제한적
⇒ 단순히 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶을 때 사용
고차 컴포넌트가 필요한 경우
- 렌더링 결과물에도 영향을 미치는 공통로직인 경우 고차 컴포넌트 사용
출처: 모던 리액트 Deep Dive