已發布·持續改進
React Native 指南 · 4/6
本章目前僅提供英文版。
State in React Native works exactly as in React. This chapter covers state with hooks, fetching server data with fetch, storing data on the device, and the state libraries teams commonly use.
Keep component state in useState or useReducer, and move values shared across screens into Context or a state library. TextInput is usually a controlled component, wired to state through value and 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="Name" />
<Text>Hello, {name || "guest"} ({count})</Text>
<Button title="+1" onPress={() => setCount((c) => c + 1)} />
</View>
);
}React Native provides the same fetch API as the web. Cancel requests with AbortController in case the screen goes away before the response arrives.
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: {error}</Text>;
return <FlatList data={posts} keyExtractor={(p) => String(p.id)} renderItem={({ item }) => <Text>{item.title}</Text>} />;
}A few things to keep in mind:
EXPO_PUBLIC_ are inlined into app code. Anyone can unpack an app bundle, so never put secrets there.10.0.2.2 instead of localhost.Instead of hand-rolling loading, error, caching and retry logic, a library such as TanStack Query keeps the code short.
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>Loading</Text>;
if (error) return <Text>Something went wrong</Text>;
return <Text>{data.length} posts</Text>;
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<PostCount />
</QueryClientProvider>
);
}React Native core has no persistent storage, so you pick a library for the job.
| Use | Library |
|---|---|
| simple key-value data (settings, cache) | @react-native-async-storage/async-storage |
| sensitive values such as tokens | expo-secure-store, react-native-keychain |
| large structured data | expo-sqlite |
| fast synchronous key-value | react-native-mmkv |
AsyncStorage is an unencrypted, asynchronous key-value store for strings, so serialize objects as 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;
}
// Tokens belong in SecureStore, backed by the iOS Keychain and Android Keystore
export const saveToken = (token: string) => SecureStore.setItemAsync("token", token);
export const loadToken = () => SecureStore.getItemAsync("token");In an Expo project, install both with npx expo install @react-native-async-storage/async-storage expo-secure-store.
For client state shared across screens, common choices are Zustand, Redux Toolkit and Jotai. This Zustand store is persisted to AsyncStorage, so it survives app restarts:
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 and useReducer for screen state, and Context, Zustand or Redux Toolkit for shared state.fetch; reach for TanStack Query when you need caching and retries.
0 則留言
登入 · 登入後即可留言。
來留下第一則留言吧。