React Native 技能
加载方式:base.md + typescript.md
项目结构
project/
├── src/
│ ├── core/ # 纯业务逻辑(无React)
│ │ ├── types.ts
│ │ └── services/
│ ├── components/ # 可重用的UI组件
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.test.tsx
│ │ │ └── index.ts
│ │ └── index.ts # 桶导出
│ ├── screens/ # 屏幕组件
│ │ ├── Home/
│ │ │ ├── HomeScreen.tsx
│ │ │ ├── useHome.ts # 屏幕特定钩子
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── navigation/ # 导航配置
│ ├── hooks/ # 共享自定义钩子
│ ├── store/ # 状态管理
│ └── utils/ # 工具
├── __tests__/
├── android/
├── ios/
└── CLAUDE.md
组件模式
仅使用函数组件
// 好的 - 简单,可测试
interface ButtonProps {
label: string;
onPress: () => void;
disabled?: boolean;
}
export function Button({ label, onPress, disabled = false }: ButtonProps): JSX.Element {
return (
<Pressable onPress={onPress} disabled={disabled}>
<Text>{label}</Text>
</Pressable>
);
}
将逻辑提取到钩子
// useHome.ts - 所有逻辑都在这里
export function useHome() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(false);
const refresh = useCallback(async () => {
setLoading(true);
const data = await fetchItems();
setItems(data);
setLoading(false);
}, []);
return { items, loading, refresh };
}
// HomeScreen.tsx - 纯展示
export function HomeScreen(): JSX.Element {
const { items, loading, refresh } = useHome();
return (
<ItemList items={items} loading={loading} onRefresh={refresh} />
);
}
总是明确Props接口
// 总是定义props接口,即使简单
interface ItemCardProps {
item: Item;
onPress: (id: string) => void;
}
export function ItemCard({ item, onPress }: ItemCardProps): JSX.Element {
...
}
状态管理
首先使用本地状态
// 从useState开始,只有在需要时才升级
const [value, setValue] = useState('');
如果需要,使用Zustand进行全局状态管理
// store/useAppStore.ts
import { create } from 'zustand';
interface AppState {
user: User | null;
setUser: (user: User | null) => void;
}
export const useAppStore = create<AppState>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
使用React Query进行服务器状态管理
// hooks/useItems.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useItems() {
return useQuery({
queryKey: ['items'],
queryFn: fetchItems,
});
}
export function useCreateItem() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createItem,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['items'] });
},
});
}
测试
使用React Native Testing Library进行组件测试
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
describe('Button', () => {
it('按下时调用onPress', () => {
const onPress = jest.fn();
const { getByText } = render(<Button label="点击我" onPress={onPress} />);
fireEvent.press(getByText('点击我'));
expect(onPress).toHaveBeenCalledTimes(1);
});
it('禁用时不调用onPress', () => {
const onPress = jest.fn();
const { getByText } = render(<Button label="点击我" onPress={onPress} disabled />);
fireEvent.press(getByText('点击我'));
expect(onPress).not.toHaveBeenCalled();
});
});
钩子测试
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('增加计数器', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
平台特定代码
谨慎使用Platform.select
import { Platform } from 'react-native';
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
},
android: {
elevation: 2,
},
}),
});
对于复杂差异,使用单独的文件
Component/
├── Component.tsx # 共享逻辑
├── Component.ios.tsx # iOS特定
├── Component.android.tsx # Android特定
└── index.ts
React Native 反模式
- ❌ 内联样式 - 使用StyleSheet.create
- ❌ 渲染中的逻辑 - 提取到钩子
- ❌ 深层组件嵌套 - 扁平层次结构
- ❌ 匿名函数作为props - 使用useCallback
- ❌ 列表中使用索引作为key - 使用稳定的ID
- ❌ 直接状态突变 - 总是使用setter
- ❌ 将业务逻辑与UI混合 - 保持core/纯粹
- ❌ 忽略TypeScript错误 - 修复它们
- ❌ 大型组件 - 分割成更小的部分