- 발행일
[ZBF] 리액트 HOOK TAIL ASK
[ZBF] 리액트 HOOK TAIL ASK
이 글은 네이버 블로그에 2024년 5월 21일에 올렸던 것을 그대로 옮겨온 것입니다.
https://github.com/streamich/react-use
- GitHub - streamich/react-use: React Hooks — 👍 — React Hooks — 👍. Contribute to streamich/react-use development by creating an account on GitHub.
함수 컴포넌트가 상태를 사용하거나 클래스 컴포넌트의 생명주기 메서드를 대체하기 위해 훅이 생성이 되었다.
리액트의 함수 컴포넌트 (Function Component)
함수 컴포넌트(Function Component)는 리액트에서 UI를 표현하기 위해 사용하는 함수 형태의 컴포넌트입니다. 함수 컴포넌트는 간단한 함수로, props를 받아서 리액트 요소를 반환합니다. 상태나 라이프사이클 메서드를 사용하기 위해서는 리액트 훅(Hook)을 사용합니다.
import React from 'react';
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
export default Greeting;
함수 컴포넌트의 장점
- 간결함: 클래스 컴포넌트보다 문법이 간결하여 작성 및 유지보수가 용이합니다.
- 이해하기 쉬움: 함수 형태로 정의되기 때문에, JavaScript 함수에 익숙한 개발자들에게 더 직관적입니다.
- 훅 사용 가능: 훅을 통해 상태와 라이프사이클 기능을 쉽게 사용할 수 있습니다.
- 성능: 함수 컴포넌트는 클래스 컴포넌트보다 메모리 사용량이 적고 성능이 더 좋습니다.
함수 컴포넌트의 단점
- 초기 렌더링 성능: 매 렌더링 시마다 함수와 훅이 재생성되어 초기 성능이 떨어질 수 있습니다.
- 훅의 사용 규칙 준수 필요: 훅은 특정 규칙을 따라야 하며, 이를 준수하지 않으면 버그가 발생할 수 있습니다.
- 라이프사이클 메서드의 명확성 부족: 클래스 컴포넌트의 라이프사이클 메서드에 비해 명확성이 떨어질 수 있습니다.
- this 컨텍스트 사용 불가: 함수 컴포넌트에서는 this를 사용할 수 없습니다.
- 고급 성능 최적화 어려움: 메모이제이션과 관련된 최적화를 적절히 구현하지 않으면 불필요한 렌더링이 발생할 수 있습니다.
리액트의 클래스 컴포넌트 (Class Component)
클래스 컴포넌트(Class Component)는 리액트에서 상태(state)와 라이프사이클 메서드를 사용하여 복잡한 UI 로직을 구현할 수 있는 컴포넌트 유형입니다. 함수 컴포넌트와 달리 클래스 컴포넌트는 ES6 클래스 문법을 사용하여 정의됩니다.
import React, { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
export default Greeting;
- 상태 관리: 클래스 컴포넌트는 자체 상태(state)를 관리할 수 있습니다.
- 라이프사이클 메서드: 클래스 컴포넌트는 컴포넌트의 생명 주기 동안 특정 시점에 작업을 수행할 수 있는 라이프사이클 메서드를 제공합니다.
- this 바인딩: 클래스 컴포넌트 내에서 메서드를 사용할 때 this를 바인딩해야 합니다.
상태 관리
클래스 컴포넌트는 state 객체를 통해 상태를 관리합니다. 상태는 컴포넌트의 데이터가 변할 때 UI를 다시 렌더링하는 데 사용됩니다.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>현재 카운트: {this.state.count}</p>
<button onClick={this.increment}>증가</button>
</div>
);
}
}
export default Counter;
라이프사이클 메서드
이 메서드들은 컴포넌트가 생성되고, 업데이트되고, 제거될 때 특정 작업을 수행할 수 있도록 합니다.
주요 라이프사이클 메서드:
- componentDidMount: 컴포넌트가 처음으로 DOM에 렌더링된 후 호출됩니다. 주로 데이터 페칭이나 DOM 조작 등의 작업을 수행합니다.
- componentDidUpdate: 컴포넌트가 업데이트된 후 호출됩니다. 이전 상태나 속성과 현재 상태나 속성을 비교하여 필요한 작업을 수행합니다.
- componentWillUnmount: 컴포넌트가 DOM에서 제거되기 전에 호출됩니다. 정리 작업(cleanup)을 수행하는 데 사용됩니다.
import React, { Component } from 'react';
class DataFetcher extends Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div>
{this.state.data ? (
<pre>{JSON.stringify(this.state.data, null, 2)}</pre>
) : (
<p>로딩 중...</p>
)}
</div>
);
}
}
export default DataFetcher;
this 바인딩
클래스 컴포넌트에서 메서드를 사용할 때, this가 클래스 인스턴스를 가리키도록 바인딩해야 합니다. 이를 위해 constructor에서 this를 바인딩하거나, 화살표 함수를 사용할 수 있습니다.
import React, { Component } from 'react';
class Button extends Component {
handleClick = () => {
console.log('버튼 클릭됨');
};
render() {
return (
<button onClick={this.handleClick}>클릭하세요</button>
);
}
}
export default Button;
- 클래스 컴포넌트는 상태와 라이프사이클 메서드를 사용하여 복잡한 UI 로직을 구현할 수 있는 컴포넌트 유형입니다.
- 상태 관리: state 객체를 통해 컴포넌트의 상태를 관리합니다.
- 라이프사이클 메서드: 컴포넌트의 생명 주기 동안 특정 시점에 작업을 수행할 수 있습니다.
- this 바인딩: 메서드를 사용할 때 this를 클래스 인스턴스로 바인딩해야 합니다.
클래스 컴포넌트의 생명 주기는 무엇인가요?
리액트 클래스 컴포넌트의 생명 주기(Lifecycle)는 컴포넌트가 생성되고, 업데이트되고, 제거되는 과정을 설명합니다. 각 단계에서 특정 메서드가 호출되어 필요한 작업을 수행할 수 있습니다. 생명 주기는 크게 세 가지 주요 단계로 나눌 수 있습니다.
마운트 (Mounting) : 마운트 단계는 컴포넌트가 DOM에 처음 삽입되는 과정
- constructor()
- static getDerivedStateFromProps(props, state)
- render()
- componentDidMount()
업데이트 (Updating) : 업데이트 단계는 컴포넌트가 다시 렌더링되는 과정
- static getDerivedStateFromProps(props, state)
- shouldComponentUpdate(nextProps, nextState)
- render()
- getSnapshotBeforeUpdate(prevProps, prevState)
- componentDidUpdate(prevProps, prevState, snapshot)
언마운트 (Unmounting) : 언마운트 단계는 컴포넌트가 DOM에서 제거되는 과정
- componentWillUnmount()
클래스 컴포넌트의 장점
- 명확한 라이프사이클 메서드:
- 컴포넌트의 생명 주기 동안 특정 시점에 작업을 수행할 수 있는 메서드 제공
- 예: componentDidMount, componentDidUpdate, componentWillUnmount
- 상태 관리와 메서드 정의:
- this.state와 this.setState를 사용하여 상태를 관리할 수 있음
- 클래스 메서드를 통해 로직을 정의할 수 있음
- this 컨텍스트 사용 가능:
- this를 통해 클래스 인스턴스에 접근하여 상태와 메서드에 쉽게 접근 가능
클래스 컴포넌트의 단점
- 복잡한 문법과 코드 가독성:
- 함수 컴포넌트에 비해 문법이 복잡하고 코드가 장황해질 수 있음
- 추가적인 코드가 필요 (예: constructor에서 상태 초기화, this 바인딩)
- 성능 최적화의 어려움:
- 함수 컴포넌트와 훅에 비해 성능 최적화가 어려울 수 있음
- shouldComponentUpdate 메서드를 사용하여 성능 최적화 필요
- 훅 사용 불가:
- 클래스 컴포넌트에서는 훅을 사용할 수 없음
- 함수 컴포넌트에서만 사용할 수 있는 훅을 통해 간결하게 상태 관리 및 라이프사이클 관리 가능
| 특징 | 클래스 컴포넌트 (Class Component) |
|---|---|
| 명확한 라이프사이클 관리 | 명확한 라이프사이클 메서드를 제공 (componentDidMount, componentDidUpdate, componentWillUnmount) |
| 상태 관리 | this.state와 this.setState를 사용하여 상태 관리 |
| 메서드 정의 | 클래스 메서드를 통해 로직 정의 및 this 사용 |
| 코드 복잡성 | 문법이 복잡하고 코드가 장황해질 수 있음 |
| 성능 최적화 | shouldComponentUpdate를 사용하여 성능 최적화 |
| 훅 사용 불가 | 함수 컴포넌트에서만 사용할 수 있는 훅 사용 불가 |
클래스 컴포넌트의 한계
- 복잡한 문법과 코드 가독성
클래스 컴포넌트는 상태 관리와 메서드 바인딩 등으로 인해 문법이 복잡해질 수 있습니다. 이는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만들 수 있습니다.
- this 바인딩 문제
클래스 컴포넌트에서 이벤트 핸들러 메서드는 this를 명시적으로 바인딩해야 합니다. 이를 실수로 빠뜨리면 런타임 에러가 발생할 수 있습니다. 화살표 함수를 사용하거나, constructor에서 this를 바인딩하는 작업이 필요합니다.
- 더 많은 코드 작성
클래스 컴포넌트는 함수 컴포넌트에 비해 더 많은 코드가 필요합니다. 특히 상태 관리나 라이프사이클 메서드를 사용할 때 코드가 길어질 수 있습니다.
- 훅(Hooks)을 사용할 수 없음
훅은 함수 컴포넌트에서만 사용할 수 있습니다. 훅은 상태와 라이프사이클 기능을 더 간단하고 직관적으로 관리할 수 있게 해주지만, 클래스 컴포넌트에서는 사용할 수 없습니다. 훅의 도입으로 함수 컴포넌트가 더 강력해졌습니다.
- 성능 문제
클래스 컴포넌트는 함수 컴포넌트보다 메모리 사용량이 더 많고, 컴파일러 최적화가 덜 효과적일 수 있습니다. 함수 컴포넌트는 상대적으로 가볍고 최적화가 용이합니다.
state와 props는 무엇인가요?
Props (Properties)
- 목적: props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용합니다. 이를 통해 컴포넌트 간에 데이터를 공유할 수 있습니다.
- 불변성: props는 컴포넌트에 전달되고 나면 해당 컴포넌트 내에서 변경할 수 없습니다. 즉, 자식 컴포넌트는 props를 받기만 하고, 직접 수정할 수는 없습니다.
- 예시: 부모 컴포넌트에서 자식 컴포넌트로 사용자 이름이나 클릭 이벤트 핸들러 같은 것들을 넘길 때 사용됩니다.
- State
- 목적: state는 주로 컴포넌트의 상태를 내부적으로 관리하기 위해 사용됩니다. 컴포넌트의 동적 데이터를 저장하고 변경할 수 있습니다.
- 변경 가능성: 컴포넌트의 state는 setState() 함수(클래스 컴포넌트)나 useState() 훅(함수 컴포넌트)을 통해 변경할 수 있습니다. state의 변경은 컴포넌트의 재렌더링을 유발할 수 있습니다.
- 예시: 사용자의 입력 데이터를 관리하거나, 버튼 클릭으로 인한 UI 상의 변화를 처리할 때 사용됩니다.
| 특징 | State | Props |
|---|---|---|
| 데이터 소유자 | 컴포넌트 내부에서 관리 | 부모 컴포넌트가 자식 컴포넌트에 전달 |
| 변경 가능성 | 변경 가능 (this.setState 또는 useState) | 읽기 전용, 변경 불가 |
| 초기화 위치 | 컴포넌트 생성 시 초기화 | 부모 컴포넌트에서 설정 |
| 목적 | 컴포넌트의 동적인 데이터 관리 | 컴포넌트 간의 데이터 전달 |
리액트의 렌더링 과정
- 초기 렌더링: 컴포넌트가 처음으로 DOM에 삽입될 때 발생하며, Virtual DOM을 생성하고 실제 DOM에 반영합니다.
- 상태나 속성 변경에 따른 업데이트: 컴포넌트의 상태나 속성이 변경될 때 다시 렌더링하며, 새로운 Virtual DOM을 생성하고 이전 Virtual DOM과 비교하여 변경된 부분만 실제 DOM에 업데이트합니다.
- Virtual DOM을 통한 효율적인 업데이트: Virtual DOM을 사용하여 변경된 부분만 실제 DOM에 최소한의 업데이트를 수행하여 성능을 최적화합니다.
훅의 규칙
- 훅은 최상위에서만 호출해야 합니다: 루프, 조건문, 중첩 함수 내에서 훅을 호출 금지
- 훅은 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출해야 합니다: 일반 함수에서 훅을 호출 금지
- 커스텀 훅의 네이밍 규칙: 커스텀 훅은 use로 시작하는 이름을 가져야 합니다.
훅을 진행할때 변경 할수 없는 이유, 훅 호출 순서의 일관성: 훅은 컴포넌트의 최상위 레벨에서만 호출해야 하며, 호출 순서가 일관되어야 리액트가 각 훅의 상태를 정확히 추적하고 관리할 수 있습니다.
setState에서 바뀌는 이유, 상태 변경 메커니즘: setState 함수는 상태 변경을 요청하며, 리액트는 이를 감지하고 컴포넌트를 다시 렌더링합니다. 이 과정에서 Virtual DOM 비교를 통해 최소한의 DOM 업데이트를 수행하여 성능을 최적화합니다.
클래스 컴포넌트와 함수 컴포넌트 비교
| 특징 | 클래스 컴포넌트 (Class Component) | 함수 컴포넌트 (Function Component) |
|---|---|---|
| 정의 방법 | ES6 클래스 사용 | 함수 사용 |
| 상태 관리 | this.state와 this.setState를 사용 | useState 훅을 사용 |
| 라이프사이클 메서드 | 다양한 라이프사이클 메서드를 지원 (componentDidMount, componentDidUpdate, componentWillUnmount 등) | useEffect 훅을 사용하여 라이프사이클 효과를 구현 |
| this 바인딩 | this 바인딩이 필요 (constructor에서 바인딩) | this 바인딩이 필요 없음 (화살표 함수 사용 가능) |
| 메서드 정의 | 클래스 메서드로 정의 (this.methodName) | 함수 내에서 정의 (화살표 함수 사용 가능) |
| 훅 사용 가능 여부 | 훅을 사용할 수 없음 | 훅을 사용할 수 있음 (useState, useEffect, useContext 등) |
| 초기 학습 곡선 | 상대적으로 높음 | 상대적으로 낮음 |
| 코드 간결성 | 상대적으로 복잡할 수 있음 | 상대적으로 간결하고 읽기 쉬움 |
| 성능 최적화 | shouldComponentUpdate 메서드를 사용하여 성능 최적화 가능 | React.memo와 useCallback, useMemo 훅을 사용하여 성능 최적화 가능 |
| 주요 사용 사례 | 과거에 많이 사용되었으며, 현재는 복잡한 컴포넌트에서 가끔 사용됨 | 현대 리액트 개발에서 주로 사용됨 |
- useEffect와 useLayoutEffect
useEffect와 useLayoutEffect는 리액트의 함수 컴포넌트에서 사이드 이펙트(side effects)를 처리하기 위해 사용하는 훅입니다. 이 두 훅은 유사한 목적을 가지고 있지만, 실행 시점과 동작 방식에서 차이가 있습니다.
useEffect
useEffect는 함수 컴포넌트에서 부수 효과를 수행하기 위해 사용됩니다. 부수 효과는 컴포넌트가 렌더링된 후에 실행되어야 하는 작업입니다. 예를 들어, 데이터 페칭, 구독 설정, DOM 업데이트 등을 처리할 수 있습니다.
주요 특징:
- 렌더링 후 실행: 컴포넌트가 렌더링된 후에 실행됩니다.
- 비동기 작업 가능: 비동기 작업을 처리하기에 적합합니다.
- 클린업 함수 지원: 컴포넌트가 언마운트되거나 업데이트되기 전에 실행될 클린업(cleanup) 함수를 반환할 수 있습니다.
import React, { useEffect, useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
// 클린업 함수
return () => {
console.log('클린업 실행');
};
}, [count]); // count가 변경될 때마다 effect가 실행됨
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default ExampleComponent;
useLayoutEffect
useLayoutEffect는 useEffect와 비슷하지만, 컴포넌트가 화면에 그려지기 전에 동기적으로 실행됩니다. 이는 DOM 업데이트가 완료된 직후에 실행됩니다. 따라서, DOM을 조작해야 하는 경우나 레이아웃을 읽어오고 동기적으로 변경해야 하는 경우에 적합합니다.
주요 특징:
- 렌더링 직후 실행: 컴포넌트가 화면에 그려지기 전에 동기적으로 실행됩니다.
- 동기 작업 수행: DOM 조작 및 레이아웃 측정 작업에 적합합니다.
- 클린업 함수 지원: useEffect와 마찬가지로 클린업 함수를 반환할 수 있습니다.
import React, { useLayoutEffect, useState, useRef } from 'react';
function LayoutEffectExample() {
const [height, setHeight] = useState(0);
const divRef = useRef();
useLayoutEffect(() => {
// div 요소의 높이를 읽고 상태 업데이트
setHeight(divRef.current.offsetHeight);
// 클린업 함수
return () => {
console.log('클린업 실행');
};
}, [height]); // height가 변경될 때마다 effect가 실행됨
return (
<div ref={divRef}>
<p>Height: {height}px</p>
<p>This is a layout effect example.</p>
</div>
);
}
export default LayoutEffectExample;
useEffect와 useLayoutEffect의 차이점
| 특징 | useEffect | useLayoutEffect |
|---|---|---|
| 실행 시점 | 렌더링 후에 비동기적으로 실행 | 렌더링 직후에 동기적으로 실행 |
| 주요 사용 사례 | 데이터 페칭, 구독 설정, 비동기 작업 | DOM 조작, 레이아웃 측정, 동기 작업 |
| 클린업 함수 | 지원 | 지원 |
| 성능 | 더 나은 성능을 위해 비동기로 실행 | 동기적으로 실행되어 렌더링 성능에 영향을 줄 수 있음 |
- useState와 useReducer
리액트에서 상태 관리를 위해 사용하는 훅은 useState와 useReducer가 있습니다. 이 두 훅은 각각 간단한 상태 관리와 복잡한 상태 관리를 위해 사용됩니다.
useState
useState는 함수 컴포넌트에서 상태를 관리하는 가장 기본적인 훅입니다. 상태 변수와 해당 상태를 업데이트하는 함수를 반환합니다. 상태가 변경되면 컴포넌트가 다시 렌더링됩니다.
사용 방법
- 상태 변수와 업데이트 함수 반환: useState는 초기 상태 값을 인자로 받아 상태 변수와 상태를 업데이트하는 함수를 배열 형태로 반환합니다.
- 상태 업데이트: 업데이트 함수는 상태를 새 값으로 업데이트하거나, 이전 상태에 기반하여 업데이트할 수 있습니다.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={increment}>증가</button>
</div>
);
}
export default Counter;
useReducer
useReducer는 복잡한 상태 로직을 관리하는 데 사용됩니다. 리듀서 패턴을 사용하여 상태 업데이트를 처리합니다. 이 훅은 상태와 디스패치 함수를 반환합니다. 상태 업데이트 로직을 컴포넌트 외부로 추상화하고, 복잡한 상태 전이를 쉽게 관리할 수 있게 합니다.
사용 방법
- 리듀서 함수 정의: 리듀서 함수는 현재 상태와 액션을 받아서 새로운 상태를 반환하는 함수입니다.
- 상태와 디스패치 함수 반환: useReducer는 리듀서 함수와 초기 상태를 인자로 받아 상태와 상태 업데이트를 위한 디스패치 함수를 반환합니다.
import React, { useReducer } from 'react';
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 (
<div>
<p>현재 카운트: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>증가</button>
<button onClick={() => dispatch({ type: 'decrement' })}>감소</button>
</div>
);
}
export default Counter;
useState와 useReducer의 차이점
| 특징 | useState | useReducer |
|---|---|---|
| 사용 용도 | 간단한 상태 관리 | 복잡한 상태 로직 관리 |
| 상태 업데이트 방식 | 상태 업데이트 함수를 직접 호출 | 디스패치 함수를 통해 액션을 디스패치하여 상태 업데이트 |
| 초기화 | 초기 상태 값을 인자로 전달 | 리듀서 함수와 초기 상태 값을 인자로 전달 |
| 리듀서 사용 여부 | 리듀서 패턴을 사용하지 않음 | 리듀서 패턴을 사용하여 상태 전이를 관리 |
| 코드의 복잡성 | 코드가 간단하고 직관적임 | 코드가 다소 복잡할 수 있으나, 상태 로직이 명확함 |
- useMemo와 useCallback
useMemo와 useCallback은 리액트에서 성능 최적화를 위해 사용하는 훅입니다. 이 두 훅은 컴포넌트가 불필요하게 다시 렌더링되는 것을 방지하여 성능을 향상시킬 수 있습니다. 이들은 컴포넌트가 반복적으로 계산을 수행하거나 함수를 생성하는 경우, 이를 캐싱하여 불필요한 작업을 줄이는 데 사용됩니다.
useMemo
useMemo는 계산된 값을 메모이제이션하는 훅입니다. 즉, 값이 바뀌지 않는 한 계산을 다시 하지 않고 이전에 계산된 값을 재사용합니다. 이를 통해 불필요한 계산을 피하고 성능을 최적화할 수 있습니다.
import React, { useMemo, useState } from 'react';
function ExpensiveComponent({ a, b }) {
const expensiveCalculation = (a, b) => {
// 복잡한 계산 수행
return a + b;
};
const result = useMemo(() => expensiveCalculation(a, b), [a, b]);
return <div>결과: {result}</div>;
}
장점:
- 불필요한 계산을 방지하여 성능을 최적화할 수 있습니다.
- 컴포넌트가 다시 렌더링될 때마다 복잡한 계산을 반복하지 않습니다.
단점:
- 적절하게 사용하지 않으면 코드가 복잡해질 수 있습니다.
- 메모이제이션이 필요한 경우에만 사용해야 하며, 모든 상황에서 사용할 필요는 없습니다.
useCallback
useCallback은 함수를 메모이제이션하는 훅입니다. 이 훅은 함수 인스턴스가 바뀌지 않도록 하여, 자식 컴포넌트에 불필요하게 새로운 함수가 전달되지 않도록 합니다. 이를 통해 자식 컴포넌트의 불필요한 재렌더링을 방지할 수 있습니다.
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return <ChildComponent increment={increment} />;
}
function ChildComponent({ increment }) {
console.log('ChildComponent 렌더링');
return <button onClick={increment}>증가</button>;
}
장점:
- 불필요한 함수 재생성을 방지하여 성능을 최적화할 수 있습니다.
- 자식 컴포넌트가 불필요하게 다시 렌더링되지 않도록 할 수 있습니다.
단점:
- 함수 인스턴스를 캐싱하기 위해 사용하며, 적절하지 않은 경우 오히려 성능에 부담을 줄 수 있습니다.
- 모든 함수에 적용할 필요는 없으며, 주로 자식 컴포넌트가 자주 렌더링되는 경우에 사용합니다.
| 특징 | useMemo | useCallback |
|---|---|---|
| 용도 | 값을 메모이제이션하여 불필요한 계산 방지 | 함수를 메모이제이션하여 불필요한 함수 재생성 방지 |
| 반환값 | 계산된 값 | 메모이제이션된 함수 |
| 주로 사용하는 경우 | 복잡하고 비용이 많이 드는 계산 | 함수가 자식 컴포넌트에 전달되어 불필요한 렌더링이 발생하는 경우 |
| 사용 방법 | useMemo(() => expensiveCalculation(a, b), [a, b]) | useCallback(() => { /* 함수 로직 */ }, [의존성]) |
useRef와 useState
useRef
- 기능: 변수를 저장하는데 사용됩니다. React의 재렌더링을 트리거하지 않으며, 주로 DOM 요소에 접근할 때 사용합니다.
- 주요 용도:
- DOM 요소에 접근 및 조작
- 렌더링 사이에 값 저장 (하지만 렌더링을 유발하지 않음)
- 초기화 방법: const ref = useRef(initialValue);
- 값 접근 및 변경: ref.current로 접근하고 값을 변경합니다.
useState
- 기능: 컴포넌트의 상태를 관리하는데 사용됩니다. 상태가 변경되면 컴포넌트가 재렌더링됩니다.
- 주요 용도:
- 컴포넌트 내 상태 관리
- 상태 변경 시 컴포넌트 재렌더링 유도
- 초기화 방법: const [state, setState] = useState(initialValue);
- 값 접근 및 변경: state로 접근하고 setState 함수로 값을 변경합니다.
구별 표
| 특징 | useRef | useState |
|---|---|---|
| 기능 | 변수 저장, DOM 접근 | 컴포넌트 상태 관리 |
| 재렌더링 | 값이 변경되어도 재렌더링 없음 | 값이 변경되면 재렌더링 발생 |
| 초기화 방법 | const ref = useRef(initialValue); | const [state, setState] = useState(initialValue); |
| 값 접근 | ref.current | state |
| 값 변경 방법 | ref.current = newValue | setState(newValue) |
| 주요 용도 | DOM 요소 접근, 렌더링 간 값 유지 | 상태 관리, 재렌더링 유도 |
| 사용 사례 | 포커스 관리, 타이머 ID 저장 | 입력 값 관리, 토글 상태 관리 |
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
contextAPI와 useContext
Context API
- 기능: React 컴포넌트 트리 전체에서 데이터를 전역적으로 공유할 수 있게 합니다.
- 주요 용도:
- 전역 상태 관리
- 테마 설정, 사용자 인증 정보 등 여러 컴포넌트에서 공통적으로 사용하는 데이터를 관리
useContext
- 기능: Context API를 사용하여 생성된 Context를 쉽게 사용할 수 있도록 도와주는 훅입니다.
- 주요 용도:
- Context 값을 읽고 구독
- Provider를 통해 전달된 전역 상태를 쉽게 접근
구별 표
| 특징 | Context API | useContext |
|---|---|---|
| 기능 | 전역적으로 상태를 관리하고 전달 | Context 값을 쉽게 사용하도록 함 |
| 사용 방법 | React.createContext로 Context 생성 | **useContext(MyContext)**로 Context 값 접근 |
| 초기화 및 설정 | const MyContext = React.createContext(defaultValue); | - |
| 값 제공 방법 | MyContext.Provider를 사용하여 값 전달 | - |
| 값 접근 방법 | - | const value = useContext(MyContext); |
| 주요 용도 | 전역 상태 관리, 다수 컴포넌트에서 데이터 공유 | Context 값을 읽고 컴포넌트에서 사용 |
| 사용 사례 | 테마, 사용자 정보, 다국어 지원 등 | Context에서 제공하는 값 사용 |
import React, { createContext, useState } from 'react';
// Context 생성
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
return (
<ThemeContext.Consumer>
{({ theme, setTheme }) => (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default App;
import React, { createContext, useState, useContext } from 'react';
// Context 생성
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}
>
Toggle Theme
</button>
);
}
export default App;
일급 함수 / 고차 컴포넌트와 사용자 정의 훅
일급 함수 (First-Class Functions)
일급 함수는 다른 변수와 동일하게 함수가 다뤄지는 프로그래밍 언어의 특성을 말합니다. 자바스크립트는 일급 함수를 지원합니다. 일급 함수는 다음과 같은 특징을 가집니다:
- 함수를 변수에 할당할 수 있습니다.
- 함수를 다른 함수에 인자로 전달할 수 있습니다.
- 함수를 다른 함수의 반환 값으로 사용할 수 있습니다.
고차 컴포넌트 (Higher-Order Components, HOC)
고차 컴포넌트는 컴포넌트를 인자로 받아서 새로운 컴포넌트를 반환하는 함수입니다. 주로 코드 재사용성을 높이기 위해 사용됩니다.
사용자 정의 훅 (Custom Hook)
사용자 정의 훅은 재사용 가능한 훅 로직을 캡슐화하는 데 사용됩니다. 훅은 함수이며, 다른 훅을 호출할 수 있습니다. 사용자 정의 훅을 사용하면 상태 관련 로직을 컴포넌트에서 분리할 수 있습니다.
import { useState, useEffect } from 'react';
// 사용자 정의 훅
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, [url]);
return { data, loading };
}
function App() {
const { data, loading } = useFetch('https://api.example.com/data');
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default App;