📦 [프로젝트 폴더]
┣ 📂 app
┃ ┗ 📜 index.tsx
┣ 📂 components
┃ ┣ 📜 CounterDisplay.tsx
┃ ┗ 📜 CouterButton.tsx
┣ 📂 context
┗ ┗ 📜 CounterContext.tsx
Tip: 나중에 상태 관리 라이브러리(Redux 등)를 도입하기 전, 간단한 전역 상태 관리는 Context API로 충분히 처리할 수 있습니다.
React.createContext()
를 사용하여 Context를 생성합니다.useContext()
Hook(또는 Consumer 컴포넌트)을 사용하여 Context에 담긴 데이터를 사용합니다.count
와 setCount
를 포함하는 CounterContextType
을 정의합니다.useCounter
Hook을 만들어 Context를 사용할 수 있도록 합니다. 이 Hook은 Context를 사용할 때마다 Provider로 감싸져 있는지 확인하고, Context의 값을 반환합니다.CounterProvider
컴포넌트를 만들어 useState
를 사용해 count
와 setCount
를 관리하고, 이를 Context의 value로 전달합니다.import React, { createContext, useState, useContext } from 'react';
// 1. Context 생성하기
interface CounterContextType {
count: number;
setCount: React.Dispatch<React.SetStateAction<number>>;
}
const CounterContext = createContext<CounterContextType | null>(null);
export const useCounter = () => {
const context = useContext(CounterContext);
if (!context) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
return context;
};
export const CounterProvider: React.FC = ({ children }) => {
const [count, setCount] = useState(0);
const value = { count, setCount };
return (
<CounterContext.Provider value={value}>
{children}
</CounterContext.Provider>
);
};
export default CounterContext;
useCounter
Hook을 사용하여 Context의 값을 가져옵니다. 서로 다른 컴포넌트에서 같은 Context를 사용할 수 있습니다.import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { useCounter } from '../context/CounterContext';
const CounterDisplay = () => {
const { count } = useCounter();
return (
<Text style={styles.countText}>현재 카운트: {count}</Text>
);
};
const styles = StyleSheet.create({
countText: {
fontSize: 24,
textAlign: 'center',
marginBottom: 20,
},
});
export default CounterDisplay;
import React from 'react';
import { Button } from 'react-native';
import { useCounter } from '../context/CounterContext';
const CounterButton = () => {
const { setCount } = useCounter();
return (
<Button title="카운트 증가" onPress={() => setCount(prev => prev + 1)} />
);
};
export default CounterButton;
CounterProvider
로 감싸고, CounterDisplay
와 CounterButton
을 사용합니다.import React from 'react';
import { View, StyleSheet } from 'react-native';
import CounterDisplay from './components/CounterDisplay';
import CounterButton from './components/CounterButton';
import { CounterProvider } from './context/CounterContext';
const App = () => {
return (
<CounterProvider>
<View style={styles.container}>
<CounterDisplay />
<CounterButton />
</View>
</CounterProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#fff',
},
});
export default App;
import React, { createContext, useState, useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
// 1. Context 생성하기
// (타입스크립트를 사용하는 경우, Context에 들어갈 데이터 타입을 지정해줍니다.)
interface CounterContextType {
count: number;
setCount: React.Dispatch<React.SetStateAction<number>>;
}
const CounterContext = createContext<CounterContextType | null>(null);
// 2. Provider 컴포넌트 만들기
const CounterProvider = ({ children }: { children: React.ReactNode }) => {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
};
// 3. Context를 사용하는 컴포넌트 만들기
const CounterDisplay = () => {
// useContext를 통해 Context의 값을 가져옵니다.
const counterContext = useContext(CounterContext);
if (!counterContext) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
const { count } = counterContext;
return (
<Text style={styles.countText}>현재 카운트: {count}</Text>
);
};
const CounterButton = () => {
const counterContext = useContext(CounterContext);
if (!counterContext) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
const { setCount } = counterContext;
return (
<Button title="카운트 증가" onPress={() => setCount(prev => prev + 1)} />
);
};
const App = () => {
return (
<CounterProvider>
<View style={styles.container}>
<CounterDisplay />
<CounterButton />
</View>
</CounterProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#fff',
},
countText: {
fontSize: 24,
textAlign: 'center',
marginBottom: 20,
},
});
export default App;
useReducer
와 Context를 함께 사용하는 방법도 고려해볼 수 있습니다.
아래 예제는 카운터 상태를 useReducer
를 통해 관리하는 방법입니다.count
상태와 INCREMENT
, DECREMENT
액션을 정의합니다.useReducer
함수를 사용하여 상태와 액션을 관리하는 counterReducer
함수를 만듭니다.CounterProvider
컴포넌트에서 useReducer
를 사용하여 상태와 dispatch 함수를 생성하고, Context에 전달합니다.import React, { createContext, useReducer, useContext } from 'react';
// 상태 타입과 액션 타입 정의
interface CounterState {
count: number;
}
type ActionType = { type: 'INCREMENT' } | { type: 'DECREMENT' };
const initialState: CounterState = { count: 0 };
const counterReducer = (state: CounterState, action: ActionType): CounterState => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// Context 타입 정의
interface CounterContextType {
state: CounterState;
dispatch: React.Dispatch<ActionType>;
}
const CounterContext = createContext<CounterContextType | null>(null);
// Provider 컴포넌트 생성
export const CounterProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
export const useCounter = () => {
const context = useContext(CounterContext);
if (!context) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
return context;
};
export default CounterContext;
CounterDisplay
와 CounterButtons
컴포넌트에서는 useCounter
Hook을 사용하여 상태와 dispatch 함수를 가져옵니다.import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { useCounter } from '../context/CounterContext';
const CounterDisplay = () => {
const { state } = useCounter();
return (
<Text style={styles.countText}>현재 카운트: {state.count}</Text>
);
};
const styles = StyleSheet.create({
countText: {
fontSize: 24,
textAlign: 'center',
marginBottom: 20,
},
});
export default CounterDisplay;
import React from 'react';
import { View, Button } from 'react-native';
import { useCounter } from '../context/CounterContext';
const CounterButtons = () => {
const { dispatch } = useCounter();
return (
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Button title="감소" onPress={() => dispatch({ type: 'DECREMENT' })} />
<Button title="증가" onPress={() => dispatch({ type: 'INCREMENT' })} />
</View>
);
};
export default CounterButtons;
CounterProvider
로 CounterDisplay
와 CounterButtons
를 감싸고, 렌더링합니다.import React from 'react';
import { View, StyleSheet } from 'react-native';
import CounterDisplay from './components/CounterDisplay';
import CounterButtons from './components/CounterButtons';
import { CounterProvider } from './context/CounterContext';
const App = () => {
return (
<CounterProvider>
<View style={styles.container}>
<CounterDisplay />
<CounterButtons />
</View>
</CounterProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#fff',
},
});
export default App;
// App.tsx
import React, { createContext, useReducer, useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
// 상태 타입과 액션 타입 정의
interface CounterState {
count: number;
}
type ActionType = { type: 'INCREMENT' } | { type: 'DECREMENT' };
const initialState: CounterState = { count: 0 };
const counterReducer = (state: CounterState, action: ActionType): CounterState => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// Context 타입 정의
interface CounterContextType {
state: CounterState;
dispatch: React.Dispatch<ActionType>;
}
const CounterContext = createContext<CounterContextType | null>(null);
// Provider 컴포넌트 생성
const CounterProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
const CounterDisplay = () => {
const counterContext = useContext(CounterContext);
if (!counterContext) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
const { state } = counterContext;
return (
<Text style={styles.countText}>현재 카운트: {state.count}</Text>
);
};
const CounterButtons = () => {
const counterContext = useContext(CounterContext);
if (!counterContext) {
throw new Error("CounterContext가 Provider로 감싸져 있지 않습니다.");
}
const { dispatch } = counterContext;
return (
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Button title="감소" onPress={() => dispatch({ type: 'DECREMENT' })} />
<Button title="증가" onPress={() => dispatch({ type: 'INCREMENT' })} />
</View>
);
};
const App = () => {
return (
<CounterProvider>
<View style={styles.container}>
<CounterDisplay />
<CounterButtons />
</View>
</CounterProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#fff',
},
countText: {
fontSize: 24,
textAlign: 'center',
marginBottom: 20,
},
});
export default App;