리액트의 모든 훅 파헤치기
-
useState
- 게으른 초기화 방식이 있다. useState의 초깃값이 복잡하거나 무거운 연산을 포함하고 있을 때 사용해야 한다.
- 다음과 같은 형태로 사용한다.
게으른 초기화 이후 리렌더링이 발생된다면 이 함수는 무시된다.
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
- 일반적으로 사용하는 useState방식으로 사용하면 랜더링이 될 때 useState의 값도 재실행이 된다.
- 클로저로 구현을 했기 때문에 초깃값은 최초에만 사용이 된다. 만약 useState에 인수로 자바스크립트에 많은 비용을 요구하는 작업이 있다면, 계속 실행될 위험이 존재한다.
- 게으른 초기화 방식을 이용하면 최초 렌더링 이후에는 실행되지 않고, 최초의 state값을 넣을 때만 실행된다.
-
useEffect
- 애플리케이션 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 훅이다.
- useEffect에 의존성 배열을 넣지 않으면 매번 컴포넌트 랜더링 마다 실행이 되므로 useEffect없이 로직을 작성해도 될까?
- 아니다. useEffect는 클라이언트 사이드에서 실행되는 것을 보장해준다.
- useEffect는 컴포넌트 렌더링의 부수 효과이므로 컴포넌트의 렌더링이 완료된 이후 실행된다.
- 직접 실행을 하게 될 경우 컴포넌트가 렌더링되는 도중에 실행된다.
- 리액트는 값을 비교할 때 얕은 비교(1장에서 설명)를 한다. 따라서 useEffect의 의존성 배열도 얕은 비교로 변경사항을 감지한다.
- useEffect 의존성 배열에 빈 배열을 넣을땐 신중하자
- useEffect의 본질은 반드시 의존성 배열로 전달한 값의 변경에 의해 실행돼야 하는 것이다.
- 빈 배열을 넘기는 것은 state,props와 같은 어떤 값의 변경과 별개로 useEffect의 부수효과가 발생한다는 의미이다.
- 정말 의존성으로 빈 배열이 필요하다면 "정말 useEffect의 부수 효과가 컴포넌트의 상태와 별개로 작동해야만 하는지" 또는 "해당 컴포넌트에서 호출을 해야 하는지" 검토를 하자.
-
useMemo
useMemo(() => <ExpensiveComponent valuue={value} /> , [value])
다음과 같이 컴포넌트 자체를 메모이제이션할 수도 있다.물론React.memo
를 사용하는게 더 현명하다.
-
useCallback
- useMemo를 래핑한 함수이다. 메모이제이션을 하는 대상이 함수이다.
-
useRef
- 랜더링에 영향을 미치지 않는 고정된 값을 관리하기 위해 useRef를 사용한다.
- 그렇다면 아래와 같은 형태로 사용해도 되는거 아닐까?
let value = 0; function Component() { function handleClick() { value += 1; } // ... }
let value = 0; function Component() { function handleClick() { value += 1; } // ... }
- 그렇지 않다. 컴포넌트가 여러 번 생성되어도 가르키는 값이 모두 value로 동일하다. 이는 모듈 캐싱과 연관이 있다.
- useRef는 아래와 같이 구현이 되어있다. current의 값이 변경이 되어도 랜더링마다 동일한 객체를 가르키게 된다.
export function useRef(initialValue) { currentHook = 5; return useMemo(() => ({ current: initialValue }), []); }
export function useRef(initialValue) { currentHook = 5; return useMemo(() => ({ current: initialValue }), []); }
-
useContext
- 상위 컴포넌트에서 만들어진 Context를 함수형 컴포넌트에서 사용할 수 있도록 만들어진 훅이다.
- useContext를 사용하면 상위 컴포넌트 어딘가에 선언된
<Contenxt.Provider />
에서 제공한 값을 사용할 수 있게 된다. - useContext는 상태관리를 위한 리액트의 API가 아니다. "상태를 주입해 주는 API다."
- 상태관리 라이브러리는 어떠한 상태를 기반으로 다른 생태를 만들어 내거나 필요에 따라 상태 변화를 최적화할 수 있어야 한다.
- Provider의 값이 변경이 되면 자식 컴포넌트 전체가 리렌더링이 일어난다. (리액트의 렌더링 동작)
- 리렌더링을 막으려면 자식 컴포넌트에
React.memo
를 달아주면 된다.
-
useReducer
- useReducer의 세번째 인수는 useState의 게으른 초기화와 동일한 동작을 한다.
- useReducer를 사용하는 목적은 복잡한 형태의 state를 사전에 정의된 dispatcher로만 수정할 수 있게 만들어서 state값에 대한 접근은 컴포넌트에서만 가능하게 한다.
- state를 업데이트하는 방법은 컴포넌트 밖에다 둔다.
- 장점은 sate값을 변경하는 시나리오를 제한적으로 두고 컴포넌트에서는 값에만 집중할 수 있게 해주고 state를 변경하고 싶을 때는 reducer만 바라볼 수 있게 해준다.
useState와 useReducer의 차이는 없다. useState는 preact에서 useReducer로 구현이 되어있다.
useReducer,useState둘다 결국 클로저를 활용해서 값을 가둬서 state를 관리한다.
export function useState(initialState) {
currentHook = 1;
return useReducer(invokeOrReturn, initialState);
}
export function useState(initialState) {
currentHook = 1;
return useReducer(invokeOrReturn, initialState);
}
-
useImperativeHandle
- 부모에게서 넘겨받은 ref를 원하는 대로 수정할 수 있는 훅이다.
- 자식 컴포넌트에서 부모에게 넘겨받은 ref에 새로운 값을 추가해줄 수 있다. (함수를 넘겨주거나 값을 넘겨줄 수 있음)
-
useLayoutEffect
-
함수의 시그니처는 useEffect와 동일하나, 모든 DOM의 변경 후에 동기적으로 발생한다.
-
함수의 시그니처가 비슷하다는 것은 두 훅의 형태나 사용 예제가 동일하다는것이다.
-
useLayoutEffects는 브라우저에 변경 사항이 반영되기 전에 실행이 되고, useEffect는 브라우저에 변경 사항이 반영된 이후에 실행된다.
-
동작 과정
- 리액트가 DOM을 업데이트 (여기서 말하는 DOM이란 렌더링을 말하는 것이다. 브라우저에 실제로 해당 변경 사항이 반영(paint 단계)되는 시점이 아니다.)
- useLayoutEffect실행
- 브라우저에 변경 사항 반영
- useEffect 실행
-
"모든 DOM의 변경 후에 동기적으로 발생한다"의 뜻은 useLayoutEffect의 실행이 종료될 때까지 기다리고 화면을 그린다는 뜻이다.
- 잘못 사용할 경우 성능에 문제가 생길수도 있다.
-
어떻게 사용을 해야할까?
- DOM은 계산됐지만 이것이 화면에 반영되기 전에 하고 싶은 작업이 있을 때 (스크롤 위치를 제어하는 등 화면에 반영되기 전에 하고 싶은 작업)
-
훅의 규칙
- 최상위에서만 훅을 호출해야 한다. 반복문,조건문,중첩된 함수 내에서 훅을 실행할 수 없다.
- 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 사용자 정의 훅 두 가지 경우뿐이다.
사용자 정의 훅과 고차 컴포넌트
-
사용자 정의 훅
- 서로 다른 컴포넌트 내부에서 같은 로직을 공유할 때 사용
- 훅을 기반으로 개발자가 필요한 훅을 만드는 기법
- 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶을때 사용하자
-
고차 컴포넌트
- 컴포넌트 자체의 로직을 재사용하기 위해 사용
- 자바스크립트의 일급 객체, 함수의 특징을 이용하는 고차 함수의 일종이므로 자바스크립트 환경에서도 사용 가능
React.memo
가 고차 컴포넌트의 일종이다.- 렌더링의 결과물에도 영향을 미치는 공통 로직이라면 고차 컴포넌트를 사용하자(고차 컴포넌트가 많으면 복잡성이 증가하므로 신중하게 사용하자)