출시·고도화 중
React Native 안내서 · 4/6
React Native 앱의 상태 관리는 React와 같습니다. 이 장에서는 훅으로 상태를 다루는 법, fetch로 서버 데이터를 가져오는 법, 기기에 데이터를 저장하는 법, 그리고 자주 쓰는 상태 관리 라이브러리를 살펴봅니다.
컴포넌트 안의 상태는 useState·useReducer로, 여러 화면이 함께 쓰는 값은 Context나 상태 라이브러리로 관리합니다. TextInput은 value와 onChangeText로 상태와 연결하는 제어 컴포넌트로 쓰는 것이 일반적입니다.
import { useState } from "react";
import { Button, Text, TextInput, View } from "react-native";
export function Greeting() {
const [name, setName] = useState("");
const [count, setCount] = useState(0);
return (
<View style={{ padding: 16, gap: 8 }}>
<TextInput value={name} onChangeText={setName} placeholder="이름" />
<Text>안녕하세요, {name || "손님"}님 ({count})</Text>
<Button title="+1" onPress={() => setCount((c) => c + 1)} />
</View>
);
}React Native는 웹과 같은 fetch API를 제공합니다. 화면이 사라진 뒤 응답이 도착하는 경우를 대비해 AbortController로 요청을 취소합니다.
import { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, Text } from "react-native";
type Post = { id: number; title: string };
const API_URL = process.env.EXPO_PUBLIC_API_URL;
export function Posts() {
const [posts, setPosts] = useState<Post[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch(`${API_URL}/posts`, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<Post[]>;
})
.then(setPosts)
.catch((e) => { if (e.name !== "AbortError") setError(String(e)); })
.finally(() => setLoading(false));
return () => controller.abort();
}, []);
if (loading) return <ActivityIndicator />;
if (error) return <Text>오류: {error}</Text>;
return <FlatList data={posts} keyExtractor={(p) => String(p.id)} renderItem={({ item }) => <Text>{item.title}</Text>} />;
}몇 가지 주의할 점이 있습니다.
EXPO_PUBLIC_으로 시작하는 환경 변수만 앱 코드에 들어갑니다. 앱 번들은 누구나 열어 볼 수 있으므로 비밀 키를 넣으면 안 됩니다.localhost 대신 10.0.2.2를 씁니다.로딩·오류·캐시·재시도를 직접 다루는 대신 TanStack Query 같은 라이브러리를 쓰면 코드가 짧아집니다.
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
import { Text } from "react-native";
const queryClient = new QueryClient();
function PostCount() {
const { data, isPending, error } = useQuery({
queryKey: ["posts"],
queryFn: () => fetch("https://example.com/api/posts").then((r) => r.json()),
});
if (isPending) return <Text>불러오는 중</Text>;
if (error) return <Text>오류</Text>;
return <Text>글 {data.length}개</Text>;
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<PostCount />
</QueryClientProvider>
);
}React Native 코어에는 영구 저장소가 없으므로 라이브러리를 씁니다.
| 용도 | 라이브러리 |
|---|---|
| 간단한 키-값(설정, 캐시) | @react-native-async-storage/async-storage |
| 토큰·비밀번호 같은 민감한 값 | expo-secure-store, react-native-keychain |
| 구조화된 많은 데이터 | expo-sqlite |
| 빠른 동기 키-값 | react-native-mmkv |
AsyncStorage는 문자열만 저장하는 비동기 키-값 저장소이며 암호화하지 않습니다. 객체는 JSON으로 바꿔 저장합니다.
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from "expo-secure-store";
export async function saveSettings(settings: { theme: string }) {
await AsyncStorage.setItem("settings", JSON.stringify(settings));
}
export async function loadSettings() {
const raw = await AsyncStorage.getItem("settings");
return raw ? (JSON.parse(raw) as { theme: string }) : null;
}
// 토큰은 iOS Keychain · Android Keystore 기반의 SecureStore에
export const saveToken = (token: string) => SecureStore.setItemAsync("token", token);
export const loadToken = () => SecureStore.getItemAsync("token");Expo 프로젝트에서는 npx expo install @react-native-async-storage/async-storage expo-secure-store로 설치합니다.
여러 화면이 공유하는 클라이언트 상태에는 Zustand, Redux Toolkit, Jotai 등이 쓰입니다. 다음은 Zustand 저장소를 AsyncStorage에 저장해 앱을 다시 켜도 유지되게 하는 예입니다.
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
type CartState = { items: string[]; add: (id: string) => void; clear: () => void };
export const useCart = create<CartState>()(
persist(
(set) => ({
items: [],
add: (id) => set((s) => ({ items: [...s.items, id] })),
clear: () => set({ items: [] }),
}),
{ name: "cart", storage: createJSONStorage(() => AsyncStorage) },
),
);useState·useReducer로, 공유 상태는 Context나 Zustand·Redux Toolkit으로 관리합니다.fetch로 가져오고, 캐시·재시도가 필요하면 TanStack Query를 씁니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.