4. RESTful API设计与客户端调用

REST 不是某个具体框架或库,而是一组围绕“资源”组织网络接口的架构约束。理解这些约束,能帮助我们在设计接口时保持路径清晰、语义一致,也能让 React Native 客户端在调用时更容易判断请求是否成功、失败原因是什么、下一步该做什么。本节从 REST 原则出发,落到资源路径设计、HTTP 状态码语义,再完整走一遍在 React Native 中调用 REST 接口并渲染列表的实现过程。

REST 的核心原则与资源路径设计

REST(Representational State Transfer,表述性状态转移)强调用统一接口操作资源。落到 HTTP API 上,最关键的几条约束是:客户端与服务器分离、无状态、可缓存、统一接口、分层系统。其中对日常开发影响最大的是“无状态”和“统一接口”。无状态意味着每个请求都应携带服务器完成处理所需的全部信息,服务器不在两次请求之间保存客户端会话上下文;统一接口则意味着用同一套方法语义操作不同资源,而不是为每个动作发明一个新路径。

资源路径设计应遵循“名词表示资源、HTTP 方法表示动作”的原则。例如文章资源用 /articles 表示集合,用 /articles/42 表示单个资源。获取列表用 GET /articles,获取单篇用 GET /articles/42,创建用 POST /articles,整体更新用 PUT /articles/42,局部更新用 PATCH /articles/42,删除用 DELETE /articles/42。路径中应避免出现动词,如 /getArticleList 或 /deleteArticleById,这类写法把动作塞进路径,破坏了统一接口,也让缓存和权限控制更难统一处理。

资源之间有关联时,用嵌套路径表达从属关系,例如 /articles/42/comments 表示文章 42 的评论集合。但嵌套不宜过深,通常不超过两层;如果评论本身可以独立寻址,也可以直接暴露 /comments?articleId=42。过滤、排序、分页属于对集合的“视图”控制,应放在查询参数中,例如 /articles?page=1&pageSize=20&sort=-createdAt,其中 -createdAt 表示按创建时间倒序。这样路径保持稳定,查询条件可组合、可缓存。

HTTP 状态码语义与客户端判断

状态码是服务器对请求结果的标准化表达,客户端应依据状态码而不是响应体中的自定义字段来判断请求结果。常见语义如下:200 OK 表示请求成功并返回内容;201 Created 表示资源创建成功,通常配合 Location 响应头指向新资源;204 No Content 表示成功但无响应体,常用于删除或更新;400 Bad Request 表示请求参数或格式有误;401 Unauthorized 表示未认证;403 Forbidden 表示已认证但无权限;404 Not Found 表示资源不存在;409 Conflict 表示与当前资源状态冲突,如重复创建;422 Unprocessable Entity 表示语义校验失败;500 Internal Server Error 表示服务器内部错误。

在客户端,fetch 只有在网络层失败时才会 reject,HTTP 4xx 和 5xx 并不会自动进入 catch。因此必须显式检查 response.ok 或 response.status。一个稳健的调用流程是:先判断网络是否可达,再判断状态码区间,最后解析响应体。2xx 视为成功,4xx 视为客户端可修正的问题,5xx 视为服务端问题并适合重试或提示稍后再试。把状态码语义映射为客户端可执行的分支,是 REST 调用中非常关键的一步。

REST 请求 → 状态码 → 客户端分支 React Native 客户端 GET /articles?page=1 Accept: application/json Authorization: Bearer … fetch(url, { method, headers }) HTTP 请求 REST 服务端 资源路由匹配 鉴权 / 参数校验 查询数据库 序列化为 JSON HTTP 响应 状态码语义 2xx 成功 4xx 客户端问题 5xx 服务端问题 response.ok / status 成功分支 200 解析 JSON 列表 201 读取 Location 204 不解析响应体 客户端问题分支 400 检查请求参数 401 跳转登录 / 刷新令牌 404 展示空状态 服务端问题分支 500 提示稍后重试 503 退避重试 记录日志便于排障 渲染列表:FlatList data / renderItem / keyExtractor 加载中 → 空列表 → 错误提示 → 正常渲染

在 React Native 中调用 REST 接口

React Native 内置 fetch,无需额外安装网络库即可发起 HTTP 请求。下面以文章列表接口 GET /articles?page=1&pageSize=20 为例,展示一个包含状态码判断、错误分类和超时控制的调用封装。示例中的接口地址、返回数据和状态码均为教学假设,实际项目应替换为真实服务端地址与字段。

const BASE_URL = 'https://api.example.com';

async function fetchArticles(page = 1, pageSize = 20) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 8000);

  try {
    const res = await fetch(
      `${BASE_URL}/articles?page=${page}&pageSize=${pageSize}`,
      {
        method: 'GET',
        headers: {
          Accept: 'application/json',
          Authorization: 'Bearer demo-token',
        },
        signal: controller.signal,
      }
    );

    if (res.status === 204) return { items: [], total: 0 };

    if (!res.ok) {
      if (res.status === 401) throw new Error('UNAUTHORIZED');
      if (res.status === 404) throw new Error('NOT_FOUND');
      if (res.status >= 500) throw new Error('SERVER_ERROR');
      throw new Error(`HTTP_${res.status}`);
    }

    const json = await res.json();
    return { items: json.data ?? [], total: json.total ?? 0 };
  } finally {
    clearTimeout(timer);
  }
}

这段代码体现了几个关键点:用 AbortController 设置 8 秒超时,避免请求悬挂;先处理 204 无响应体的情况,再判断 res.ok;把 401、404、5xx 映射为不同的错误标识,便于 UI 层分别处理;解析 JSON 时用空值合并运算符兜底,防止字段缺失导致渲染崩溃。

渲染列表与状态管理

拿到数据后,用 FlatList 渲染列表。列表组件需要处理四种状态:加载中、加载成功、空列表、加载失败。下面是一个完整的函数组件示例,包含下拉刷新和分页加载。

import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, FlatList, ActivityIndicator, RefreshControl } from 'react-native';

export default function ArticleList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [refreshing, setRefreshing] = useState(false);
  const [error, setError] = useState(null);

  const load = useCallback(async (targetPage, isRefresh = false) => {
    if (isRefresh) setRefreshing(true);
    else setLoading(true);
    setError(null);

    try {
      const { items: list } = await fetchArticles(targetPage, 20);
      setItems(prev => (isRefresh || targetPage === 1 ? list : [...prev, ...list]));
      setPage(targetPage);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
      setRefreshing(false);
    }
  }, []);

  useEffect(() => { load(1); }, [load]);

  if (loading && items.length === 0) {
    return <ActivityIndicator size="large" style={{ marginTop: 40 }} />;
  }

  if (error && items.length === 0) {
    return <Text style={{ textAlign: 'center', marginTop: 40 }}>加载失败:{error}</Text>;
  }

  return (
    <FlatList
      data={items}
      keyExtractor={(item) => String(item.id)}
      renderItem={({ item }) => (
        <View style={{ padding: 16, borderBottomWidth: 1, borderColor: '#eee' }}>
          <Text style={{ fontSize: 16, fontWeight: '600' }}>{item.title}</Text>
          <Text style={{ color: '#666', marginTop: 4 }}>{item.summary}</Text>
        </View>
      )}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={() => load(1, true)} />
      }
      onEndReachedThreshold={0.3}
      onEndReached={() => { if (!loading) load(page + 1); }}
      ListEmptyComponent={<Text style={{ textAlign: 'center', marginTop: 40 }}>暂无文章</Text>}
    />
  );
}

这段实现中,keyExtractor 使用服务端返回的稳定 id,避免用数组下标导致列表项复用错乱;onEndReached 触发下一页加载,并用 loading 防止重复请求;RefreshControl 实现下拉刷新,刷新时把页码重置为 1 并替换列表;ListEmptyComponent 处理空数据状态。错误信息通过 error 状态展示,若已有数据则保留旧列表,避免一次失败清空整个页面。

调用 REST 接口时,建议把网络层封装为独立模块,统一处理 baseURL、超时、鉴权头、状态码映射和错误日志。组件只关心“加载中、成功、空、失败”四种 UI 状态,不直接拼接 URL 或解析状态码,这样后续更换接口或增加重试策略时改动范围更小。
不要用响应体中的 code: 0 或 success: true 替代 HTTP 状态码作为唯一判断依据。状态码是 HTTP 协议层的标准语义,代理、网关、缓存和监控系统都依赖它;自定义业务码可以作为补充,但不应取代状态码。另外,fetch 不会因 4xx/5xx 自动抛错,忘记检查 res.ok 会把错误响应当成正常数据解析,导致难以定位的渲染异常。

把以上环节串起来:设计阶段用名词路径和 HTTP 方法表达资源与动作,用查询参数表达过滤分页;服务端用状态码准确表达结果;客户端用 fetch 发起请求,显式判断状态码并映射为可处理的错误类型;最后用 FlatList 渲染列表,覆盖加载、成功、空、失败四种状态。这样一条链路既符合 REST 约束,也能让 React Native 页面在真实网络环境下保持稳定和可维护。