리액트는 UI 라이브러리로써, 컴포넌트를 기반으로 한 개발 방식을 채용한다. 이런 모든 리액트 컴포넌트에는 라이프사이클(수명 주기)이 존재한다. 컴포넌트의 수명은 페이지에 렌더링 되기 전인 준비 과정에서 시작해 페이지에서 사라질 때 끝난다.
리액트 라이프사이클은 컴포넌트가 생성되고, 업데이트되며, 소멸될 때의 상태를 설명하는 메서드이다. 이 메서드들은 컴포넌트가 라이프사이클을 따라서 동작할 수 있도록 도와주는 역할을 수행한다.
라이프사이클 메서드 이해
라이프사이클 메서드의 종류는 크게 3가지 종류로 나눌 수 있다.
- 마운트
마운트는 컴포넌트가 페이지에 새로 추가될 때 발생하는 단계이다. 이 단계에서는 다음과 같은 메서드가 호출된다.
- constructor : 컴포넌트가 생성될 때 호출
- getDerivedStateFromProps : props에 있는 값을 state에 넣을 때 사용하는 메서드이다. 컴포넌트가 생성될 때와 새로운 props를 받았을 때 호출된다.
- render : 컴포넌트가 렌더링될 때 호출된다. 즉, 우리가 준비한 UI를 렌더링해주는 메서드이다.
- componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드이다.
- 업데이트
컴포넌트는 다음과 같은 총 네 가지 경우에 업데이트된다.
- props의 변경
- state의 변경
- 부모 컴포넌트 리렌더링
- this.forceUpdate로 인한 강제 렌더링
이렇게 컴포넌트를 업데이트 할 경우 다음 메서드들을 호출한다.
- getDerivedStateFromProps : 컴포넌트가 생성될 때와 새로운 props를 받았을 때 호출된다. props의 변화에 따라 state도 변화를 주고 싶을 때 사용한다.
- shouldComponentUpdate : 컴포넌트가 업데이트되어야 하는지 여부를 결정한다. 이 메서드는 true 혹은 false 값을 반환행야하며, true를 반환하면 라이프사이클 메서드를 계속 실행하고, false를 반환하면 작업을 중지한다. 즉, false일 경우 컴포넌트가 리렌더링 되지 않는다.
- render : 컴포넌트가 리렌더링될 때 호출된다.
- getSnapshotBeforeUpdate : render() 메서드 호출 직후, 실제로 DOM에 반영되기 전에 호출되는 메서드이다.
- componentDidUpdate : 컴포넌트가 업데이트된 후 호출된다.
- 언마운트
업데이트는 컴포넌트의 state나 props가 변경될 때 발생하는 단계이다. 이 단계에서는 다음과 같은 메서드가 호출된다.
- componentWillUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출되는 메서드이다.
메서드 살펴보기
render
render 메서드는 리액트 컴포넌트에서 가장 중요한 라이프사이클 메서드 중 하나이다. 이 메서드는 컴포넌트의 UI를 렌더링하는 역할을 한다. render 메서드는 항상 호출되며, 컴포넌트를 업데이트할 때마다 새로운 UI를 생성한다. 또한, 컴포넌트의 JSX 코드를 반환해서 이를 통해 리액트가 브라우저에 실제로 렌더링할 HTML 코드를 생성한다.
render 메서드는 순수 함수로 작성되어야 하며, 항상 같은 결과를 반환해야 한다. 또한, 컴포넌트의 state나 props를 변경해서는 안된다. 이러한 규칙을 지키지 않을 경우, 컴포넌트가 렌더링 될 때 예상치 못한 결과가 발생할 수 있다.
리액트는 render() 메서드를 호출해 UI를 렌더링하는 동안, 브라우저의 실제 DOM은 업데이트하지 않는다. 대신 리액트는 렌더링된 결과를 Virtual DOM에 저장한다. Virtual DOM은 리액트에서 렌더링 성능을 향상시키는데 핵심적인 역할을 수행하며, 이전에 렌더링 된 결과와 현재 렌더링 된 결과를 비교하고, 변경된 부분만 실제 DOM에 업데이트한다. 이를 통해 매번 실제 DOM을 업데이트하지 않고도 빠른 렌더링을 구현할 수 있다.
interface Props {
// props 타입 정의
}
interface State {
// state 타입 정의
}
class MyComponent extends React.Component<Props, State> {
// constructor 메서드 등 다른 메서드 정의
render(): JSX.Element {
return (
// JSX 문법으로 작성된 UI 반환
);
}
}
이 예제는 JSX.Element 타입을 반환한다. 이는 리액트가 UI를 구성하는 요소를 표현하는 타입으로, JSX 문법을 사용해 작성하기 때문이다. 반환값은 오직 단일 요소여야 하며, 여러 요소가 포함될 시, 이를 묶어주는 컨테이너요소를 사용해야 한다.
render 메서드는 컴포넌트의 상태나 props에 따라 UI를 반환할 수 있다. 이를 통해 동적인 UI를 구성할 수 있다.
interface Props {
// props 타입 정의
}
interface State {
isVisible: boolean;
}
class MyComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isVisible: true,
};
}
toggleVisibility = () => {
this.setState((prevState) => ({
isVisible: !prevState.isVisible,
}));
};
render(): JSX.Element {
const { isVisible } = this.state;
return (
<div>
<button onClick={this.toggleVisibility}>
{isVisible ? "Hide" : "Show"}
</button>
{isVisible && <p>This text is visible</p>}
</div>
);
}
}
이 예제는 state를 이용해 UI를 동적으로 구성하는 예제이다. 버튼을 누를 때 마다 텍스트를 숨기거나 보이게 한다. render 메서드에서는 isVisible state에 따라 텍스트를 보여주거나 숨긴다.
constructor
constructor 메서드는 클래스 컴포넌트의 생성자 함수를 정의하는 메서드이다. 컴포넌트가 생성될 때 호출되며, 컴포넌트의 초기 상태를 설정하거나 인스턴스 변소를 초기화하는 등의 작업을 수행할 수 있다.
타입스크립트에서는 constructor 메서드의 반환값의 타입도 지정해줄 수 있다.
interface Props {
// props 타입 정의
}
interface State {
// state 타입 정의
}
class MyComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
// 인스턴스 변수 초기화
this.state = {
// 초기 상태 값 설정
};
// 이벤트 핸들러 바인딩 등 다른 초기화 작업 수행
}
// 컴포넌트 렌더링 메서드 등 다른 메서드 정의
}
위와 같은 형식으로 작성할 수 있으며, 예제에서 Props와 State는 인터페이스로 정의되어 있다. 이 인터페이스들은 컴포넌트가 받는 props와 state의 타입을 각각 정의하고 있으며, constructor 메서드의 매개변수 props는 Props 타입으로 정의되어 있으며, 반환값은 void이다.
getDerivedStateFromProps
이 메서드는 컴포넌트의 props가 변경될 때마다 호출되며, props로부터 state를 도출(derive)하여 반환한다.
이 메서드는 다음과 같은 형태로 작성할 수 있다.
import React, { useState, useMemo } from "react";
interface Props {
name: string;
}
interface State {
name: string;
greeting: string;
}
const Greeting = React.memo<Props>(function Greeting(props) {
const [state, setState] = useState<State>({
name: props.name, // 초기 name 값으로 props.name을 사용
greeting: `Hello, ${props.name}!`,
});
useMemo(() => {
if (props.name !== state.name) {
setState({
name: props.name,
greeting: `Hello, ${props.name}!`,
});
}
}, [props.name]);
return <h1>{state.greeting}</h1>;
});
export default Greeting;
import React from "react";
import Greeting from "./Greeting";
const App = () => {
return (
<div>
<Greeting name="USER" />
</div>
);
};
export default App;
함수형 컴포넌트를 사용한다면, React.memo나 React.forwardRef와 같은 HOC(Higher-Order Component)를 사용해 컴포넌트를 감싸줘야 한다.
위 예제에서 Greeting 컴포넌트는 props로부터 이름을 받아 환영 메세지를 구성한다. useState 훅을 사용해 state 상태를 정의하고, useMemo 훅을 사용해 getDerivedStateFromProps 메서드를 구현했다. useMemo 훅은 props의 변경을 감지해 상태 값을 도출하고, 변경이 필요 없는 경우 이전 상태 값을 반환한다. 이를 통해 Greeting 컴포넌트에서는 상태에 따라 UI를 구성할 수 있다.
또한, 함수형 컴포넌트에서는 클래스형 컴포넌트와 달리 static 키워드는 사용할 수없다. 그리고, Props와 State 타입을 컴포넌트 외부에서 정의하고, 이를 함수형 컴포넌트의 제네릭 타입으로 지정해야한다.
위 예제 실행 시, 원하는 동작이 표시된다.
componentDidMount
이 메서드는 클래스형 컴포넌트에서 사용되는 메서드이다. 컴포넌트가 마운트된 직후에 호출되며, 주로 DOM 요소를 가져오거나 외부 데이터를 가져오는 등의 초기화 작업을 수행하는 데 사용된다.
함수형 컴포넌트에서는 useEffect 훅을 사용해 componentDidMount와 동일한 작업을 수행할 수 있다. useEffect 훅의 두 번째 인자로 빈 배열이 전달되면, 컴포넌트가 마운트 될 때만 useEffect 콜백 함수가 호출된다.
예시는 함수형 컴포넌트와 useEffect를 사용해 componentDidMount와 동일한 작업을 수행하도록 했다.
import React, { useState, useEffect } from "react";
interface Props {
url: string;
}
function MyComponent(props: Props) {
const [data, setData] = useState<any>(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(props.url);
const data = await response.json();
setData(data);
}
fetchData();
}, []);
if (!data) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{data.title}</h1>
<p>{data.body}</p>
</div>
);
}
export default MyComponent;
useEffect 훅을 사용해 props.url로부터 데이터를 가져오는 작업을 수행하고, data state를 업데이트 한다. useEffect의 두 번째 인자로 빈 배열을 전달해 컴포넌트가 마운트될 때만 useEffect의 콜백 함수가 호출되도록 한다. data가 존재하지 않을 경우 "Loding..."을, 존재하면 데이터를 화면에 렌더링한다.
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<div>
<MyComponent url="https://jsonplaceholder.typicode.com/posts/1" />
</div>
);
};
export default App;
shouldComponentUpdate
이 메서드는 컴포넌트의 업데이트 성능을 최적화 할 때 사용된다. 리액트에서 컴포넌트가 업데이트 될 때는 props나 state가 변경될 때 발생한다. shouldComponentUpdate 메서드는 props나 state가 변경될 때마다 호출되며, 이전 props와 state를 비교해 true나 false를 반환하도록 구현할 수 있다.
또한, 컴포넌트의 불필요한 렌더링을 방지해 애플리케이션의 성능을 향상시킬 수 있다.
컴포넌트를 만들 때 이 메서드를 따로 생성하지 않으면 언제나 true 값을 반환하는데, 이 메서드가 false 값을 반환한다면 업데이트 과정은 여기서 중지된다.
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 메서드는 컴포넌트가 업데이트 되기 전에 호출된다. 이전 상태와 props를 참조하여 컴포넌트가 업데이트 되기 전의 snapshot(캡쳐)을 반환한다. 이 snapshot은 componentDidUpdate 메서드에서 사용할 수 있다.
함수형 컴포넌트에서 getSnapshotBeforeUpdate 메서드는 useRef 훅을 사용하여 구현할 수 있다. useRef 훅을 사용하면 함수형 컴포넌트에서도 인스턴스 변수를 사용할 수 있다.
import React, { useState, useEffect, useRef } from "react";
interface Props {
message: string;
}
function MyComponent(props: Props) {
const [count, setCount] = useState(0);
const prevCountRef = useRef<number>(count);
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const getSnapshotBeforeUpdate = (
prevProps: Props,
prevState: { count: number }
) => {
if (prevCountRef.current < count) {
return "Count has increased!";
}
return null;
};
const componentDidUpdate = (
prevProps: Props,
prevState: { count: number },
snapshot: any
) => {
if (snapshot) {
console.log(snapshot);
}
};
return (
<div>
<h1>Count: {count}</h1>
<h2>Message: {props.message}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default MyComponent;
위 코드에서는 useRef 훅을 사용하여 prevCountRef 변수를 만들고, useEffect 훅을 사용하여 이 변수를 컴포넌트가 업데이트 될 때마다 현재 count 값으로 업데이트한다.
그리고 getSnapshotBeforeUpdate 메서드에서는 prevCountRef.current와 현재 count 값을 비교하여, count 값이 증가했는지 여부에 따라 적절한 snapshot을 반환한다.
마지막으로, componentDidUpdate 메서드에서는 getSnapshotBeforeUpdate 메서드에서 반환한 snapshot을 사용하여 적절한 작업을 수행한다.
componentDidUpdate
이 메서드는 컴포넌트가 업데이트 된 후 호출된다. 이 메서드는 컴포넌트가 업데이트 되었을 때 실행할 추가적인 로직을 작성할 수 있도록 한다.
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot?: SS): void {
// ...
}
이 메서드는 위와 같은 형식을 가지며, 여기서 prevProps와 prevState는 이전 프로퍼티 상태이고, snapshot은 getSnapshotBeforeUpdate 메서드가 반환하는 값이다.
// MyComponent
import React, { useState, useEffect, useRef } from "react";
interface Props {
message: string;
}
function MyComponent(props: Props) {
const [count, setCount] = useState(0);
const prevCountRef = useRef<number>(count);
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const getSnapshotBeforeUpdate = (
prevProps: Props,
prevState: { count: number }
) => {
if (prevCountRef.current < count) {
return "Count has increased!";
}
return null;
};
const componentDidUpdate = (
prevProps: Props,
prevState: { count: number },
snapshot: any
) => {
if (snapshot) {
console.log(snapshot);
}
};
return (
<div>
<h1>Count: {count}</h1>
<h2>Message: {props.message}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default MyComponent;
// App
import React from "react";
import MyComponent from "./MyComponent";
function App() {
const [message, setMessage] = React.useState("Hello, World!");
return (
<div>
<MyComponent message={message} />
<button onClick={() => setMessage("Hello, React!")}>
Change Message
</button>
</div>
);
}
export default App;
MyComponent 컴포넌트는 count와 prevCountRef라는 상태 변수를 선언하고, useEffect를 사용하여 count가 변경될 때마다 prevCountRef.current를 업데이트한다. 그리고 getSnapshotBeforeUpdate 메서드를 구현하여, count 상태 값이 이전 값보다 증가한 경우 "Count has increased!" 메시지를 반환하고, 그렇지 않으면 null을 반환한다. 이후 componentDidUpdate 메서드에서 이전 값(prevState)과 새 값(snapshot)이 함께 로그에 출력되도록 구현하였다.
App 컴포넌트는 message라는 상태 변수를 선언하고, MyComponent 컴포넌트를 렌더링해준다. 또한 버튼을 클릭하면 message 상태를 업데이트한다.
이 코드를 실행하면, MyComponent가 렌더링될 때마다 getSnapshotBeforeUpdate 메서드가 호출되고, count 상태 값이 증가하면 componentDidUpdate 메서드가 호출된다. 이전 값(prevState)과 새 값(snapshot)이 함께 로그에 출력되는 것을 확인할 수 있다. 또한 App 컴포넌트에서 message 상태 값을 변경하면, MyComponent도 함께 리렌더링되는 것을 확인할 수 있다.
componentWillUnmount
componentWillUnmount는 클래스 컴포넌트에서 사용되는 라이프사이클 메서드 중 하나이다. 이 메서드는 컴포넌트가 언마운트 될 때 호출되며, 주로 컴포넌트에서 사용하는 리소스를 해제하거나, 타이머를 제거하거나, 이벤트 핸들러를 등록 해제하는 등의 작업을 수행한다.
함수형 컴포넌트에서는 useEffect 훅을 사용하여 componentWillUnmount와 같은 동작을 구현할 수 있다. useEffect는 컴포넌트가 마운트될 때, 언마운트될 때, 업데이트될 때 등 다양한 시점에서 실행되는 훅으로, 언마운트될 때 사용할 콜백 함수를 반환하는 방식으로 componentWillUnmount와 같은 동작을 구현할 수 있다.
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(interval);
console.log("Interval has been cleared");
};
}, []);
return (
<div>
<h1>Count: {count}</h1>
</div>
);
}
export default App;
함수형 컴포넌트에서 componentWillUnmount와 같은 동작을 구현해보았다. 위 코드에서 useEffect 훅에서 반환한 함수는 컴포넌트가 언마운트될 때 실행된다. 이 함수에서는 setInterval로 생성한 타이머를 clearInterval로 해제하고, "Interval has been cleared" 메시지를 출력한다. 이렇게 useEffect와 반환 함수를 사용하면 componentWillUnmount와 같은 동작을 구현할 수 있다.
componentDidCatch
이 메서드는 클래스 컴포넌트에서 사용되는 메소드 중 하나로, 컴포넌트 내에서 발생한 자바스크립트 예외 처리를 담당한다. 예외가 발생하면 이 메소드가 실행되고, 에러 정보를 받아 처리할 수 있다.
함수 컴포넌트에서는 componentDidCatch 대신에 ErrorBoundary라는 특수한 컴포넌트를 사용해 에러를 처리할 수 있다. ErrorBoundary 컴포넌트는 try-catch 문과 사용해 자식 컴포넌트에서 발생한 예외를 캐치하고, componentDidCatch 메소드에서 수행했던 것과 동일한 작업을 처리한다.
import React, { Component } from "react";
interface Props {
children: React.ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
errorInfo?: React.ErrorInfo;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.setState({ errorInfo });
}
render() {
const { hasError, error, errorInfo } = this.state;
if (hasError) {
return (
<div>
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: "pre-wrap" }}>
{error && error.toString()}
<br />
{errorInfo && errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
function ChildComponent() {
const [count, setCount] = React.useState(0);
if (count > 2) {
throw new Error("Count is too high.");
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function App() {
return (
<div>
<ErrorBoundary>
<ChildComponent />
</ErrorBoundary>
</div>
);
}
export default App;
이 코드는 ErrorBoundary 컴포넌트를 사용한 예제이다. ErrorBoundary 컴포넌트는 자식 컴포넌트로 ChildComponent를 받아서 렌더링하며, 자식 컴포넌트에서 예외가 발생하면 componentDidCatch 메소드에서 처리해준다.
ChildComponent는 count가 3 이상이 되면 예외를 발생시키도록 구성되어 있다. ErrorBoundary 컴포넌트는 이 예외를 캐치하고, hasError 상태를 true로 변경해서 render 메소드에서 에러 처리 화면을 렌더링한다.
'WEB > React' 카테고리의 다른 글
리액트 Hooks 이해하기 [react/hooks/typescript] (0) | 2023.03.15 |
---|---|
리액트 라이프사이클 메서드 사용하기 [react/life cycle/typescript] (0) | 2023.03.13 |
리액트 컴포넌트 반복 사용하기 [react/component/typescript] (0) | 2023.03.12 |
리액트 DOM 네이밍 이해하기 (0) | 2023.03.12 |
리액트 이벤트 시스템 이해하기 [react/event/typescript] (0) | 2023.03.11 |