📌 컴포넌트의 성능 향상
함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 제공하는 기술입니다. 이전에는 클래스 컴포넌트에서만 상태 관리와 생명주기 메서드를 사용할 수 있었지만, Hooks를 사용하면 함수형 컴포넌트에서도 이러한 기능을 사용할 수 있게 됩니다.
useState
: 상태(state)를 관리하기 위한 훅. 상태를 선언하고 새로운 값으로 업데이트useEffect
: 생명주기(lifecycle) 기능을 제공하는 훅. 컴포넌트가 렌더링될 때마다 특정 작업을 수행하고, 상태가 업데이트될 때 특정 작업을 처리useContext
: 전역으로 상태를 공유하고 접근할 수 있는 훅.useReducer
: 복잡한 상태 관리를 위해 사용되는 훅, useState보다 다양한 상태 업데이트 로직 구현 가능.useRef
: DOM 요소나 컴포넌트에 접근하기 위한 Ref를 생성useMemo
: 연산 결과를 기억하는 훅. 이전에 계산된 값을 재사용하여 불필요한 연산 방지.useCallback
: 콜백 함수를 기억하는 훅. 불필요한 함수 재생성을 방지State 최적화란 ?
- 프로그램의 성능을 향상시키기 위해 상태(state)를 효율적으로 관리하는 과정을 말합니다.
- 프로그램의 상태는 변수, 데이터 구조, 객체 등을 포함하며, 이러한 상태를 올바르게 조작하고 저장하는 것은 중요합니다.
React.memo
: 함수 컴포넌트의 이전 렌더링 결과를 기억, 입력이 변경되었을 때만 재렌더링. 자식 컴포넌트의 불필요한 렌더링을 방지
import React, { memo } from 'react';
function MemoComponent({ data }) {
// data가 변경되지 않으면 이전에 렌더링된 결과를 재사용
return <div>{data}</div>;
}
export default memo(MemoComponent);
단점: 부모가 원시타입이 아닌 유형을 props의 배열로 전달하면 중단됨.
이 문제를 해결하기 위해 변수값을 메모화하는 useMemo
를 사용해보자!(아래)
useMemo
: 이전에 계산된 값을 캐시 → 동일한 입력에 대한 계산을 다시 수행하지 않음. 비용이 높은 함수나 연산 결과를 캐싱 → 불필요한 계산 방지
import React, { useMemo } from 'react';
function UseMemoComponent({ data }) {
const processedData = useMemo(() => {
// 복잡한 데이터 처리 로직
return processData(data);
}, [data]);
// 렌더링에 processedData 사용
return <div>{processedData}</div>;
}
useCallback
: 동일한 콜백 함수를 재생성하지 않고 재사용. 자식 컴포넌트에 props로 전달되는 콜백 함수를 최적화하는 데 유용함!
import React, { useCallback } from 'react';
function UseCallbackComponent({ onClick }) {
const handleClick = useCallback(() => {
// 클릭 핸들러 로직
onClick();
}, [onClick]);
return <button onClick={handleClick}>Click Me</button>;
}
useReducer
: 복잡한 상태 로직을 관리하는 데 유용. state와 state를 업데이트하는 reducer 함수를 정의해서 효율적으로 관리.
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 });
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
const decrement = () => {
dispatch({ type: 'DECREMENT' });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
hooks에 대한 기본적인 최적화 방법 외에 state를 최적화 할 수 있는 법을 알아보자.
💡 자식 컴포넌트가 부모 컴포넌트의 상태에 의존하지 않고 독립적으로 동작할 수 있도록 설계하는 것을 의미
count
상태는 구성 요소 내에서만 사용되고, 상위 컴포넌트에 해당 내용을 공유할 필요가 없음. count
개수가 업데이트될 때 부모 구성 요소에서 불필요한 재렌더링을 방지함.💡 최소한의 상태와 렌더링을 유지하여 성능을 향상시키는 것을 의미 → 불필요한 상태 제거 & 변경 없는경우 랜더링 방지
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '장보기', completed: false },
{ id: 2, text: '강아지 산책시키기', completed: true },
{ id: 3, text: '빨리하기', completed: false },
]);
const toggleTodoCompletion = (id) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
[todo.id](http://todo.id/) === id ? { ...todo, completed: !todo.completed } : todo));
};
return (
<ul>
{todos.map((todo) => (
<li
key={[todo.id](http://todo.id/)}
style={{textDecoration: todo.completed ? 'line-through' : 'none',}}
onClick={() => toggleTodoCompletion([todo.id](http://todo.id/))}
>
{todo.text}
</li>
))}
</ul>
);
}
export default TodoList;
import React, { useState } from "react";
const user = {
name: "테스트 이름",
email: "test@test.com"
};
function UserProfile() {
const [isEditing, setEditing] = useState(false);
const toggleEditing = () => {
setEditing(!isEditing);
};
return (
<div>
<h2>Minimal render 예시</h2>
{isEditing ? <input value={user.name} /> : <p>{user.name}</p>}
<button onClick={toggleEditing}>{isEditing ? "Save" : "Edit"}</button>
</div>
);
}
export default UserProfile;
isEditing
상태에 따라 다른 요소를 렌더링toggleEditing
함수는 클릭할 때마다 isEditing
상태를 토글하여 편집 모드와 일반 모드를 번갈아가면서 전환isEditing
상태에 따라 불필요한 렌더링을 방지, 필요한 상황에만 렌더링되게함💡 하위 컴포넌트 간 공통 상태를 상위 컴포넌트로 올려서 관리하는 것을 의미 → 상태업데이트를 중앙에서 처리 & 상태 일관성 유지
import React, { useState } from 'react';
// 사용자 이름 관리
function ParentComponent() {
const [name, setName] = useState('홍길동');
const handleNameChange = (newName) => {
setName(newName);
};
return (
<div>
<h2>Parent Component 입니당</h2>
<ChildComponent name={name} onNameChange={handleNameChange} />
</div>
);
}
// 이름표시+변
function ChildComponent({ name, onNameChange }) {
const handleChange = (event) => {
const newName = event.target.value;
onNameChange(newName);
};
return (
<div>
<h2>Child Component 입니당</h2>
<p>Name: {name}</p>
<input type="text" value={name} onChange={handleChange} />
</div>
);
}
export default ParentComponent;
https://velog.io/@shin6403/React-렌더링-성능-최적화하는-7가지-방법-Hooks-기준
https://kellis.tistory.com/150
https://dev.to/redraushan/memoizing-react-components-2km7
https://lucianohgo.com/posts/react-memoization-cheatsheet
https://medium.com/@taraparakj75/react-state-management-everything-you-need-to-know-3175f04da6a
https://medium.com/wantedjobs/react-profiler를-사용하여-성능-측정하기-5981dfb3d934