Hook React 버전 16.8부터 React 요소로 새로 추가된 기능이다. 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 사용할 수 있게 하는 몇 가지 기술을 Hook 이라고 부른다.
덕분에 기존에 함수형 컴포넌트에서 할 수 없던 상태값 관리 (useState), 컴포넌트 생명 주기 함수 (useEffect) 등을 이용할 수 있게 되었다.
함수형 컴포넌트와 클래스형 컴포넌트 차이:
클래스형 :
- state, lifeCycle 관련 기능 사용 가능
- 메모리 자원을 함수형 컴포넌트보다 조금 더 사용
- 임의 메서드를 정의할 수 있음
함수형 :
- state, lifeCycle 관련 기능사용 불가능 (Hook을 통해 해결 됨)
- 메모리 자원을 함수형 컴포넌트보다 덜 사용
- 컴포넌트 선언이 편함
출처: 클래스형과 함수형 차이
기존의 클래스형 컴포넌트에는 문제점이 꽤 있었다:
이러한 문제점들을 해결하기 위해, 클래스 없이도 리액트의 여러 기능들을 사용할 수 있도록 Hook을 개발하였다.
그렇다고 Class 형 컴포넌트를 없애지는 않는다. React는 항상 하위 호환성을 중요시 하기 때문에, 앞으로도 계속 Class 컴포넌트를 지원한다고 한다. 다만, 앞으로 작성하는 코드에는 Hook을 활용하는 것이 좋다.
Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 한다.
useState
는 상태를 관리하는 Hook 이다. 기본적인 특징은 아래와 같다:
state
값과, 이 값을 없데이트 하는 함수를 쌍으로 제공한다.state
는 컴포넌트가 다시 렌더링 되어도 그대로 유지된다.state
는 초기값을 받는다. (optional)state
는 객체일 필요가 없다. 문자열, 숫자, null, 객체, 배열 등 여러가지 값을 넣을 수 있다.그 특성들을 아래 예시에서 더 자세히 알아보자.
import React, { useState } from 'react';
function Example() {
// useState Hook을 React에서 가져옴
// "count"라는 새 상태 변수 선언. 0으로 초기화
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
// 버튼 클릭시 setCount 함수를 호출하여 state 변수(count)를 갱신
// 새로운 count 변수를 Example 컴포넌트에 넘기며 해당 컴포넌트를 리렌더링
Click me
</button>
</div>
);
}
위 예시에서 const [count, setCount] = useState(0);
가 바로 state hook 이다. useState
의 첫번째 인자 count
는 상태를 나타내고, 두번째 인자 setCount
는 해당 상태 값을 업데이트 하는 함수이다.
또한, useState(0)
을 보면 초기값을 0으로 설정한 것을 알 수 있다. 카운터를 셀 때 0부터 세기 위해서 넣었고, 이 초기값은 첫 번째 렌더링에만 딱 한번 사용된다.
'Click me' 라는 버튼을 누르면 onClick
이벤트로 setCount
함수를 실행시킨다. 함수 안에는 count + 1
이라고 적혀있는데, 기존 상태값 (count)에 1을 더해주는 함수이다. 즉, 버튼을 누를 때마다 count 상태 변수 값이 하나씩 올라가고 <p>You clicked {count} times</p>
에 보여지는 숫자가 1씩 증가한다.
function ExampleWithManyStates() {
// 상태 변수를 여러 개 선언했습니다!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
또한, 위 예시처럼 하나의 컴포넌트 내에서 여러개의 State Hook을 사용할 수도 있다.
Effect Hook, 즉 useEffect
훅은 함수 컴포넌트 네에서 side effect를 수행할 수 있게 해준다.
리액트 Side Effect 란?
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 모든 동작을 “side effects”(또는 짧게 “effects”)라고 한다. 왜냐하면 해당 동작은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문이다.
즉, 리액트 Class에서 사용했던 componentDidMount
나 componentDidUpdate
, componentWillUnmount
를 하나의 API로 통합한 것이 useEffect
훅 이다. useEffect
의 기본적인 특징은 아래와 같다.
useEffect(() => {
console.log('hello') ; // function
}, []); // deps
deps
를 생략하면 모든 렌더링에서 함수가 실행된다.// '리렌더링 될 때 마다' 실행됨
useEffect(() => {
console.log('hello') ;
});
deps
를 빈 배열([])로 설정하면 첫 렌더링에만 함수가 실행된다.// 처음 렌더링 될 때 '한번만' 실행됨
useEffect(() => {
console.log('hello') ;
}, []);
deps
에 원하는 props 혹은 state를 넣으면, 해당 값이 바뀔때 마다 함수가 실행된다.// count 라는 변수 (혹은 Props)가 업데이트 될때만 실행됨
useEffect(() => {
console.log('hello') ;
}, [count]);
return
을 사용해 클린업(clean-up) 함수 적용 (언마운트 될 때만 실행)// count 라는 변수 (혹은 Props)가 업데이트 될때만 실행됨
useEffect(() => {
console.log('hello') ;
return console.log('bye'); // 클린업 함수.
});
특정값이 업데이트 되기 직전에 클린업 함수를 실행시키고 싶다면, 뎁스에 특정 값을 넣어주면 된다.
클린업 (clean-up) 함수란?
- 기존 React의 Class에서는 흔히 componentDidMount에 구독(subscription)을 설정한 뒤 componentWillUnmount에서 이를 정리(clean-up) 한다.
- 이와 동일하게, effect 함수에서도 추가적인 클린업이 필요할 경우, return 이후에 클린업 함수를 적용하면 된다.
- React는 컴포넌트가 마운트 해제되는 때에 정리(clean-up)를 실행한다. 그리고 그 정리(clean-up)는 한번만이 아니라, 모든 리렌더링시에 실행된다.
클린업 (clean-up) 함수가 필요한 경우?
외부 데이터에 구독(subscription)을 설정해야 하는 경우에 클린업 함수가 필요하다. 이런 경우에 메모리 누수가 발생하지 않도록 정리(clean-up)하는 것은 매우 중요하기 때문.
DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트를 예제를 살펴보자.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷합니다
useEffect(() => {
// 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
document.title = `You clicked ${count} times`;
});
return (
<div>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위 useEffect
함수는 deps
를 생략했기 때문에 모든 리렌더링에서 아래 함수를 실행한다.
() => {
// 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
document.title = `You clicked ${count} times`;
}
기존 클래스 생명주기(LifeCycle)로 치자면 componentDidMount
와 componentDidUpdate
일때 해당 함수를 실행시켜주는 것이다.
위 예제를 실행하면, 화면 첫 렌더링 시 useState의 초기값 '0'이 문서 타이틀에 나타난다. 그리고 버튼을 클릭할 때마다 setCount 함수를 호출해 count 변수를 바꿔주고, DOM을 바꾼 뒤에 “effect” 함수를 실행해 변경된 count 값을 문서 타이틀에 보여준다.
그 외에도 아래와 같은 특성을 띈다:
useState
와 마찬가지로 컴포넌트 내에서 여러개의 effect를 사용 가능useContext
는 기존의 React에 존재하는 Context를 더 편하게 사용할 수 있게 한다.
Context란, 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있는 것을 뜻한다. context API를 사용하기 위해서는 Provider
, Consumer
, createContext
세 가지 개념을 알고 있어야 한다.
아래 예제를 통해 더 자세히 알아보자.
import React, { createContext } from "react";
import Children from "./Children";
// AppContext 객체 생성
export const AppContext = createContext();
const App = () => {
const user = {
name: "사용자",
age: "25"
};
return (
<>
// context provider 에 user 값을 전달
<AppContext.Provider value={user}>
<div>
<Children />
</div>
</AppContext.Provider>
</>
);
};
export default App;
import React, { useContext } from "react";
import { AppContext } from "./App";
const Children = () => {
// useContext를 이용해서 props를 불러옴
const user = useContext(AppContext);
return (
<>
<h3>user의 name은 {user.name}입니다.</h3>
<h3>user의 age는 {user.age}입니다.</h3>
</>
);
};
export default Children;
user의 name은 사용자입니다.
user의 age는 25입니다.
useReducer
는 useState
의 대체 함수(hook)이다. 기능은 비슷하나, 더 복잡한 함수를 필요로 할때 useReducer를 쓴다. 기본 구조는 다음과 같다.
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState와 동일하게, state를 관리하고 업데이트할 수 있다. 다수의 하윗값을 포함하는 복잡한 정적 로직을 만들거나, 다음 state가 이전 state에 의존적인 경우에 useState 대신 useReducer
를 사용한다.
앞서 살펴본 useContext
와 같이 사용되는 경우가 많다.
기존 useState
로 만든 카운터를 useReducer
로 변경하면 아래와 같다.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useCallback
을 사용하면 메모이제이션된 콜백을 반환한다. 즉, useCallback
은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용한다.
메모이제이션이란?
메모이제이션(memoization)은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
출처: 위키
useCallback은 useCallback(fn, deps)
와 같은 형태로 사용하는데, useEffect와 마찬가지로 함수와 의존성 배열을 인자로 갖는다.
const memoizedCallback = useCallback(
() => {
doSomething(a, b); // function
},
[a, b], // deps
);
하위 컴포넌트에 전달하는 콜백 함수가 inline 함수로 사용되거나 컴포넌트 내에서 함수를 직접 생성하고 있다면, 컴포넌트가 리렌더링 될 때 마다 새로운 함수가 만들어진다.
함수를 반복적으로 재선언하는 것 보다, 한번 만든 함수를 재사용하고 필요할 때만 재생성 하는것이 더 효율적이다. 따라서, useCallback으로 특정 함수를 재사용할 수 있다.
useMemo
는 메모이제이션된 값을 반환한다. 즉, 성능 최적화를 위해 연산된 값을 재사용하는 기능을 가진 훅이다.
memoization 이란?
memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말한다.
memoization을 절적히 적용하면 중복 연산을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있습니다.
출처: React(44) 리액트 훅 - useMemo 란?
React에서는 컴포넌트가 여러번 렌더링 될 수 있는데, 렌더링 될 때마다 함수를 호출해 반복적으로 연산을 한다. 이때, useMemo
를 활용해 매번 연산을 하지 않고, 연산된 값을 재사용할 수 있다.
사용법은 아래와 같다. 첫번째 파라미터에는 연산 함수를 넣고, 두번째에는 deps 배열을 넣어주면 된다. 해당 배열안의 내용이 변경될 때마다 등록한 연산 함수를 호출해 값을 연산한다.
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b) // 함수
, [a, b]); // deps
참고:
useRef
는 특정 DOM을 선택할 수 있게 하는 훅이다. useRef는 'reference'의 줄임말인데, 말 그대로 무엇인가를 참조할 때 사용된다.
javscript에서 특정 DOM 요소를 선택해야할 때 DOM Selector를 사용해본적이 있을 것이다. React 프로젝트에서도 특정 요소를 잡아 포커싱을 하거나, 선택해야하는 상황이 있다. 이런 상황에서 useRef
함수를 사용할 수 있다.
아래 예제를 통해 사용법을 알아보자.
버튼을 누르면 input 태그에 focus를 하는 기능이다.
function TextInputWithFocusButton() {
// 1. Ref 객체 생성
const inputEl = useRef(null);
const onButtonClick = () => {
// 3. Ref 객체의 `current` 는 우리가 선택한 DOM (즉, input)을 가리킴
// 4. 해당 DOM에게 포커싱을 해주는 DOM API focus()를 호출
inputEl.current.focus();
};
return (
<>
// 2. 선택하고 싶은 DOM에 (여기서는 input) 속성으로 ref값 설정
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>인풋 포커스</button>
</>
);
}
useRef 함수는 current 속성을 가지고 있는 객체를 반환하는데, 인자로 넘어온 초기값을 current 속성에 할당한다. 반환된 객체는 값을 변경해도 다시 렌더링 되지 않는다.또한, React 컴포넌트가 다시 렌더링 되어도, 해당 current 속성값이 변하지 않는다. 이러한 특성을 정리하자면 아래와 같다.
그 외에도 useImperativeHandle
, useImperativeHandle
, useDebugValue
등 여러가지 훅이 있다. 자세한 내용은 공식 도큐를 참고하면 좋다.
또한, 앞서 설명한 훅 뿐만 아니라, 나만의 훅을 직접 만들 수도 있고 다른 사람들이 만든 여러가지 훅들이 있다. (예시: https://github.com/rehooks/awesome-react-hooks). 내가 원하는 훅이 있는지 확인하고 프로젝트에 활용해봐도 좋을 것 같다.