함수 컴포넌트
함수 컴포넌트란 함수를 기반으로 작성하는 컴포넌트를 말한다. 클래스형 컴포넌트에 비해 훨씬 짧고 직관적인 코드를 짤 수 있고, 함수 프로그래밍을 할 수 있게 해준다. 함수 컴포넌트의 Hook이라는 기능으로 클래스형 컴포넌트의 생명주기함수와 같은 기능을 사용할 수 있다.
리액트 공식 문서에서도 함수 컴포넌트 + Hook 조합을 추천하고 있다.
Hooks
리액트의 Hook 은 함수 컴포넌트에서 React state와 라이프사이클 기능을 “연동(hook into)“할 수 있게 해주는 함수이다.
Hook은 class 안에서는 동작하지 않고 class 없이 React를 사용할 수 있게 해준다.
Hook의 개요
리액트 훅을 도입하게 된 목적은 여러가지가 있다.
- 컴포넌트에서 상태관련로직을 사용할 때 Hook 이전에 재사용 가능한로직을 사용하기 위해서는 render props 나 고차 컴포넌트와 같은 패턴을 사용했는데 이런 패턴은 코드의 추적(코드의 독해)을 어렵게 만들었다.
- Hook을 활용하면 상태 관련로직을 추상화해 독립적인 테스트와 재사용이 가능해 레이어 변화 없이 재사용할 수 있다.
- 기존의 라이프사이클 메소드 기반이 아닌 로직 기반으로 나눌 수 있어서 컴포넌트를 함수 단위 로 잘게 쪼갤 수 있다는 이점 때문이다. (라이프사이클 메소드에는 관련 없는로직이 자주 섞여 들어가는데, 이로인해 버그가 쉽게 발생하고, 무결성을 쉽게 해친다.)
이 외에도 클래스형 컴포넌트 를 지양하고자 하는 목적도 있다.
Hook 사용 규칙
- 최상위 에서만 Hook을 호출해야한다. (반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 안된다.)
이 규칙을 따르면 컴포넌트가 렌더링될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장된다. - 리액트 함수 컴포넌트에서만 Hook을 호출해야하고, 일반 JS함수에서는 Hook을 호출해서는 안된다.
이 두가지 규칙을 강제하는 eslint-plugin-react-hooks 라는 ESLint플러그인이 있는데, 이 플러그인은 Create React App에 기본적으로 포함되어 있다.
Hook의 종류
기본 Hooks | 추가 Hooks |
useState | useReducer |
useEffect | useCallback |
useContext | useMemo |
useRef | |
useImperativeHandle | |
useLayoutEffect | |
useDebugValue |
useState
useState 는 가장 기본적인 Hook으로, 함수 컴포넌트에서도 가변적인 상태 를 지니고 있을 수 있게해준다. 함수 컴포넌트에서 상태(state) 를 관리해야 되는 일이 발생한다면 useState 를 사용하면 된다.
하나의 useState()는 하나의 상태 값만 관리할 수 있다.
const [state값, state변경함수] = useState(초기값);
const [value, setValue] = useState(0);
useEffect
useEffect 는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정 할 수 있게 해준다. 클래스형 컴포넌트의 componentDidMount() 와 componentDidUpdate() 를 합친 형태라고 생각하면 된다.
- 마운트 될 때만 실행하고 싶을 때만약 useEffect 에서 설정한 함수가 컴포넌트가 화면에 가장 처음 렌더링 될 때만 실행되고 업데이트 할 경우에는 실행 할 필요가 없는 경우엔 함수의 두번째 파라미터로 비어있는 배열을 넣어주면 된다.
useEffect(() => {
console.log('마운트 될 때만 실행');
}, []);
- 특정 값이 업데이트 될 때만 실행하고 싶을 때 useEffect 를 사용 할 때 특정 값이 변경이 될 때만 호출하게 하고 싶을 경우도 있다. useEffect 의 두번째 파라미터로 배열 을 넣고, 전달되는 배열 안에 검사하고 싶은 값 을 넣어주면 된다.
useEffect(() => {
console.log(name);
}, [name]);
배열 안에는 useState를 통해 관리하고 있는 상태를 넣어도 되고, props로 전달받은 값을 넣어도 된다.
- 마무리(뒷정리 하기) useEffect 는 기본적으로 렌더링 되고난 직후마다 실행되며, 두번째 파라미터배열에 무엇을 넣느냐 에 따라 실행되는 조건이 달라진다.
- 만약 컴포넌트가 언마운트 되기 전이나, 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect 에서 뒷정리(cleanup) 함수를 반환해주어야 한다.
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
});
언마운트 될 때만 뒷정리 함수를 호출하고 싶다면 useEffect 함수의 두번째 파라미터에 비어있는 배열을 넣으면 된다.
useEffect(() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
}, []);
useRef
useRef 는 함수 컴포넌트에서 ref 를 쉽게 사용 할 있게 해준다. (ref : Reference)
특징
- DOM요소에 이름을 부여할 때 사용한다.
- id를 대신해서 사용한다.
- 컴포넌트 내에서만 사용한다.
- DOM을 꼭 직접적으로 변경할 때 사용해야 한다.
(특정 input에 focus추가, 스크롤박스 조작, Canvas요소에 그림그리기 등)
useRef 를 사용하여 ref 를 설정하면, useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트 를 가르키게 된다.
useMemo
useMemo를 사용하면 함수 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다. 렌더링 과정이 필요 없을 때에도 연산을 실행하는 불필요한 상황을 useMemo를 사용하면 이러한 작업을 최적화 할 수 있다.
렌더링 하는 과정에서 특정 값 이 바뀌었을 때만 연산을 실행하고 만약에 원하는 값이 바뀐 것이 아니라면 이전에 연산했던 결과를 다시 사용하는 방식이다.
const avg = useMemo(() => getAverage(list), [list]);
// list 배열의 내용이 변경 될 때에만 함수 호출
useCallback
컴포넌트가 처음 렌더링 될 때만 함수를 생성하는 함수다. useCallback은 useMemo와 상당히 비슷하다. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용하는데, useCallback을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성 할 수 있다.
컴포넌트가 리렌더링 될 때마다 함수들이 새로 생성되는 대부분의 경우에는 문제가 되지 않지만, 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포넌트의 개수가 많아진다면 최적화 해주는 것이 좋다.
useCallback의 첫번째 파라미터에는 생성해주고 싶은 함수를 넣어주고, 두번째 파라미터에는 배열을 넣어주면 되는데 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야 하는지 명시해주어야 한다.
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(
e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
},
[number, list]
); // number 혹은 list 가 바뀌었을 때만 함수 생성