[React] 자식 컴포넌트에서 부모 컴포넌트 상태 변경하기
React에서 컴포넌트 간의 효율적인 상태 관리는 중요한 요소 중 하나다. 특히 자식 컴포넌트에서 부모 컴포넌트의 상태를 업데이트하는 상황이 종종 있다. 자식 컴포넌트에서 부모 컴포넌트의 상태를 업데이트 여러 가지 방법이 있는데, 이러한 다양한 방법은 각각의 상황에 맞게 선택되어야 한다. 각 방법의 장단점과 코드 예시를 함께 알아보면서 효율적인 상태 관리를 구현하는 방법을 이해하도록 해보자.
1. 콜백 함수를 활용한 상태 업데이트
React의 기본 원칙 중 하나는 단방향 데이터 흐름이다. 부모 컴포넌트는 자식 컴포넌트에게 콜백 함수를 전달하고, 자식 컴포넌트에서 해당 콜백을 호출하여 부모 컴포넌트의 상태를 간접적으로 업데이트할 수 있다. 이는 단방향 데이터 프름을 유지하면서 부모 컴포넌트와 자식 컴포넌트 간의 상태 업데이트를 가능하게 한다. 주로 간단한 구조에서 사용된다.
장점
- 간단하고 직관적인 패턴
- React의 단방향 데이터 흐름을 유지하면서 부모 컴포넌트의 상태를 업데이트할 수 있다.
단점
- 자식 컴포넌트가 부모 컴포넌트에 의존하므로 결합도가 높아진다.
- 복잡한 상태 로직이나 여러 컴포넌트 간의 상태 공유에는 제한적이다.
예시 코드
// 부모 컴포넌트
import { useState } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const updateCount = (newCount) => {
setCount(newCount);
};
return (
<div>
<p>Count: {count}</p>
<ChildComponent onUpdateCount={updateCount} />
</div>
);
};
// 자식 컴포넌트
import React, { useState } from 'react';
const ChildComponent = ({ onUpdateCount }) => {
const [newCount, setNewCount] = useState(0);
const handleClick = () => {
onUpdateCount(newCount);
};
return (
<div>
<input
type="number"
value={newCount}
onChange={(e) => setNewCount(Number(e.target.value))}
/>
<button onClick={handleClick}>
Update Parent Count
</button>
</div>
);
};
export default ChildComponent;
2. Context API를 활용한 상태 관리
useContext를 사용하여 부모 컴포넌트의 상태를 전역적으로 공유하는 방법이다. Context를 통해 전역 상태를 제공하고, 자식 컴포넌트에서 해당 상태를 업데이트할 수 있다.
부모 컴포넌트에서 createContext를 사용하여 상태를 생성하고, Provider로 해당 상태를 자식 컴포넌트에 전달한다. 자식 컴포넌트는 useContext를 사용하여 해당 상태를 쉽게 사용하고 업데이트할 수 있다.
장점
- 여러 계층의 컴포넌트 간에 편리하게 상태 전달
- 전역적인 상태 관리가 필요한 경우 효과적
단점
- 모든 자식 컴포넌트가 부모 컴포넌트의 상태를 필요로 할 때에만 유용
예시 코드
// 상태 관리용 Context 생성
import { createContext, useContext, useState } from 'react';
const CountContext = createContext();
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const updateCount = (newCount) => {
setCount(newCount);
};
return (
<CountContext.Provider value={{ count, updateCount }}>
{children}
</CountContext.Provider>
);
};
// 부모 컴포넌트
import { CountProvider } from './CountContext';
const App = () => {
return (
<CountProvider>
{/* App 내의 자식 컴포넌트들 */}
</CountProvider>
);
};
// 자식 컴포넌트
import React, { useContext, useState } from 'react';
import { CountContext } from './CountContext';
const ChildComponent = () => {
const { count, updateCount } = useContext(CountContext);
const handleClick = () => {
updateCount(count + 1);
};
return (
<div>
<p>Count from Context: {count}</p>
<button onClick={handleClick}>
Update Count from Context
</button>
</div>
);
};
export default ChildComponent;
3. useReducer를 활용한 상태 업데이트
useReducer 훅을 사용하여 복잡한 상태 로직을 다루는 방법이다. useReducer를 사용하면 복잡한 상태 로직을 다루기에 용이하며, 여러 액션을 통해 상태를 업데이트할 수 있다. 부모 컴포넌트에서 useReducer를 사용하여 상태와 액션을 관리한다. 자식 컴포넌트에 diaspatch 함수를 전달하면 자식 컴포넌트에서 dispatch를 통해 부모 컴포넌트의 상태를 간접적으로 업데이트할 수 있다.
장점
- 복잡한 상태 로직을 다루기에 용이
- 여러 액션을 통한 상태 업데이트가 가능
단점
- 작은 규모의 애플리케이션에서는 useReducer 사용이 오버헤드일 수 있다.
예시 코드
// 부모 컴포넌트
import { useReducer } from 'react';
import ChildComponent from './ChildComponent';
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
};
const ParentComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<ChildComponent dispatch={dispatch} />
</div>
);
};
// 자식 컴포넌트
import React from 'react';
const ChildComponent = ({ dispatch }) => {
return (
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
Increment
</button>
);
};
export default ChildComponent;
4. Redux를 이용한 상태 관리
Redux는 전역 상태 관리 라이브러리로, 상태를 중앙에서 효율적으로 관리한다. 부모 컴포넌트에서 Redux 스토어를 생성하고, 자식 컴포넌트에서 Redux 액션을 디스패치하여 부모 컴포넌트의 상태를 업데이트할 수 있다.
장점
- 부모 컴포넌트의 상태를 직접 업데이트하는 대신, Redux를 통해 자식 컴포넌트가 간접적으로 부모 컴포넌트와 통신할 수 있다.
단점
- 작은 규모의 애플리케이션에서는 Redux 사용이 오버헤드일 수 있다.
예시 코드
1) Redux 설정
// actions.js
export const increment = () => ({
type: 'INCREMENT',
});
// reducer.js
const initialState = {
count: 0,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
};
export default reducer;
// store.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
2) 부모 컴포넌트에서 Redux store 사용
부모 컴포넌트에서 Redux store를 생성하고 상태를 업데이트할 액션을 디스패치한다.
// ParentComponent.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { increment } from './actions';
const ParentComponent = () => {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
const handleIncrement = () => {
dispatch(increment());
};
return (
<div>
<p>Count: {count}</p>
<ChildComponent onIncrement={handleIncrement} />
</div>
);
};
3) 자식 컴포넌트에서 action dispatch
자식 컴포넌트에서 부모 컴포넌트의 상태를 업데이트하기 위해 action을 dispatch 한다.
// ChildComponent.js
import React from 'react';
const ChildComponent = ({ onIncrement }) => {
return (
<div>
<button onClick={onIncrement}>Increment in Parent</button>
</div>
);
};
export default ChildComponent;