React Hooks
React Hooks는 React 16.8에 새로 추가된 기능이다. React 클래스 컴포넌트를 작성하지 않고도 상태 및 리액트의 기능을 사용할 수 있게 된 것이다. 물론 리액트 팀에서도 밝혔지만, 리액트에서 클래스를 제거할 계획은 없기 때문에 훅이 도입되었다고 해서 모든 것을 마이그레이션 할 필요는 없다.
Golden Rule: Don't use them until absolutely necessary!
React Hooks
위에서도 말했지만, React Hooks가 도입됨으로서, 리액트의 클래스 컴포넌트를 작성하지 않고도 상태 및 리액트의 기능을 사용할 수 있게 됐다. 그렇다고 React Hooks는 기존 React의 기본 개념에 대한 부분을 대체하진 않는다. 다만 이미 알고 있는 props, state, context, ref, life cycle 등의 개념을 보다 직관적인 API를 제공해 준다.
useState
클래스에서 상태 관리를 위해 사용했던 this.setState()
대신 사용 가능한 훅이다. 함수 컴포넌트에서 상태를 쉽게 처리할 수 있도록 해준다. 또한 여러 사용도 가능하다.
useEffect
함수 컴포넌트에서 데이터 패칭, 구독, DOM
변경 등의 사이드 이펙트 작업을 수행할 때 사용한다. 이는 클래스 컴포넌트의 componentDidMount
, componentDidUpdate
, componentWillUnmount
와 동일한 목적으로 사용되며, 클래스 컴포넌트의 경우 3가지 함수로 제공되나, 함수 컴포넌트에서는 useEffect
라는 단일 API로 제공된다.
클래스 컴포넌트의 함수들과는 다르게 useEffect
또한 여러번 사용 가능하며, 이로 인해 관심사에 따른 사이드 이펙트의 분리를 만들 수 있다. 또한 내부에서 선택적으로 함수를 반환하여 Clean up
하는 방법을 지정할 수 있다.
만약 조건부로 효과를 발생시키고 싶다면, useEffect
의 두번째 인자에 있는 종속성 배열에 종속성을 추가하면 된다. 여기에 추가된 값 중 하나라도 변경되는 경우에도 Hook
이 실행된다.
memo
React.memo
는 고차 컴포넌트(HOC
)이다.
만약 컴포넌트가 **동일한 props
**를 받아 동일한 결과를 렌더링하는 경우, React.memo
를 사용하여 몇몇 경우에서 메모화된 결과를 이용하여 성능 향상을 이룰 수 있다. 이 경우 React
는 컴포넌트 렌더링을 건너 뛰고 마지막으로 렌더링된 결과를 재사용한다.
이때 React.memo
는 오직 props
의 변화만 얕은 비교(shallow compare
)를 한다. 문서에도 나와있지만 이 함수는 성능 최적화 용도로만 사용해야지, 렌더링 방지용으로는 사용하지 말자. 버그가 발생할 수 있으니 정말 앱이 느려지는 것을 발견하거나 아주 많은 목록을 렌더링할 때만 고려해 보고 도입하자.
+ 실제로 메모를 사용하여 성능 향상을 느낄 수 있는 부분은 상당히 적지만, 또 하나의 장점이 존재한다. 바로 props
를 비교 함수를 통해 직접 비교하여 컴포넌트의 렌더링 시기를 정확하게 선택할 수 있다는 점이다. 물론 이것을 위해 memo
를 쓰진 말자. 그냥 참고용이다.
고차 컴포넌트(HOC)란?
고차 컴포넌트란 컴포넌트를 받아서 새 컴포넌트를 반환하는 함수를 말한다. 컴포넌트의 로직을 재사용하기 위해 사용하는 기술이며, React에서는 일반적으로 HOC에는 접두사로 with를 많이 사용한다.
이보다 자세한 내용은 공식문서를 참고하자.
useMemo
useMemo
의 경우 React
컴포넌트 내부에서 사용되며, 메모된 값을 반환한다. useMemo
는 React.memo
와 달리 종속성을 처리한다. 즉 React.memo
는 두번째 인자로 비교 함수를 받지만, useMemo
의 경우 함수 대신 종속성 배열을 받아, 종속성 배열의 변경 사항을 체크하여 값이 변경된 경우에만 함수를 다시 실행하며 새 메모화된 값을 가져온다.
메모제이션이란?
함수가 실행되면 결과를 얻기 위해 다시 함수를 실행하는 것이 아니라, 처음 실행했을 때의 결과를 기억한다는 의미이다. 그리고 동일한 인자로 함수가 실행된다면 메모한 결과값을 반환한다.
useCallback
값을 메모하는 useMemo
와 달리 함수를 메모한다. useMemo
의 경우 인자를 받아 값을 반환하는 비용이 많이 드는 코드가 있는 경우 사용하여, 렌더링 간이 계산 비용이 많이 드는 코드를 다시 실행하지 않고 해당 값을 계속 참조할 수 있다.
반면 useCallback
은 렌더링 간에 지속되는 함수가 필요한 경우 함수의 해당 인스턴스를 기억할 수 있게 하기 위해 필요한다. 이는 컴포넌트 밖에서 함수를 만드는 것과 비슷하다고 생각할 수 있으며, 코드가 리렌더링될 때, 함수도 유지되게 된다.
아래 예제를 보면서 차이점이 무엇인지 생각해 보자.
여기서 각각 메모되고 있는 값은 무엇일까?
nextSeconds1
에서 useMemo
가 기억하고 있는 것은 1이며, nextSeconds2
에서 useCallback
이 기억하고 있는 것은 () => seconds + 1
이다. nextSeconds1
은 종속성이 변경될때까지 함수의 결과 값을 기억하고 반환할 것이다. nextSeconds2
는 종속성이 변경될때까지 함수를 재생성하지 않으며 제공한 함수를 기억할 것이다.
useCallback의 참조 동등성
참조 동등성이란 쉽게 말해 () => {} === () => {} 의 결과는 참조 동등하지 않기 때문에 false이지만, const func = ()= > {}라고 정의하고 func === func를 비교한다면 참조 동등하기 때문에 true의 결과가 나오는 것을 말한다.
useCallback 또한 렌더링 간의 참조 동등성을 제공한다.
useMemo와 useCallback은 항상 성능이 향상될까?
만약 위의 문장이 명제라면, React에서는 저 두 함수를 기본값으로 사용했을 것이다. 하지만 그렇지 않다. 왜냐하면 함수나 값을 메모하는 것과 관련된 코드와 업데이트가 필요한지 비교하는 코드는 훅에 전달하는 자체 함수의 코드보다 비용이 훨씬 크고 비쌀 수 있기 때문이다.
useLayoutEffect
useEffect
와 대부분의 기능이나 문법은 동일하지만, 다른 점은 useLayoutEffect
의 경우 DOM
의 변형이 끝난 후에 동기적으로 실행된다.
대부분의 모든 경우에서는 useEffect
를 사용하면 되나, 브라우저가 페인트하기 전에 직접 DOM
노드를 조작해야 한다면 그때 사용을 고려해 보자.
useImperativeHandle
useImperativeHandle
의 경우 forwardRef
와 같이 사용되며, 부모 컴포넌트에서 ref
를 사용할 때 노출될 인스턴스 값을 커스터마이징하기 위해 사용된다.
useReducer
useState
에 대한 대안이다. 복잡한 상태 로직을 가질때 사용하면 유용하다. redux
와 거의 동일하기 때문에 많은 설명은 공식 예제 코드로 대신하겠지만, 중요 차이점은 useReducer
은 컴포넌트와 해당 컴포넌트의 자식의 컨텍스트로만 제한된 다는 것이다(redux
는 전역 저장소를 전체 앱에서 접근할 수 있다).
Last updated