Dev-Tino

Dev-Tino 8주차(4):: React Hook이란?

useEffect, useCallback, useMemo, useState, useContext

React 컴포넌트의 생명주기

리액트의 컴포넌트는 라이프 사이클(생명주기)가 존재한다 Mount(생성될 때), Update(업데이트할 때), Unmount(제거할 때) 세 가지로 나뉜다.

-Mounting: DOM이 생성되고 웹 브라우저에 나타나는 것을 마운트라고 한다.

-Updating: 컴포넌트가 업데이트될 때를 말하며, props, state가 바뀌거나 부모 컴포넌트가 리렌더링, 혹은 forceUpdate 함수를 통해 강제로 렌더링 시킬 때 등에 의한 상황에 발생한다.

-Unmounting: 컴포넌트가 DOM에서 제거되는 과정

HOOK

Hook은 함수 컴포넌트에서 React state와 생명 주기 기능(lifecycle features)을 연동(hook into)할 수 있게 해주는 함수이다. Hook은 class 안에서는 동작하지 않고, 대신 class 없이 React를 사용할 수 있게 해준다. react 라이브러리에서 import하여 사용하며 컴포넌트 내부 최상단에서 선언하고 사용할 수 있다.

이는 React 16.8부터 추가된 요소로, 다음과 같은 특징을 가진다::

-선택적 사용:
기존의 코드를 다시 작성할 필요 없이 일부의 컴포넌트들 안에서 HOOK을 사용할 수 있다. 그러나 당장 HOOK이 필요한 게 아니라면 HOOK을 사용할 필요는 없다.

-100% 이전 버전과의 호환성:

HOOK은 호환성을 깨뜨리는 변화가 없다.

-현재 사용 가능

HOOK은 배포 v16.8.0에서 사용할 수 있다. 이는 알고 있는 React 컨셉을 대신하는 대신 props, state, context, refs, 그리고 lifecycle과 같은 개념에 더 직관적인 API를 제공하기 위해 쓰이기 시작했다.

React는 기본적으로 몇 가지 HOOK을 지원한다. 자주 사용되는 HOOK으로는 다음이 있다.

useState

-상태(동적인 값)을 관리할 때 사용한다.

-값을 변경할 때 절대 상태를 직접 변경하는 것이 아닌, 상태 변경 함수에 값을 전달하는 식으로 변경한다.

이 훅을 사용하면 함수 컴포넌트에 상태를 추가할 수 있다. useState 훅은 현재 상태 값과 해당 값을 업데이트하는 함수로 구성된 배열을 반환한다.

useState Hook을 사용하여 함수 컴포넌트에 React 상태를 추가할 수 있다.

이 예시는 Counter 컴포넌트를 정의한다. state를 이용하여 count 변수를 추적하는데, useState(0)는 count를 0으로 초기화하고 setCount는 count를 업데이트하는 데 사용되는 함수이다. 버튼이 클릭되면 setCount가 count + 1 로 호출되어 count가 업데이트되고 컴포넌트가 업데이트된 count로 재렌더링한다.

useEffect

-마운드/언마운트/업데이트 시 할 작업을 설정할 때 사용한다.

-값이 변하면 업데이트 전, 후에 동일한 작업을 수행하게 된다.
두 번째 인자에 빈 배열을 넣게 되면 mount, unmount의 작업만 수행하게 된다.

이 훅을 사용하면 함수 컴포넌트에서 Side Effect(* 외부 세계와의 상호작용을 통해 발생하는, 예측이 어려운 결과. 리액트 컴포넌트가 이러한 외부 요소들과 상호작용할 필요가 있을 때, 그 결과가 항상 예측 가능한 것만은 아니다.)를 수행할 수 있다.
useEffect는 두 가지 인수를 받는데 각각 Side Effect 로직이 포함된 함수와 의존성 배열이다. 이 Side Effect 로직은 컴포넌트가 마운트될 때와 의존성 중 하나라도 변경될 때 실행된다. 컴포넌트가 마운트될 때 한 번만 실행하려면 두 번째 인수로 빈 배열[]을 전달하면 된다.

이 예시는 버튼이 클릭된 횟수에 따라 문서 제목을 업데이트하는 것을 보여준다. useEffect는 함수를 인자로 받아 매 렌더링 후 해당 함수를 실행한다. count 상태가 변경되면 컴포넌트가 다시 렌더링되고 효과가 실행되어 문서 제목이 업데이트된다.

useContext

-context 객체(React.createContext 에서 반환된 값)을 받아 그 context의 현재 값을 반환한다.

-context의 현재 값은 트리 안에서 이 Hook을 호출하는 컴포넌트에 가장 가까이에 있는 <MyContext.Provider>의 value prop에 의해 결정된다.

컴포넌트에서 가장 가까운 <MyContext.Provider>가 갱신되면 이 Hook은 그 MyContext.Provider에게 전달된 가장 최신의 context value를 사용하여 렌더러를 트리거한다. 상위 컴포넌트에서 React.memo 또는 shouldComponentUpdate(*현재 state 또는 props의 변화가 컴폰너트의 출력 결과에 영향을 미치는지 여부를 React가 알 수 있게 해주는 함수. 기본 동작은 매 state 변화마다 다시 렌더링을 수행하는 것이며, 대부분의 경우 기분 동작에 따른다. 이는 props 또는 state가 새로운 값으로 갱신되어서 렌더링이 발생하기 직전에 호출된다. 성능 최적화를 위한 것.)를 사용하더라도 useContext를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링된다. 이 때, useContext로 전달한 인자는 context 객체 그 자체여야만 한다.

이 예시는 기본값이 ‘light’인 컨텍스트를 생성하고 ThemeButton 컴포넌트에서 해당 컨텍스트를 사용한다. useContext는 컨텍스트 객체를 인자로 받아 현재 컨텍스트 값을 반환한다. 이 경우 현재 컨텍스트 값은 ‘light’이다.

useMemo

-이전에 계산한 값을 재사용할 때 사용한다.

-두 번째 인자에 의존할 값을 넣으면 해당 값이 변할 때마다 useMemo 또한 새로 연산된다.

- 두 번째 인자에 빈 배열을 넣으면 mount 시 한 번만 연산된다.

useMemo는 재렌더링 사이에 계산 결과를 캐싱할 수 있게 해주는 React Hook이다.

매개변수 중 caculateValue는 캐싱하려는 값을 계산하는 함수이다. 순수해야 하며, 인자를 받지 않고, 모든 타입의 값을 반환할 수 있어야 한다. React는 초기 렌더링 중 함수를 호출한다. 다음 렌더링에서, React는 마지막 렌더링 이후 dependencies가 변경되지 않았을 때 동일한 값을 다시 반환한다. 그렇지 않다면 calculateValue를 호출하고 결과를 반환하며, 나중에 재사용할 수 있도록 저장한다.

dependencies는 calculateValue 코드 내에서 참조된 모든 반응형 값들의 목록이다. 반응형 값에는 props, state와 컴포넌트 바디에 직접 선언된 모든 변수와 함수가 포함된다.

초기 렌더링에서, useMemo는 인자 없이 calculateValue를 호출한 결과를 반환한다. 다음 렌더링에서는 마지막 렌더링에서 저장된 값을 반환하거나(종속성이 변경되지 않은 경우), calculateValue를 다시 호출하고 반환된 값을 저장한다.

이 때, useMemo는 Hook이므로 컴포넌트의 최상위 레벨 또는 자체 Hook에서만 호출할 수 있으며(반복문이나 조건문 내부에서는 호출할 수 없다. 만약 호출이 필요하다면 새 컴포넌트를 추출하고 그 안으로 옮겨야 한다.)
Strict Mode에서는 계산 함수를 두 번 호출한다. (실제 프로덕션 환경에는 영향을 미치지 않는다.
이렇게 캐싱된 값은 버려야 할 특별한 이유가 없는 한 버려지지 않는다는 점을 주의하여야 한다.

useCallback

-useMemo를 기반으로 만들어졌다.

-특정 함수를 재사용하고 싶을 때 사용한다.

useCallbakc은 리렌더링 간에 함수 정의를 캐싱해주는 React Hook이다. useMemo와 비슷하게 작동하는데, useMemo는 특정 결과값을 재사용할 때 사용하는 반면 useCallback은 특정 함수를 새로 만들지 않도 재사용하고 싶을 때 사용한다.

이 함수들은 컴포넌트가 리렌더링 될 때마다 새로 만들어진다. 함수를 선언하는 것 자체는 메모리도, CPU도 많이 차지하지 않기에 그 자체만으로 큰 부하가 생길 일은 없으나, 함수를 재사용하는 것은 엄연히 중요하다.
useCallback은 다음과 같이 사용한다::

본 코드는 벨로퍼트의 게시글에서 받아왔다. 이 때, 함수 안세어 사용하는 상태 또는 props가 있다면 꼭 deps 배열 안에 포함시켜야 한다는 점이다.
deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면 함수 내에서 해당 값들을 참조할 때 가장 최신값을 참조할 것이라고 보장할 수 없다. props로 받아온 함수가 있다면, 이 또한 deps에 넣어주어야 한다.

useLocation

-이 훅은 현재 Location 객체를 반환한다. 현재 위치가 변경될 때마다 어떤 부작용을 수행하고 싶을 때 유용하다.

location.hash :: 현재 URL의 해시.
location.key :: 이 위치의 고유 키
location.pathname :: 현재 URL의 경로
location. search :: 현재 URL의 쿼리 문자열
location.state :: <Link state> 또는 navigate에 의해 생성된 위치의 상태 값

useLocation을 import하고 변수에 useLocation의 정보를 받는다. 이후 원하는 정보를 가져다 쓰면 된다고 한다.

react router에서 지원하는 이 hook으로, useNavigate를 이용해 전송된 데이터를 받을 수 있다.

useRef

-반환된 useRef 객체는 컴포넌트의 전 생애주기를 통해 유지된다.

-컴포넌트가 계속해서 렌더링이 되어도 컴포넌트가 언마운트 되기 전까지는 값을 그대로 유지할 수 있다.

-값을 변경하더라도, 상태를 변경할 때처럼 React 컴포넌트가 재렌더링되는 일은 없다.

-state와 비슷하게 어떤 값을 저장하는 저장 공간으로 사용된다.

-변경 시 렌더링을 발생시키지 말아야 하는 값을 다룰 때 사용한다. (변화는 감지해야 하지만 그 변화가 렌더링을 발생시키면 안될 때)

-일반함수 안의 변수와는 달리, 컴포넌트가 렌더링되더라도 언마운트 되기 전까지는 값을 계속해서 유지한다. (변수는 초기화될 가능성이 존재한다.)

useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook이다. 컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.
.current 프로퍼티로 전달된 인자(initialValue)로 초기화한, 변경 가능한 ref 객체를 반환한다. 반환된 객체는 컴포넌트의 전 생애 주기를 통해 유지될 것이다. 이를 통해 특정한 DOM 요소에 접근할 수 있지만 불필요한 재렌더링을 하지 않는다는 장점이 있다.

반환 요소에 접근하기 위해서는 다음과 같이 쓴다::


이 곳의 글은 다음 두 게시글을 참고하였다::

번외: useCallback과 useMemo에서의 ‘메모이제이션’

메모이제이션:: 컴퓨터 프로그램이 동일한 계산을 반복해야 할 째, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술

useCallback과 useMemo는 메모이제이션을 지원한다. useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하며 useMemo는 특정 결과값을 재사용한다는 차이점이 있지만 둘 모두 ‘무언가를 재사용하여 프로그램의 효율을 높이기 위한’ 도구라는 점에서 동일한 특징을 가진다.

(*https://react.vlpt.us/basic/17-useMemo.html 의 코드. 한 번 실행해보고자 하였으나 UserList와 CreateUsers에 대한 코드가 주어지지 않아 실패. 이에 대해서는 나중에 메모이제이션에 대해 더 자세히 조사하며 추가로 알아보지 싶다.)

본 코드 App.js에 countActiveUsers라는 함수를 만들어 active 값이 true인 사용자의 수를 세어 화면에 렌더링하였다. 이 때 countActiveUsers가 console.log로 ‘활성 사용자 수를 세는 중…’을 출력하는 것을 확인할 수 있다. 이를 통해 우리는 언제 이 함수가 실행되는지를 알 수 있다. 다른 계정명을 눌러 초록색으로 만들 때에 countActiveUsers가 실행되고, 활성 사용자의 수를 센 뒤 그 길이를 반환한다.

이 코드에서 발생하는 성능적 문제는 ‘input 값을 바꿀 때에도 countActiveUsers 함수가 작동된다는 것이다. 이는 users에 변화가 있을 때에만 세어야 하는 것인데 input의 값이 바뀔 때에도 리렌더링되는 것은 곤란하다.

useMemo는 이때문에 쓰이는 것이다. 이 불필요한 ‘함수 재사용’을 막기 위해 말이다. useMemo를 통해 우리는 불필요한 활성 사용자 수를 세는 것을 막고 전에 사용한 값을 재사용할 수 있다.

▲useMemo를 통해 수정한 코드

▲코드 중 useMemo 부분

useMemo의 첫 번째 파라미터에는 어떻게 연산할지 정의하는 함수를 넣고 두 번째 파라미터에는 deps 배열을 넣는다. 이 배열 안에 넣은 내용이 바뀌면 우리가 등록한 함수를 호출해 값을 연산한다. 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용한다.

[참고 자료]

-https://ko.legacy.reactjs.org/docs/hooks-intro.html

-https://react.devU/reference/react/hooks

-https://wikidocs.net/197627

-https://velog.io/@whansu/React-hooks-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

-https://react.dev/reference/react/useContext

-https://ko.legacy.reactjs.org/docs/hooks-reference.html

-https://ko.react.dev/reference/react/useMemo

-https://ko.react.dev/reference/react/useCallback

-https://react.vlpt.us/basic/18-useCallback.html

-https://reactrouter.com/en/main/hooks/use-location

-https://reactrouter.com/en/main/hooks/use-location

-https://velog.io/@nemo/useLocation

-https://ko.react.dev/reference/react/useRef //useRef

-https://velog.io/@hyoribogo/react-useref //useRef

-https://hihiha2.tistory.com/19

-https://react.vlpt.us/basic/17-useMemo.html

-https://react.vlpt.us/basic/18-useCallback.html

카테고리
#기타

댓글 0



추천 포스트