React Native 基础知识参考字典

React Native 条目式字典:组件 → 样式 → Hooks → 列表 → 导航 → 构建。每条给语义、示例、边界与坑。

1. 核心组件对照(RN ↔ Web)

描述

RN 没有 DOM,组件是原生视图的封装。关键差异:文字必须在 Text 内,Image 网络图必须定尺寸,长列表必须 FlatList。

语法和示例

<table>
<thead><tr><th>RN</th><th>Web 对应</th><th>关键约束</th></tr></thead>
<tbody><tr><td>View</td><td>div</td><td>布局容器;不支持语义样式继承</td></tr><tr><td>Text</td><td>p / span</td><td>一切文字必须在它内;嵌套 Text 才继承样式</td></tr><tr><td>Image</td><td>img</td><td>网络图必须 width/height;本地用 require</td></tr><tr><td>Pressable</td><td>button</td><td>统一按压反馈(取代 Touchable 系)</td></tr><tr><td>FlatList</td><td>—</td><td>长列表虚拟化(必须)</td></tr><tr><td>ScrollView</td><td>overflow:scroll</td><td>全量渲染,短内容才用</td></tr><tr><td>TextInput</td><td>input</td><td>受控 value + onChangeText</td></tr><tr><td>SafeAreaView</td><td>—</td><td>刘海/底部安全区(safe-area-context)</td></tr></tbody></table>

注意事项

  • Text 外放字符串会直接崩(RN 校验严格)
  • Image 不定尺寸等于 0x0(网络图不会自动撑开)
  • ScrollView 内放长列表会让性能崩塌——必须 FlatList
  • Pressable 比 TouchableOpacity 更可控(支持按压状态样式)
  • RN 没有 CSS 级联:样式不继承(Text 例外)

2. Flexbox 默认差异与样式规则

描述

RN 的 Flexbox 默认 flexDirection: ‘column’(Web 是 row);尺寸无单位(iOS 是 pt,Android 是 dp);没有 px/% 混用的概念。

语法和示例

const s = StyleSheet.create({
  row: {
    flexDirection: 'row',          // 必须显式
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: 8,                        // 0.71+ 支持 gap
  },
  card: { padding: 16, borderRadius: 12, backgroundColor: '#fff' },
});
// 百分比:'100%' 只支持部分场景
width: '100%';
flex: 1;                           // 占满剩余空间
// 阴影平台差异
...Platform.select({
  ios: { shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8 },
  android: { elevation: 4 },
});

注意事项

  • 默认 column 是最常见踩坑点——从 Web 转过来的人总要显式写 row
  • 没有 CSS 继承:fontSize/color 要在每个 Text 上设(或用包装组件)
  • margin/padding 负数可用于视觉偏移,但会影响触摸区域
  • 阴影在两端实现完全不同(iOS shadow*,Android elevation)
  • StyleSheet.create 有性能收益(创建一次复用),但样式仍是普通对象

3. useState 函数式更新

描述

依赖旧值时必须传函数形式,否则批量更新下会丢更新。这与 React 的批处理机制有关。

语法和示例

// 正确:函数式更新
setCount(c => c + 1);
setItems(list => [...list, item]);
// 错误:连续两次点只加一次
setCount(count + 1);
setCount(count + 1);
// 对象合并
setForm(f => ({ ...f, name: v }));
// 条件更新
setItems(list => list.filter(x => x.id !== id));

注意事项

  • 循环里连续 setState 用非函数式会只生效最后一次
  • RN 的批处理与 React 18 一致(自动批处理),但异步回调里仍可能分批
  • setState 是异步的,紧接着读 state 拿到旧值——要用 useEffect 或局部变量
  • 大对象/大数组更新前先判等,避免无谓渲染
  • 不要直接把 state 对象改内部字段(引用不变导致不重渲染)

4. useEffect 竞态与清理

描述

副作用(请求、订阅、定时器)。清理函数在卸载/依赖变化时执行,用于取消请求与解绑。

语法和示例

useEffect(() => {
  let dead = false;
  fetch(url).then(r => r.json())
    .then(d => { if (!dead) setData(d); });
  return () => { dead = true; };      // 卸载后不写状态
}, [url]);

// AbortController 版
useEffect(() => {
  const ac = new AbortController();
  fetch(url, { signal: ac.signal })
    .then(r => r.json()).then(setData)
    .catch(e => { if (e.name !== 'AbortError') log(e); });
  return () => ac.abort();
}, [url]);

// 订阅清理
useEffect(() => {
  const sub = NetInfo.addEventListener(handle);
  return () => sub();
}, []);

注意事项

  • 请求后未防竞态:快速切换页面会导致旧请求覆盖新数据
  • 清理函数必须取消订阅/定时器,否则内存泄漏与重复执行
  • 依赖数组漏项会导致闭包拿到旧值(ESLint 的 exhaustive-deps 规则能抓)
  • Effect 里不要用 async 函数(返回 Promise 而非清理函数)——内部包一层
  • 空依赖 [] 只跑一次;不写依赖每次都跑(性能杀手)

5. FlatList 性能 props

描述

长列表虚拟化的关键参数:keyExtractor、getItemLayout、windowSize、initialNumToRender、onEndReached。

语法和示例

<FlatList
  data={rows}
  keyExtractor={(r) => r.id}
  getItemLayout={(_, i) => ({ length: ROW_H, offset: ROW_H * i, index: i })}
  initialNumToRender={12}
  windowSize={9}                  // 默认 21,降低换内存
  maxToRenderPerBatch={10}
  updateCellsBatchingPeriod={50}
  removeClippedSubviews             // Android 收益大,长列表可能白屏
  onEndReached={loadMore}
  onEndReachedThreshold={0.4}
  refreshControl={<RefreshControl refreshing={loading} onRefresh={reload} />}
/>

注意事项

  • 缺 keyExtractor 会用 index(增删导致错位与重渲染)
  • getItemLayout 只适用定高行——不定高时别传(会滚动错位)
  • windowSize 影响渲染范围,太小会白屏(滚动快时来不及渲染)
  • removeClippedSubviews 在某些 Android 版本有白屏 bug,需实测
  • renderItem 内不要创建新函数/对象(每行都重建)——提到外层或用 useCallback

6. 导航:expo-router 与 React Navigation

描述

expo-router 是文件式路由(推荐给新项目);React Navigation 是底层库(Stack/Tabs/Drawer)。参数传递与返回结果都要显式处理。

语法和示例

// expo-router:文件即路由(app/ 目录)
app/index.tsx           → /
app/about.tsx           → /about
app/[id].tsx            → /:id
// 跳转与参数
import { useRouter, useLocalSearchParams } from 'expo-router';
const router = useRouter();
router.push(`/detail/${id}`);
const { id } = useLocalSearchParams<{ id: string }>();

// React Navigation
navigation.navigate('Detail', { id: 42 });
const { id } = route.params;
// Android 返回键
BackHandler.addEventListener('hardwareBackPress', () => { return true; });

注意事项

  • URL 参数是字符串,id 要自己转数字
  • expo-router 的路由文件必须在 app/ 目录且命名符合约定(_layout/tabs/[id])
  • 返回上一屏带结果需用 router 的 params 或状态管理,不是直接 return
  • Android 的物理返回键默认退出应用,需要拦截时返回 true
  • 深链与通知跳转都要在根 layout 里统一处理

7. 平台分叉与安全区

描述

平台差异:Platform.select 与文件后缀(.ios.tsx/.android.tsx);安全区用 safe-area-context。

语法和示例

import { Platform, useWindowDimensions } from 'react-native';
const pad = Platform.select({ ios: 10, android: 8, default: 8 });
// 文件级分叉:Button.ios.tsx / Button.android.tsx 自动按平台加载
// 安全区
import { SafeAreaView } from 'react-native-safe-area-context';
<SafeAreaView edges={['top', 'bottom']}>...</SafeAreaView>
// 响应旋转
const { width, height } = useWindowDimensions();

注意事项

  • Platform.OS 是运行时判断,文件后缀是编译时分离——两者都可用但用途不同
  • iOS 与 Android 的阴影/字体/触摸反馈差异较大,关键 UI 要分别调
  • 安全区必须用 safe-area-context(RN 内置的 SafeAreaView 已不推荐)
  • useWindowDimensions 必须用 Hook(Dimensions.get 不响应旋转)
  • 鸿蒙/Weex 等非标准平台需额外适配,别默认 RN 全兼容

8. 构建与发布

描述

Expo EAS 云构建(Windows 也能出 iOS 包);本地 Android 用 Gradle;OTA 更新只覆盖 JS 层。

语法和示例

bash
1. EAS(推荐,跨平台)
eas build --platform android
eas build --platform ios          # 云端 macOS 构建
1. 本地 Android
cd android && ./gradlew assembleRelease
1. 版本号
1. android/app/build.gradle: versionCode / versionName
1. ios: CFBundleShortVersionString / CFBundleVersion

注意事项

  • Windows 无法本地构建 iOS 包(必须 macOS 或 EAS 云构建)
  • OTA 更新(Expo Updates)只更新 JS 与资源,原生代码变更必须发版
  • versionCode 每次上传都要递增,否则商店拒绝
  • 生产签名密钥必须妥善保存(丢失后无法更新已发布应用)
  • aab 上 Google Play,apk 用于内测分发;iOS 需 TestFlight 或 Ad Hoc
上一篇