useMemo, useCallback, useRef [처음 만난 리액트 #22]
7강. Hooks
대표적인 Hooks
1. useState() & 2. useEffect()
Hooks의 개념과 useState, useEffect [처음 만난 리액트 #21]
7강. Hooks [무료] 처음 만난 리액트(React) - 인프런 | 강의 자바스크립트와 CSS 기초 문법과 함께 리액트의 기초를 탄탄하게 다질 수 있습니다., - 강의 소개 | 인프런... www.inflearn.com Hooks - Hooks은 리액
hisoit.tistory.com
3. useMemo()
Memorized value를 리턴하는 Hook
Memorized value란?
Memoization
최적화를 위해 사용하는 개념
비용이 높은, 즉 연산량이 많이 드는 함수의 호출 결과를 저장해두었다가 같은 입력값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해놨던 호출 결과를 바로 반환
-> 함수 호출 결과를 받기까지 걸리는 시간이 짧아짐 & 불필요한 중복 연산도 하지 않음 : 컴퓨터의 자원을 적게 사용
Memoization된 결과값을 Memorized value라고 부름
useMemo() 사용법
const memoizedValue = useMemeo(
() => {
// 연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
● useMemo() 훅은 파라미터로 memoizedValue를 생성하는 create 함수와 의존성 배열을 받음
● Memoization의 개념처럼, 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 create 함수를 호출하여 결과값을 반환하며, 그렇지 않은 경우에는 기존 함수의 결과값을 그대로 반환
● useMemo() 훅을 사용하면 컴포넌트가 다시 렌더링 될 때 마다 연산량이 높은 작업들을 반복하는 것을 피할 수 있음
-> 빠른 렌더링 속도
** useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행된다는 점을 기억!
렌더링이 일어나는 동안 실행되어서는 안될 작업을 useMemo()의 함수에 넣으면 안됨
ex) useEffect() 훅에서 실행되어야 할 side effect
서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 작업들은 렌더링이 일어나는 동안 실행되어서는 안되기 때문에 useMemo() 훅의 함수에 넣으면 안되고 useEffect() 훅을 사용해야 함
의존성 배열을 넣지 않을 경우, 매 렌더링마다 함수가 실행됨
const memeoizedValue = useMemo(
() => computeExpensiveValue(a, b)
);
-> useMemo() 훅에 의존성 배열을 넣지 않고 사용하는 것은 아무런 의미가 없음
의존성 배열이 빈 배열인 경우, 컴포넌트 마운트 시에만 호출됨
const memoizedValue = useMemo(
() => {
return computeExpensiveValue(a, b);
},
[]
);
마운트 이후에는 값이 변경되지 않음
마운트 시점에만 한 번 값을 계산할 필요가 있을 경우 이렇게 사용할 수 있음
But 대부분의 경우에는 useMemo() 훅의 의존성 배열에 변수들을 넣고, 해당 변수들의 값이 바뀜에 따라 새로 값을 계산해야할 경우 사용함
3. useCallback()
useMemo() Hook과 유사하지만 값이 아닌 함수를 반환
컴포넌트가 렌더링될 때 마다 매번 함수를 새로 정의하는 것이 아니라 의존성 배열의 값이 바뀌는 경우에만 함수를 새로 정의해서 리턴
useCallback() 사용법
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
● useMemo() 훅과 마찬가지로 함수(Callback)와 의존성 배열을 파라미터로 받음
● 의존성 배열에 있는 변수 중 하나라도 변경되면 Memoization된 Callback 함수를 반환
의존성 배열에 따라 memoized 값을 반환한다는 점은 useMemo() 훅과 동일
동일한 역할을 하는 두 줄의 코드
useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);
useCallback() 훅을 사용하지 않고 컴포넌트 내의 함수를 정의한다면, 매번 렌더링이 일어날 때 마다 함수가 새로 정의
-> useCallback() 훅을 사용하여 특정 변수의 값이 변한 경우에만 함수를 다시 정의하도록 해서 불필요한 반복작업을 업새줌
<예제코드 - useCallback() 훅을 사용하지 않은 경우>
import { useState } from "react";
function ParentComponent(props) {
const [count, setCount] = useState(0);
// 재렌더링 될 때마다 매번 함수가 새로 정의됨
const handleClick = (event) => {
// 클릭 이벤트 처리
};
return (
<div>
<button
onClick={() => {
setCount(count + 1);
}}
>
{count}
</button>
<ChildComponent handleClick={handleClick} />
</div>
);
}
위 코드처럼 useCallback() 훅을 사용하지 않고 컴포넌트 내에서 정의한 함수를 자식 컴포넌트의 props로 넘겨 사용하는 경우에 부모 컴포넌트가 다시 렌더링 될 때 마다 매 번 자식 컴포넌트도 다시 렌더링 됨
<예제코드 - useCallback() 훅을 사용한 경우>
import { useState } from "react";
function ParentComponent(props) {
const [count, setCount] = useState(0);
// 컴포넌트가 마운트 될 때만 함수가 정의됨
const handleClick = useCallback((event) => {
// 클릭 이벤트 처리
}, []);
return (
<div>
<button
onClick={() => {
setCount(count + 1);
}}
>
{count}
</button>
<ChildComponent handleClick={handleClick} />
</div>
);
}
useCallback() 훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 되므로,
함수가 다시 정의되지 않는 경우에는 자식 컴포넌트도 재렌더링이 일어나지 않음
이 경우에는 의존성 배열에 빈 배열이 들어갔기 때문에 컴포넌트가 처음 마운트 되는 시점에만 함수가 정의되고 이후에는 다시 정의되지 않으며, 결국 자식 컴포넌트도 불필요하게 재렌더링이 일어나지 않게 됨
3. useRef()
Reference를 사용하기 위한 Hook
Reference란?
특정 컴포넌트에 접근할 수 있는 객체
useRef() Hook은 Reference 객체를 반환
Reference 객체에는 current라는 속성이 있음
current : 현재 참조하고 있는 Element
useRef() 사용법
const refContainer = useRef(초깃값);
파라미터로 초깃값을 넣으면 해당 초깃값으로 초기화된 Reference 객체를 반환
만약 초깃값이 null이라면 current의 값이 null인 Reference 객체가 반환됨
이렇게 반환된 Reference 객체는 컴포넌트의 lifetime 전체에 걸쳐 유지
(컴포넌트가 마운트 해제 전 까지는 계속 유지)
useRef() Hook은 변경 가능한 current라는 속성을 가진 하나의 상자
useRef() Hook 예제코드
function TextInputWithFocusButton(props) {
const inputElem = useRef(null);
const onButtonClick = () => {
//`currunt`는 마운트된 input element를 가리킴
inputElem.current.focus();
};
return (
<>
<input ref={inputElem} type="text" />
<button onClick={onButtonClick}>
Focus the input
</button>
</>
);
}
▲ useRef() Hook을 사용하여 버튼 클릭 시 input에 focus하도록 하는 코드
초깃값으로 null을 넣었고, 결과로 반환된 inputElem이라는 Reference 객체를 input 태그에 넣어줌
버튼 클릭시 호출되는 함수에서 inputElem.current를 통해 실제 element에 접근하여 focus 함수를 호출하고 있음
<div ref={myRef} />
리액트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current 속성에 현재 해당되는 DOM 노드를 저장
useRef() Hook은 클래스의 인스턴스 필드를 사용하는 것과 유사하게 다양한 변수를 저장할 수 있다는 장점이 있다
- 일반적인 자바스크립트 객체를 리턴하기 때문
직접 current 속성이 포함된 자바스크립트 객체를 만들어서 써도 되는 것이 아닌가?
useRef() Hook은 매번 렌더링될 때 마다 항상 같은 Reference 객체를 반환하기 때문에 다름!
useRef() Hook은 내부의 데이터가 변경되었을 때 별도로 알리지 않는다
current 속성을 변경한다고 해도 재렌더링이 일어나지는 않음
Ref에 DOM 노드가 연결되거나 분리되었을 경우에 어떤 코드를 실행하고 싶다면 callback ref를 사용해야 함
Callback ref
DOM 노드의 변화를 알기 위한 가장 기초적인 방법으로 callback ref를 사용하는 방법
리액트는 ref가 다른 노드에 연결될 때 마다 callback을 호출
function MeasureExample(props) {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientReact().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>안녕, 리액트</h1>
<h2>위 헤더의 높이는 {Math.round(height)}px 입니다.</h2>
</>
);
}
위 코드에는 Reference를 위해서 useRef() Hook을 사용하지 않고, useCallback() Hook을 사용하는 Callback ref 방식을 사용
● useRef() Hook을 사용하게 되면 Reference 객체가 current 속성이 변경되었는지를 따로 알려주지 않기 때문
● Callback ref 방식을 사용하게 되면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고, 이를 통해 다른 정보들을 업데이트할 수 있음
- 위 예제 코드에서는 h1 태그의 높이값을 매 번 업데이트 하고 있음
- useCallback() Hook의 의존성 배열로 비여있는 배열(empty array)를 넣었는데, 이렇게하면 h1 태그가 마운트, 언마운트될 때만 callback 함수가 호출되며 재렌더링이 일어날 때는 호출되지 않음