React Hook이란?
리액트 훅은 리액트의 함수형 컴포넌트에서 상태와 다양한 리액트 기능을 사용할 수 있게 하는 함수이다.
Hooks는 React 16.8버전부터 도입되었으며, 클래스 컴포넌트 외에도 함수형 컴포넌트에서도 강력한 도구로 사용된다.
Hooks를 사용하면 코드를 더 간결하게 작성하고 컴포넌트 로직을 재사용하기 쉽게 만들 수 있다.
useState
'useState' 훅은 함수형 컴포넌트에서 상태를 관리할 수 있게 한다. 이 훅을 사용하여 컴포넌트의 상태를 초기화하고 업데이트 할 수 있다.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useEffect
'useEffect' 훅은 컴포넌트의 사이드 이펙트를 다룰 때 사용된다. 사이드 이펙트로 API호출, 구독 설정, DOM조작, 타이머 설정 등을 처리할 수 있다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useContext
'useContext' 훅은 리액트의 컨텍스트를 사용하여 컴포넌트 트리를 통해 데이터를 전파하는데 사용된다.
import React, { useContext } from 'react';
import MyContext from './MyContext';
function MyComponent() {
const value = useContext(MyContext);
return <div>Value from Context: {value}</div>;
}
useReducer
'useReducer' 훅은 컴포넌트의 상태 관리를 좀 더 복잡한 로직으로 다룰 때 사용된다. 주로 상태와 관련된 action을 dispatch하여 상태를 업데이트한다.
import React, { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
custom Hooks
사용자 정의 커스텀 훅을 작성하여 컴포넌트 로직을 추상화하고 재사용할 수 있다.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
}
fetchData();
}, [url]);
return data;
}
리액트 훅은 함수형 컴포넌트에서 라이프사이클 메서드와 관련된 작업을 처리하고 상태를 관리하기 위한 강력한 도구이다.
함수형 프로그래밍 패러다임을 따르며 코드를 간결하게 작성할 수 있고, 코드의 가독성과 유지보수성을 향상시킨다.
리액트 훅의 사용규칙에 대해서
리액트 훅을 사용할때는 몇가지 중요한 규칙을 따라야한다.
이러한 규칙을 준수하면 리액트 훅을 올바르게 사용하고 컴포넌트의 상태 및 동작을 관리할 수 있다.
- 최상위에서만 Hook 호출하기
Hook은 함수형 컴포넌트의 최상위 레벨에서만 호출되어야 한다.
조건문, 반복문, 중첩함수 내에서 Hook을 호출하면 동작이 예측할 수 없게 될 수 있다.
이 규칙은 항상 동일한 순서로 Hook을 호출하도록 보장하며, 리액트가 컴포넌트의 상태를 올바르게 추적할 수 있도록 돕는다.
function MyComponent() {
const [count, setCount] = useState(0); // 올바른 위치
if (count === 0) {
const [data, setData] = useState([]); // 잘못된 위치
}
// ...
}
- 함수 컴포넌트 내에서만 Hook 호출하기
Hook을 함수형 컴포넌트 내에서 호출해야한다.
Hook을 일반 자바스크립트 함수나 중첩함수, 루프, 조건문 등에서 호출하지 말아야한다.
function MyComponent() {
const [count, setCount] = useState(0); // 올바른 위치
if (count === 0) {
someFunction(); // 함수 내에서 Hook 호출은 허용되지 않음
}
// ...
}
- Hook 호출 순서 지키기
Hook을 컴포넌트 내에서 호출할 때는 항상 동일한 순서로 호출해야한다.
이렇게 함으로써 리액트는 각 Hook을 해당 상태 변수에 연결하고 추적할 수 있다.
function MyComponent() {
const [count, setCount] = useState(0); // 올바른 순서
const [data, setData] = useState([]); // 올바른 순서
// ...
}
- 조건부로 Hook 호출하지 말기
Hook 호출은 조건문 안에서 수행되어서는 안된다.
대신 조건에 따라 컴포넌트를 렌더링하고 Hook을 호출해야한다.
function MyComponent({ condition }) {
if (condition) {
const [count, setCount] = useState(0); // 허용되지 않는 방식
}
// ...
}
규칙을 지키지 않을 경우 발생할 수 있는 문제점
- Hook을 최상위에서 호출하지 않았을때 발생할 수 있는 문제점
리액트 훅을 최상위에서 호출하지 않을 경우, 컴포넌트의 동작이 예상치 않은 방식으로 동작할 수 있으며 예측 불가능한 버그가 발생할 수 있다.
이를 방지하고 예상치 않은 문제를 피하기 위해 Hook을 최상위에서 호출하는것이 중요하다.
Hook을 최상위에서 호출하지 않았을때 발생할 수 있는 문제점과 이로 인한 예기치 못한 동작에 대한 설명들을 살펴보자
- 상태가 유지되지 않음 : Hook을 최상위에서 호출하지 않으면 컴포넌트의 상태가 유지되지 않을 수 있다. Hook은 컴포넌트의 렌더링 사이에 상태를 보존하고 관리하기 위한 방법이다. 만약 중첩 함수나 조건문내에서 Hook을 호출하면 리액트는 각 호출을 독립적인 상태로 처리하게 되어, 상태가 다른 컴포넌트간에 공유되지 않고 예상치 않게 초기화될 수 있다.
- 리렌더링 문제 : 최상위에서 Hook을 호출하지 않으면 컴포넌트가 다시 렌더링될 때 Hook의 호출 순서가 변경될 수 있다. 이로인해 예상치 않은 결과가 발생할 수 있으며, 컴포넌트의 동작이 예측 불가능해질 수 있다.
- 디버깅 어려움 : Hook을 최상위에서 호출하지 않으면 디버깅이 어려워질 수 있다. 코드 내에서 Hook을 어떤 조건에 따라 호출하고 있는지 파악하기 어렵기 때문에, 버그를 찾는데 더 많은 노력이 필요할 수 있다.
- 포팅과 리팩토링 어려움 : 코드의 구조를 변경하거나 컴포넌트를 포팅할 때 Hook을 최상위에서 호출하지 않았다면, 코드를 다시 작성하거나 수정하기 어려울 수 있다. 최상위에서 Hook을 호출하면 컴포넌트의 구조와 동작이 더 간결하고 예측 가능해지므로 유지보수 및 리팩토링이 용이해진다.
따라서 리액트 훅을 사용할때는 최상위 레벨에서 호출하는것이 좋다.
- 함수형 컴포넌트 내에서만 Hook을 호출하지 않고 일반 자바스크립트 함수, 중첩함수, 루프, 조건문에서 호출했을때 발생할 수 있는 문제점
리액트 훅을 함수형 컴포넌트 내에서 호출하지 않고 일반 자바스크립트 함수, 중첩 함수, 루프, 조건문 등에서 호출할 경우 다음과 같은 문제점이 발생할 수 있다.
- Hook호출 순서 문제 : 리액트는 Hook 호출 순서를 중요하게 여긴다. 일반적으로 컴포넌트의 렌더링 사이에 Hook 호출 순서가 변경되면 예기치 않은 결과가 발생할 수 있다. 일반 자바스크립트 함수, 중첩 함수, 루프, 조건문 내에서 Hook을 호출하면 호출 순서가 컨트롤하기 어려워진다.
- 상태 관리 문제 : Hook을 함수 외부에서 호출하면 해당 함수 내에서 반환된 상태 변수와 업데이트 함수가 해당 컴포넌트와 연결되지 않을 수 있다. 이로 인해 상태의 업데이트가 컴포넌트의 렌더링을 트리거하지 않고 상태가 변경되지 않을 수 있다.
- 렌더링 루프 및 무한 루프 : Hook을 루프내에서 호출하면 무한 루프가 발생할 수 있다. 예를들어, 상태 업데이트 후 다시 렌더링이 일어나는데, 이 렌더링이 다시 Hook 호출을 트리거하고 루프가 계속해서 반복될 수 있다.
- 리소스 누수 : 일반 자바스크립트 함수 내에서 비동기 작업을 수행하면 컴포넌트가 언마운트되어도 해당 작업이 계속 실행될 수 있다. 이로 인해 리소스 누수와 메모리 누수가 발생할 수 있다.
- 코드 복잡성 증가 : Hook을 함수 외부에서 호출하면 컴포넌트 내부 로직이 분산되고 코드가 복잡해질 수 있다. 컴포넌트 내부에서 Hook을 호출하면 해당 컴포넌트의 동작을 한 곳에서 관리할 수 있으며, 코드의 가독성과 유지보수성이 향상된다.
정리하자면, 리액트 훅을 함수 컴포넌트 외부에서 호출하면 옜아치 않은 동작, 무한 루프, 리소스 누수, 코드복잡성 등의 문제가 발생할 수 있다.
리액트는 Hook의 호출 순서와 컴포넌트의 상태를 올바르게 추적하기 위해 Hook을 컴포넌트 내에서 호출하도록 설계되었으며 이 규칙을 따르는 것이 중요하다.
- Hook 호출 순서를 지키지 않았을때 발생할 수 있는 문제점
리액트 훅을 사용할때 Hook 호출 순서를 지키지 않으면 다양한 문제가 발생할 수 있다. Hook 호출 순서를 지키지 않으면 리액트가 컴포넌트의 상태와 효율적인 렌더링을 관리하는 데 어려움이 있어서 예상치 않는 동작과 오류가 발생할 수 있다.
- 예상치 않은 동작 : Hook 호출 순서가 변경되면 컴포넌트의 동작이 예상치않게 변경될 수 있다. 예를들어, 상태가 다시 렌더링되기 전에 Effect Hook이 실행되거나, 상태가 업데이트된 후에 렌더링이 발생하지 않을 수 있다. 이로 인해 UI가 업데이트되지 않거나 예상치 않은 동작이 발생할 수 있다.
- 무한루프 : Hook 호출 순서가 뒤바뀔 경우 무한 루프가 발생할 수 있다. 예를들어, 상태 업데이트 후 상태를 읽어오는 Effect Hook이 계속해서 호출되어 무한 루프에 빠질 수 있다.
- 리소스 누수 : Hook 호출 순서가 지켜지지 않으면 비동기 작업이중단되지 않고 계속 실행될 수 있으므로 리소스 누수가 발생할 수 있다. 예를들어, 네트워크 요청이 완료되지 않은 상태에서 컴포넌트가 언마운트 되는 경우, 이러한 요청이 계속해서 백그라운드에서 실행될 수 있다.
- 디버깅의 어려움 : Hook 호출 순서가 뒤바뀌면 컴포넌트의 동작을 이해하고 디버깅하기 어려워진다. 코드내에서 Hook 호출 순서가 무작위로 변경되면 컴포넌트의 동작을 파악하기 어렵고, 버그를 찾는것이 어려워진다.
- 컴포넌트 구조의 복잡성 : Hook 호출 순서를 지키지 않으면 컴포넌트 내부 로직이 분산되고 복잡해질 수 있다. 각각의 Hook은 특정 상태 또는 동작과 관련되어야 하며, 이러한 분리는 컴포넌트의 코드 구조를 복잡하게 만들 수 있다.
- 조건부로 Hook을 호출했을때 생길 수 있는 문제점
리액트 훅을 조건문 내에서 호출하면 컴포넌트의 상태와 동작이 예상치 않은 방식으로 동작할 수 있으며, 예기치 못한 버그가 발생할 수 있다.
- 조건에 따라 Hook이 호출되지 않을 경우
import React, { useState } from 'react';
function ConditionalHook() {
const [count, setCount] = useState(0);
if (condition) {
// 조건이 참일 때만 Hook이 호출됨
setCount(count + 1); // 조건이 거짓인 경우 호출되지 않으므로 상태 업데이트도 이루어지지 않음
}
return (
<div>
<p>Count: {count}</p>
</div>
);
}
조건부로 Hook을 호출하면 해당 Hook은 조건이 참일 때만 호출되고, 조건이 거짓인 경우 호출되지 않는다.
이로인해 Hook이 호출되지 않으면 해당 상태가 초기화되지않고, Hook이 관련된 동작을 수행하지 않게 된다.
- 조건문에 따라 컴포넌트 동작이 달라짐
조건부로 Hook을 호출하면 컴포넌트의 동작이 조건에 따라 달라진다.
이는 컴포넌트의 동작을 예측하기 어렵게 만들며, 유지보수가 어렵고 버그 발생 가능성이 높아진다. - 리액트의 조건부 렌더링 기능 활용
import React, { useState } from 'react';
function ConditionalRendering() {
const [count, setCount] = useState(0);
return (
<div>
{condition && (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)}
</div>
);
}
대부분의 경우, 조건부로 Hook을 호출하는 대신 리액트의 조건부 렌더링 기능을 활용하여 컴포넌트를 렌더링 하는것이 적합하다.
조건부 렌더링을 통해 조건에 따라 컴포넌트를 렌더링하고, 각 상태와 동작을 컴포넌트 내에서 일관되게 관리할 수 있다.
정리하자면, 조건부로 Hook을 호출하면 컴포넌트의 동작이 예상치 않게 달라지며, 버그가 발생할 가능성이 높아진다.
따라서 Hook은 항상 조건부 렌더링을 토해 호출하지 않고, 컴포넌트 내에서 일관되게 호출하는것이 좋다.
Q. 'useReducer' 사용 vs Redux 사용
리액트 애플리케이션에서 'useReducer'훅을 사용하여 상태 관리를 수행하고 상태 업데이트 로직을 처리할 수 있다.
리덕스는 리액트 애플리케이션에서 상태 관리를 위한 라이브러리로, 'useReducer'와 유사한 목적을 가지고 있다.
따라서 'useReducer'를 사용하는 경우에 리덕스로 대체할 수 있지만, 몇가지 고려해야 할 점들이 있다.
- 복잡성 및 필요성 : 리덕스는 상태 관리를 위한 라이브러리이며, 주로 대규모 애플리케이션 또는 상태가 복잡한 애플리케이션에서 활용된다. 작은 규모의 프로젝트나 단순한 상태 관리의 경우에는 리덕스를 도입할 필요가 없을 수도 있다. 'useReducer'를 사용하는것만으로 충분한 경우가 많다.
- 코드양과 복잡성 : 리덕스를 도입하면 state, action, reducer, store등의 개념을 학습하고 구현해야한다. 이로 인해 코드양과 복잡성이 증가할 수 있으며, 초기 설정 및 유지보수에 노력이 필요하다.
- 미들웨어와 사이트이팩트 : 리덕스는 미들웨어를 사용하여 비동기 작업 및 사이드이펙트를 다루는 데 유용하다. 'useReducer'훅만으로는 비동기작업을 처리하기 어렵기때문에 리덕스의 미들웨어를 사용할 수 있다.
- 컴포넌트 간 상태 공유 : 리덕스를 사용하면 여러 컴포넌트간에 상태를 공유하고 동기화하기 용이하다. 컴포넌트 트리의 어느곳에서든 상태에 접근하고 업데이트할 수 있다.
- DevTools 및 중앙화된 상태 : 리덕스는 강력한 개발자 도구(Redux DevTools)를 제공하며, 애플리케이션의 전체상태가 중앙화된 스토어에 저장되어 디버깅 및 추적이 쉽게 가능하다.
따라서 리덕스를 사용할것인지, 'useReducer'를 사용할것인지는 프로젝트의 크기, 복잡성, 상태 관리 요구사항, 팀의 경험 등을 고려하여 결정해야한다.
리덕스는 대규모 애플리케이션에서 상태관리와 데이터 흐름을 효과적으로 다루는데 도움이 될 수 있지만, 간단한 프로젝트나 작은 규모의 컴포넌트에서는 'useReducer'와 로컬 상태로 충분할 수 있다.
Q. 비동기 작업을 처리하기 위한 리덕스의 미들웨어에는 어떤것이 있는가?
리덕스의 미들웨어를 사용하면 리덕스 액션과 상태 업데이트에 대한 커스텀 로직을 추가하고, 비동기 작업 및 사이드이펙트를 처리할 수 있다.
리덕스 미들웨어는 리덕스의 동작을 확장하고 향상서ㅣ키는 역할을 한다.
다양한 리덕스 미들웨어가 있으며, 몇가지 주요한 리덕스 미들웨어에 대한 설명을 살펴보자.
- redux-thunk : redux-thunk는 가장 일반적으로 사용되는 리덕스 미들웨어 중 하나이다. 이 미들웨어를 사용하면 액션 생성자 함수가 아닌 비동기함수를 디스패치할 수 잇다. 비동기 작업을 수행하고, 작업이 완료되면 해당 결과를 액션으로 디스패치할 수 있다.
- redux-saga : redux-saga는 리덕스 애플리케이션에서 비동기 작업을 관리하기 위한 미들웨어이다. Generator함수를 사용하여 액션을 감시하고, 비동기작업을 수행할 수 있으며, 여러 복잡한 비동기 시나리오를 다룰 수 있다.
- redux-observable : redux-observable는 RxJS를 기반으로 하는 리덕스 미들웨어로, 비동기작업을 관리하기 위한 강력한 도구이다. 옵저버블을 사용하여 액션을 처리하고, 비동기 작업을 구조화하고 조합할 수 있다.
- redux-promise : redux-promise는 프로미스를 반환하는 액션을 처리하기 위한 간단한 미들웨어이다. 액션을 프로미스로 처리하고, 프로미스가 해결될 때 액션을 디스패치한다.
- redux-offline : redux-offline은 오프라인 상황에서 앱의 동작을 관리하기 위한 미들웨어이다. 네트워크 연결이 끊겼을때 액션을 저장하고 나중에 재실행할 수 있도록 도와준다.
- redux-logger : redux-logger는 리덕스 액션 및 상태 업데이트를 디버깅하는 데 도움을 주는 미들웨어로 개발 환경에서 주로 사용된다.
- redux-undo : redux-undo는 상태의 이전 버전을 저장하고 액션을 통해 상태를 되돌릴 수 있는 미들웨어이다. 상태의 히스토리를 관리하며 "실행취소", "다시 실행"기능를 제공한다.
'개발공부 > 기술면접준비' 카테고리의 다른 글
[TypeScript] TypeScript를 사용할 때 어떤 장단점이 존재하는가 (0) | 2023.10.12 |
---|---|
[TypeScript] TypeScript는 정확히 무엇이며 JavaScript와 어떻게 다른가 (0) | 2023.10.12 |
[React]Class Component와 Function Component의 차이점 (0) | 2023.10.11 |
[React] useRef가 필요한 상황과 예시 (0) | 2023.09.21 |
[Redux] Redux의 주요 개념들과 연결 관계에 대해서 (0) | 2023.09.21 |