- 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 사용할 수 있게 하는 기능이다.
- 기존에 함수형 컴포넌트에서 못하던 상태값 관리, 컴포넌트의 생명 주기 함수를 이용할 수 있다.
- 함수형 컴포넌트들은 기본적으로 리렌더링이 될 때, 함수 안에 작성된 모든 코드가 다시 실행되어 기존에 가지고 있던 상태(state)를 전혀 기억할 수 없다.
- 리액트 훅을 통해 브라우저에 메모리를 할당함으로써 함수형 컴포넌트가 상태(state)를 가질 수 있게 되었다.
- => 클래스형 컴포넌트에서만 가능하던 상태관리를 더 손쉽게 할 수 있어 필요하다.
- 최상위에서만 Hook을 호출
- React 함수의 최상위에서만 Hook을 호출할 것
- 반복문, 조건문, 중첩된 함수 등에서 호출 X
- React 함수에서만 Hook을 호출
- Custom Hook에서는 호출 가능
- 일반적인 Javascript 함수에서는 호출 X
- Hook을 만들 때 앞에 use 붙이기
- React는 Hook이 호출되는 순서에 의존
- 한 컴포넌트에서 여러개의 Hook이 사용되는 경우 Hook은 위에서부터 아래로 순서에 맞게 동작한다.
- useState (동적 상태 관리)
- useEffect (side effect 수행 -mount/unmount/update)
- useContext (컴포넌트를 중첩하지 않고도 전역 값 쉽게 관리)
- useReducer (복잡한 컴포넌트들의 state를 관리/분리)
- useCallback (특정 함수 재사용)
- useMemo (연산한 값 재사용)
- useRef (DOM선택, 컴포넌트 안에서 조회/수정할 수 있는 변수 관리)
- useImperativeHandle
- useLayoutEffect
- useDebugValue
useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 정하기
-
useEffect를 사용할 때 첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열(
deps
)을 넣는다. -
deps
배열을 비우게 된다면, 컴포넌트가 처음 나타날 때에만 useEffect에 등록한 함수가 호출된다. -
useEffect는 함수를 반환할 수 있고, 이를
cleanup
함수라고 부른다. -
deps
배열이 비워져 있다면 컴포넌트가 사라질 때cleanup
함수가 호출된다. -
useEffect(() => { console.log('컴포넌트가 화면에 나타남'); return () => { console.log('컴포넌트가 화면에서 사라짐'); }; }, []);
-
마운트 시 주로 하는 작업
- props로 받은 값을 컴포넌트의 로컬 상태로 설정
- 외부 API 요청 (REST API 등)
- 라이브러리 사용 (D3, video.js 등)
- setInterval을 통한 반복 작업 혹은 setTimeout을 통한 작업 예약
-
언마운트 시 주로 하는 작업
- setInterval, setTimeout을 사용하여 등록한 작업들 clear하기 (clearInterval, clearTimeout)
- 라이브러리 인스턴스 제거
-
deps에 특정 값을 넣으면 컴포넌트가 처음 마운트될 때도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 된다.
-
cleanup 함수를 통해 언마운트시에도 호출이 되고, 값이 바뀌기 직전에도 호출이 된다.
-
useEffect(() => { console.log('user 값이 설정됨'); console.log(user); return () => { console.log('user 가 바뀌기 전..'); console.log(user); }; }, [user]);
-
주의! useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어야 한다.
- deps 파라미터를 생략한다면, 컴포넌트가 리렌더링될 때마다 호출된다.
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(enteredEmail.includes('@') && enteredPassword.trim().length > 6);
}, 500);
return () => {
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
- 키를 타이핑할 때마다 state를 업데이트 하는 것이 아니라 일정량 이상부터 키 입력을 수집하거나 키 입력 후 일정시간 동안 일시 중지되는 것을 기다린 후에 확인하고 싶을 수 있다.
- 모든 키 입력에 대해 500밀리초 후에 작업이 일어나는 것이 아니라 타이머를 저장하고 다음에 키가 입력되면 새로운 타이머를 설정하기 전에 전 타이머를 clean up 함수를 통해 지운다.
React.memo()로 불필요한 재평가 방지하기
-
리액트에서는 부모컴포넌트가 재평가되면 자식컴포넌트가 변경되지 않아도 재평가되는 문제점이 있다.
-
React.memo() 함수는 자식 컴포넌트의 props가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해준다.
-
사용방법
-
import React from 'react'; const CreateUser = ({ username, email, onChange, onCreate }) => { return ( <div> ... </div> ); }; export default React.memo(CreateUser);
-
함수형 컴포넌트에서 props가 바뀌었는지 확인할 컴포넌트를 지정한 후에 React.memo()로 감싼다.
-
-
React.memo는 인자로 들어간 컴포넌트에 어떤 props가 입력되는지 확인하고 입력되는 모든 props의 신규값을 확인한 뒤 기존의 props의 값과 비교하도록 리액트에 전달하고, props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 재평가한다.
-
하지만 최적화에도 비용이 따르기 때문에 적절하게 사용하는 것이 중요!
-
App.js
import React, { useState, useCallback } from 'react'; import Button from './components/UI/Button/Button'; import DemoOutput from './components/Demo/DemoOutput'; import './App.css'; function App() { const [showParagraph, setShowParagraph] = useState(false); console.log('APP RUNNING'); const toggleParagraphHandler () => { setShowParagraph((prevShowParagraph) => !prevShowParagraph); } return ( <div className="app"> <h1>Hi there!</h1> <DemoOutput show={false} /> <Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button> </div> ); } export default App;
-
DemoOutput.js
import React from 'react'; import MyParagraph from './MyParagraph'; const DemoOutput = (props) => { console.log('DemoOutput RUNNING'); return <MyParagraph>{props.show ? 'This is new!' : ''}</MyParagraph>; }; export default React.memo(DemoOutput);
-
Button.js
import React from 'react'; import classes from './Button.module.css'; const Button = (props) => { console.log('Button RUNNING'); return ( <button type={props.type || 'button'} className={`${classes.button} ${props.className}`} onClick={props.onClick} disabled={props.disabled} > {props.children} </button> ); }; export default React.memo(Button);
-
DemoOutput과 Button 컴포넌트 모두 React.memo를 적용했는데 Button 컴포넌트가 props가 바껴도 계속 재렌더링이 되는 문제점이 생겼다.
- 이유: 버튼에 전달되는 함수가 매번 재생성되기 때문
useCallback을 사용하여 함수 재사용하기
-
컴포넌트 실행 전반에 걸쳐 함수를 저장할 수 있게 하는 훅이다.
-
리액트에 이 함수를 저장하여 매번 실행할 때마다 이 함수를 재생성할 필요가 없다는 것을 알릴 수 있다.
-
선택한 함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행될 때마다 이것을 재사용한다.
-
저장하려는 함수를 래핑하여 사용한다.
-
const toggleParagraphHandler = useCallback(() => { setShowParagraph((prevShowParagraph) => !prevShowParagraph); }, []);
-
useCallback 훅을 사용하고 함수를 첫 번째 인자로 전달하면 useCallback은 저장된 함수를 반환한다.
-
이 App 함수가 다시 실행되면 useCallback이 리액트가 저장된 함수를 찾아서 같은 함수 객체를 재사용하게 된다.
-
어떤 함수가 절대 변경되어서는 안된다면 useCallback을 사용해 함수를 저장하면 된다.
-
두번째 인자는 useCallback 호출에 대한 의존성 배열로 함수 안에서 사용하는 상태 혹은 props가 있다면 이 의존성 배열 안에 포함시켜야 한다.
useMemo를 사용하여 연산한 값 재사용하기
-
연산된 값을 useMemo를 이용하여 재사용하여 성능을 최적화한다.
-
useCallback이 함수에 대한 것을 저장하듯이 모든 종류의 데이터를 저장할 수 있다.
-
첫 번째 인자에는 저장하고 싶은 것을 반환할 함수가 들어간다.
-
두 번째 인자에는 의존성 배열이 들어가는데, 배열 안에 넣은 내용이 바뀌면 앞에 등록한 함수를 호출해서 값을 연산해주고, 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용한다.
-
const { items } = props const sortedList = useMemo(() => { return items.sort((a, b) => a - b) }, [items])
-
const listItems = useMemo(() => [5, 3, 1, 10, 9], []) return( <DemoList title={listTitle} items={listItems} /> )