本节摘要:网络请求是移动应用连接世界的窗口。RN 用内置 Fetch 或第三方 Axios,Flutter 用官方 Http 或第三方 Dio。本节讲清网络请求的五个步骤(构建、发送、接收、处理、容错)、JSON 数据解析与模型类、以及"加载中、成功、失败"三态界面管理——这是从"能联网"到"可靠联网"的关键。核心主张:网络层的完整度由错误处理决定。
阅读完本节,你应当能够:
一个 App 如果完全不联网,能力就砍掉了一大半。但"联网"这个需求,初学者的实现往往只有一半:发请求、拿数据、渲染——看起来能跑,一遇到失败就露馅。慢网络下转圈卡死、断网时白屏、后端报错时界面毫无反馈。真实的联网体验,三分靠发请求,七分靠处理"发不出去、收不回来、解析不了"这三类异常。
本节的核心主张先放在这里:网络请求的完整度,由错误处理决定,而不是由请求本身决定。学会发请求只需要十分钟,学会优雅地处理失败才需要真正理解网络。带着这个标准去读本节,你会抓住比 API 更重要的东西。
再补一个观念铺垫:网络层是移动应用里最"不可控"的部分。本地代码的行为你可预期,网络请求的结果不可预期——服务器可能宕机、网络可能断、数据可能超时、返回格式可能变。正因如此,网络层必须被设计成"防御性"的:默认假设会失败,然后在失败时给出优雅的反馈。这个"默认防御"的心态,会贯穿你后面写的每一个联网功能。
一次完整的网络交互拆开看,永远是五个步骤:
构建请求:定义方法(GET/POST)、URL、请求头、请求体。
发送请求:通过网络库发出。
接收响应:服务器返回状态码、响应头、响应体。
处理响应:检查状态码判断成功与否,解析响应体数据。
错误处理:捕获网络错误、服务器错误、解析错误。
一句话点题:五个步骤里,新手往往只关注前三个,老手把后两个当成重点。状态码检查与错误处理,才是网络代码的护城河。
Fetch API 是 RN 内置能力,无需安装,基于 Promise:
fetch('https://api.example.com/data') .then((response) => { if (!response.ok) { throw new Error('请求失败,状态码:' + response.status); } return response.json(); }) .then((data) => { console.log('获取数据:', data); }) .catch((error) => { console.error('请求出错:', error); });
Axios 是第三方库,功能更强:提供拦截器、请求取消、超时配置等。写法更简洁:
const response = await axios.get('https://api.example.com/data'); console.log(response.data);
Fetch 够用、Axios 更好用。多数项目直接用 Axios,它把状态码检查、JSON 解析这些常规操作简化了。
GET 拉数据之外,POST 是另一类高频操作。POST 需要设置请求头与请求体:
// Fetch 的 POST fetch('https://api.example.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'John', age: 30 }), }) .then((response) => response.json()) .then((data) => console.log('提交结果:', data)); // Axios 的 POST 更简洁 await axios.post('https://api.example.com/submit', { name: 'John', age: 30 });
注意 Fetch 需要手动设置 Content-Type 并把对象序列化成 JSON 字符串;Axios 自动处理这两步。这是"基础库"与"增强库"差异的典型体现——基础库把细节留给你,增强库替你包办。
直接使用解析后的 Map 或对象,字段访问散落各处,接口字段一改就要全局搜索替换。专业做法是为每个接口定义模型类:
// RN 侧用 TypeScript 定义模型 interface TodoItem { id: number; title: string; completed: boolean; }
// Flutter 侧定义模型类 class TodoItem { final int id; final String title; final bool completed; TodoItem({required this.id, required this.title, required this.completed}); factory TodoItem.fromJson(Map<String, dynamic> json) { return TodoItem( id: json['id'], title: json['title'], completed: json['completed'], ); } }
模型类的价值有三:字段集中定义、类型检查(编译期抓错)、解析逻辑复用。接口字段变更时,只改模型类一处,界面代码不用动。这个习惯从第一个联网页面就值得建立,后续项目规模越大收益越明显。
Http 包是 Flutter 官方基础库,简单直接:
final response = await http.get(Uri.parse('https://api.example.com/data')); if (response.statusCode == 200) { final data = jsonDecode(response.body); print(data); }
Dio 是第三方库,功能对标 Axios:拦截器、取消、超时、日志。大型项目常用。
final response = await Dio().get('https://api.example.com/data'); print(response.data);
对应关系清晰:Fetch 对应 Http(内置基础),Axios 对应 Dio(功能增强)。选型逻辑也一致:简单需求用内置,复杂需求上第三方。
Flutter 没有 TypeScript 的类型系统加持,模型类转换是常规操作。上面定义的 fromJson 工厂方法就是为这一步服务的:
Future<List<TodoItem>> fetchTodos() async { final response = await http.get(Uri.parse('https://api.example.com/todos')); if (response.statusCode != 200) { throw Exception('请求失败:${response.statusCode}'); } final List<dynamic> jsonList = jsonDecode(response.body); return jsonList .map((json) => TodoItem.fromJson(json)) .toList(); }
这段代码把"网络请求、状态码检查、JSON 解析、模型转换"全部封装进一个方法。界面只需要调用 fetchTodos 拿到 List,不用关心网络细节。这种"网络层与界面层分离"的结构,是 Flutter 项目组织的最佳实践——网络代码归网络层,界面只消费结果。
JSON 解析。RN 的 response.json() 自动解析;Flutter 用 jsonDecode 解析成 Map 或 List,再用模型类转换。建议为每个接口定义模型类,把"解析"封装起来,避免散落的字段访问。
三态管理。联网页面普遍需要三个界面状态:
| 状态 | 界面表现 | 触发时机 |
|---|---|---|
| 加载中 | 转圈或骨架屏 | 请求发起后 |
| 成功 | 渲染数据 | 数据返回并解析成功 |
| 失败 | 错误提示加重试按钮 | 请求失败或解析失败 |
三态的工程实现:用状态字段(loading、data、error)驱动界面分支。把三态封装成通用组件,每个联网页面复用,是工程质量的体现。
把"加载中、成功、失败"三个界面状态画成一张图,联网页面的结构就清楚了:

| 能力 | React Native | Flutter |
|---|---|---|
| 内置基础库 | Fetch API | Http 包 |
| 增强第三方库 | Axios | Dio |
| JSON 解析 | response.json() | jsonDecode |
| 拦截器 | Axios 支持 | Dio 支持 |
| 请求取消 | AbortController | CancelToken |
坑一:不检查状态码。把任何响应都当成功,后端 500 了还在解析数据。先查 response.ok 或 statusCode。
坑二:错误处理只在 .catch 里打日志。用户看到的是"卡住"而不是"出错"。catch 里必须更新界面状态为失败。
坑三:不在卸载时取消请求。5.4 节讲过,请求返回时页面可能已关。用 AbortController(RN)或 CancelToken(Dio)在清理时取消。
⚠️ 常见坑:把整个 JSON 响应直接塞进界面状态。未解析的原始数据与界面耦合,字段一改就要改界面。正确做法是解析成模型对象,界面只消费模型。
💡 关键直觉:把网络请求想成"不确定的异步操作"——可能成功、可能失败、可能超时、可能在返回前被用户放弃。代码要覆盖这四种可能,才叫完整的网络层。
把待办应用的"添加待办"从本地改为调用模拟 API(可以用公开的测试接口或本地 Mock),实现加载中、成功、失败三态:请求时按钮转圈、失败时提示重试、成功后刷新列表。这个练习把本节所有要点落到了真实需求上,也是"从 Demo 到产品"的关键一步。
问:Fetch 和 Axios 到底怎么选? 简单请求用 Fetch(内置、够用),需要拦截器、取消、统一错误处理时用 Axios。多数真实项目直接用 Axios,因为它把高频需求都封装好了。判断标准:你的项目需要"所有请求统一加 token"这类横切逻辑吗?需要就上 Axios,它用拦截器一条语句解决。
问:接口数据字段变了怎么办? 这就是模型类的价值所在。如果所有界面直接消费解析后的 Map,字段改名要全局搜索替换;用模型类则只改 fromJson 一处。这也是为什么前面强调"从第一个联网页面就建模型类"——它能持续省下这种维护成本。
问:HTTPS 证书、跨域这些 Web 概念在移动端存在吗? 部分存在。RN 与 Flutter 在移动端没有浏览器同源策略的跨域限制,但 HTTPS 证书校验仍然存在——测试环境用自签名证书时,可能需要临时信任或关闭校验(仅限开发期)。生产环境必须走正规证书,这是底线。
问:弱网环境下请求很慢,怎么优化? 几个方向:设置合理的超时时间(别无限等)、加加载状态反馈(用户知道在加载)、考虑请求重试与缓存、图片用渐进加载。弱网优化是个系统话题,入门阶段先做到"超时可控 + 状态可见",已经领先大多数 Demo 了。
网络请求的代码散落在各个页面里,是新手项目常见的乱象。进阶做法是接口层封装:把所有网络调用集中到一个模块,页面只调用方法、不直接写请求代码。比如定义一个获取待办、提交待办的函数集,页面里 fetchTodos() 一调即可。这个分层带来三个好处:接口变化只改一处、页面代码更干净、请求逻辑(超时、重试、错误处理)可以统一管理。第 6.2 节学的所有网络知识,最终都该沉淀到这样一层"接口层"里,而不是堆在每个页面中。
网络请求还有两个容易被忽略的配置:超时与重试。超时——请求挂起多久算失败。不设超时意味着"无限期等待",弱网下界面卡死。RN 的 fetch 支持 AbortController 控制超时,Axios 有 timeout 配置;Flutter 的 Dio 有 connectTimeout 与 receiveTimeout。重试——失败后要不要重试、试几次。多数网络库支持重试机制,但要控制次数(一般一次)并加上延迟,避免对后端造成压力。这两个配置加起来,网络层的健壮性会明显提升——它们属于"默认防御"心态的具体实现。
把本节的要点合成一个完整的请求流程,方便对照检查:构建请求(带超时)→ 发送 → 检查状态码(非 2xx 抛错)→ 解析 JSON → 转换模型 → 更新界面状态(成功态)→ 捕获错误(更新失败态并提示重试)。任何一个环节缺失,都说明网络层不完整。初学者最容易漏的是"状态码检查"与"失败态界面",这两处正是第 2.1 节说过的"护城河"。写每个联网功能时,对着这个流程逐项确认,网络层就会越来越接近生产级质量。
网络功能写完后,调试它有一个固定套路:先用网络面板确认请求是否发出、状态码是什么、响应体长什么样,再判断问题出在"请求没发出去""返回了错误码"还是"解析出了问题"。这个顺序能把问题快速定位到三层中的一层。特别提醒:真机测试时,模拟器的网络环境与真机可能不同(比如本地开发服务器),"模拟器能连、真机连不上"常常是网络配置而非代码问题。按这个套路排查,联网功能的问题大多能快速收敛。
数据能联网拉进来了,下一步让数据"落地"——本地存储,让应用关掉再打开数据还在。