

클래스 컴포넌트에서는 state와 관련된 기능 뿐만 아니라 컴포넌트의 생명주기 함수들까지 명확히 정의되어 있습니다.
하지만 함수 컴포넌트는 별도로 state를 정의해서 사용하거나 컴포넌트의 생명주기에 맞춰 어떤 코드가 실행되도록 할 수 없습니다.
React의 state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만들었는데, 이 때 실행되는 함수를 Hook 이라고 합니다. 이러한 hook의 이름은 모두 use로 시작합니다.
함수 컴포넌트에서는 기본적으로 state를 제공하지 않습니다. useState() 는 state를 사용하기 위한 hook입니다.
const [변수명, set함수명] = useState(초기값);
클래스 컴포넌트의 생성자에서 state를 선언할 때 초기값을 넣어주는 것과 동일합니다. 초기값을 넣어 useState를 노출하면 return 값으로 배열이 나옵니다.
import React, { useState } from 'react';
function Counter(props) {
var count = 0;
return (
<div>
<p> 총 {count}번 클릭했습니다.</p>
<button onClick={() => count++}>
클릭
</button>
</div>
);
}
Counter 함수 컴포넌트는 버튼을 클릭하면 카운트를 하나씩 증가시키고 현재 카운트를 보여주는 컴포넌트입니다.
하지만 이처럼 카운트를 함수의 변수로 선언해서 사용하게 되면 버튼 클릭 시 카운트 값을 증가시킬 수는 있지만 재렌더링이 일어나지 않아 새로운 카운트 값이 화면에 표시되지 않습니다.
import React, { useState } from 'react';
function Counter(props) {
const [count, setCount] = useState(0);
return (
<div>
<p> 총 {count}번 클릭했습니다.</p>
<button onClick={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
useState를 사용해 카운터 값이 변경되면 재렌더링되도록 합니다.
Side effect 를 수행하기 위한 훅입니다. React에서 Side effect 란 그냥 효과 혹은 영향을 뜻하는 effect에 가깝습니다. 예를 들면 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 등의 작업을 말합니다.
해당 작업들은 다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없습니다. useEffect함수는 생명 주기 함수들을 하나로 통합하여 제공합니다.
useEffect(이펙트 함수, 의존성 배열);
// 이펙트 함수가 mount, unmount 시에 단 한 번씩만 실행됨
useEffect(이펙트 함수, []);
// 컴포넌트가 업데이트될 때마다 호출됨
useEffect(이펙트 함수);
기본적으로 이펙트 함수는 처음 컴포넌트가 렌더링 된 이후와 의존성 배열에 있는 변수들 중 하나라도 값이 변경되어 재렌더링된 이후에 실행됩니다.
import React, { useState, useEffect } from 'react';
function Counter(props) {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷하게 작동합니다.
useEffect(() => {
// 브라우저 API를 사용해서 document의 title을 업데이트합니다.
document.title = `You clicked ${count} times`;
});
return (
<div>
<p> 총 {count}번 클릭했습니다.</p>
<button onClick={() => setCount(count + 1)}>
클릭
</button>
</div>
);
}
count라는 state에 접근하여 해당 값이 포함된 문자열을 생성해서 사용하고 있습니다.
import React, { useState, useEffect } from 'react';
function UserStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => { // 컴포넌트가 unmount될 때 호출됨
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
if (isOnline === null) {
return "대기 중...";
}
return isOnline ? '온라인' : '오프라인';
}
이 코드는 useEffect에서 먼저 서버 API를 사용하여 사용자의 상태를 구독하고 있습니다. useEffect에서 리턴하는 함수는 컴포넌트가 unmount될 때 호출됩니다. 결과적으로 componentWillUnmount함수가 하는 역할과 동일합니다.
function UserStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `총 ${count}번 클릭했습니다.`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
return () => {
ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
...
useEffect와 useState를 여러개 사용할 수도 있습니다.
Memoized value를 리턴하는 훅입니다.
Memoized value ?
memoization은 최적화를 위해서 사용하는 개념으로, 연산량이 많이 드는 함수의 호출 결과를 저장해두었다가 같은 입력 값으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해 놨던 호출 결과를 바로 반환하는 것을 말합니다. memoization된 결과값을
Memoized value라고 합니다.
const memoizedValue = useMemo(
() => {
// 연산량이 높은 작업을 수행하여 결과를 반환
return computeExpensiveValue(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
useMemo 훅 함수로 전달된 함수는 렌더링이 일어나는 동안 실행됩니다. Side effect같은 것들은 렌더링이 일어나는 동안 실행되서는 안되기 때문에 useMemo 훅 함수에 넣으면 안됩니다.
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b)
);
의존성 배열을 넣지 않을 경우, 렌더링이 일어날 때마다 매번 create함수가 실행되기 때문에 useMemo훅에 의존성 배열을 넣지 않고 사용하는 것은 아무런 의미가 없습니다.
const memoizedValue = useMemo(
() => {
return computeExpensiveValue(a, b);
},
[]
);
의존성 배열에 빈 배열을 넣게 되면 컴포넌트가 마운트될 때만 create함수가 호출됩니다. 마운트 시점에만 한 번 값을 계산할 필요가 있을 경우에는 이렇게 사용하면 됩니다.
useMemo훅과 유사하지만 값이 아닌 함수를 반환합니다. 컴포넌트가 렌더링될 때마다 의존성 배열의 값이 바뀐 경우에만 함수를 새로 정의해서 리턴합니다.
const memoizedCallback = useCallback(
() => {
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);
useMemo훅과 마찬가지로 함수와 의존성 배열을 파라미터로 받습니다. useCallback훅에서는 파라미터로 받는 함수를 callback이라고 합니다. 그리고 의존성 배열에 있는 변수 중 하나라도 변경되면 memoization된 callback함수를 반환합니다.
// 동일한 역할
useCallback(함수, 의존성 배열);
useMemo(() => 함수, 의존성 배열);
import { useState, useCallback, Children } from 'react';
function ParentComponent(props) {
const [count, setCount] = useState(0);
// 컴포넌트가 마운트되 때만 함수가 정의됨
const handleClick = useCallback((event) => {
// 클릭 이벤트 처리
}, []); // 의존성 배열이 빈 배열임
return (
<div>
<button
onClick={() => {
setCount(count + 1);
}}
>
{count}
</button>
<ChildrenComponent handleClick={handleClick} />
</div>
);
}
useCallback훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의하게 되므로 함수가 다시 정의되지 않은 경우에는 자식 컴포넌트도 재렌더링이 일어나지 않습니다.
의존성 배열에 빈 배열이 들어갔기 때문에 컴포넌트가 처음 마운트되는 시점에만 함수가 정의되고 이후에는 다시 정의되지 않으며 결국 다시 자식 컴포넌트도 불필요하게 재렌더링이 일어나지 않게 됩니다.
Reference객체를 반환하는 훅입니다. 리액트에서 Reference란 특정 컴포넌트에 접근할 수 있는 객체를 말합니다.
레퍼런스 객체에는 current라는 속성이 있는데 이것은 현재 레퍼런스하고 있는 엘리먼트를 의미합니다.
const refContainer = useRef(초기값);
파라미터로 초기값을 넣으면 해당 초기값으로 초기화된 레퍼런스 객체를 반환합니다. 이렇게 반환된 레퍼런스 객체는 컴포넌트가 마운트 해제 전까지 계속 유지됩니다.
<div ref={myRef} />
리애트에서는 위와 같이 코드를 작성하면 노드가 변경될 때마다 myRef의 current 속성에 현재 해당되는 DOM노드를 저장합니다. useRef훅은 일반적인 자바스크립트 객체를 리턴하기 때문에 다양한 변수를 저장할 수 있습니다.
function TextInputWithFocusButton(props) {
const inputElem = useRef(null);
const onButtonClick = () => {
// 'current'는 마운트된 input element를 가리킴
inputElem.current.focus();
};
return (
<>
<input ref={inputElem} type="text" />
<button onClick={onButtonClick}>
Focus the input
</button>
</>
);
}
useRef훅을 사용해 버튼 클릭 시 input에 포커스를 하도록 하는 코드입니다.
초기 값으로 null을 넣었고 결과로 반환된 inputElem라는 레퍼런스 객체를 input태그에 넣었습니다. 그리고 버튼 클릭 시 호출되는 함수에서 inputElem.current를 통해 실제 엘리먼트에 접근해 포커스 함수를 호출하고 있습니다.
useRef훅은 내부의 데이터가 변경되었을 때 별도로 알리지 않습니다. 그래서 current속성을 변경한다고 해서 재렌더링이 일어나지 않습니다.
돔노드의 변화를 알려면 Callback ref를 사용해야 합니다. React는 ref가 다른 노드에 연결될 때마다 callback을 호출하게 됩니다.
function MeasureExample(props) {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>안녕, 리액트</h1> // 매번 업데이트
<h2>위 헤더의 높이는 {Math.round(height)}px 입니ㅏㄷ.</h2>
</>
);
}
위 코드는 레퍼런스를 위해서 useRef훅을 사용하지 않고 useCallBack훅을 사용하는 callbackRef 방식을 사용했습니다. useCallBack훅에 의존성 배열을 비어있는 배열
callbackRef 방식은 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보들을 업데이트할 수 있습니다.
useCallBack훅에 의존성 배열을 빈 배열로 설정하면 h1 태그가 mount, unmount될 때만 callback함수가 호출되며, 재렌더링할 때는 호출되지 않습니다.