【开源鸿蒙跨平台开发学习笔记】Day04:React Native 开发 HarmonyOS-GitCode口袋工具开发-2
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
前言
在参加「开源鸿蒙跨平台开发学习活动」的过程中,我选择了 React Native + HarmonyOS 这一技术路线。一方面是希望验证 React Native 在 OpenHarmony 场景下的实际可行性,另一方面也想借这个机会,把自己从环境搭建 → 基础开发 → 实战功能的完整过程系统性地整理下来,形成一套可复用的学习笔记。
到目前为止,这个系列已经完成了前几篇内容,包括:
-
开发环境与工程结构搭建
-
React Native 在 HarmonyOS 中的基础运行
-
GitCode 用户信息等「无需认证接口」的联调
本篇作为第四篇,重点进入一个更接近真实 App 开发的阶段:
👉 GitCode API 的认证接入,以及需要授权的接口联调实践。
我们将以「GitCode 口袋工具 App」为例,实现已 Star 仓库列表的获取与展示,这一步也是整个 App 从“Demo”向“工具型产品”过渡的重要节点。
一、GitCode的API认证和用户star列表展示
上一篇中,我们已经成功联调了无需鉴权的用户信息接口,这一阶段更多是为了验证:
-
网络请求是否能正常跑通
-
React Native 页面渲染是否稳定
-
HarmonyOS 设备上的网络权限是否配置正确
而从这一篇开始,就必须正式引入认证机制。
1. Star 仓库接口说明
GitCode 提供了如下接口,用于获取某个用户已经 Star 的仓库列表:
https://api.gitcode.com/api/v5/users/:username/starred
该接口的参数结构如下:
路径参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | GitCode 用户名 |
请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| access_token | string | 是 | 用户授权令牌 |
| sort | string | 否 | created / last_push |
| direction | string | 否 | asc / desc |
| page | number | 否 | 当前页码 |
| per_page | number | 否 | 每页数量(最大 100) |
可以看到,这里第一次出现了必须携带 access_token 的接口。
也正是从这里开始,如果继续在每个接口里手动拼接 token,不仅代码冗余,而且后续维护成本会迅速上升。
因此,在真正请求这个接口之前,先对网络请求层做一次统一封装,是一个更合理的选择。
因为后续还需要对接很多需要access_token的接口所以我们要封装一下请求请求这块,然后续请求都携带这个access_token;
二、统一封装 GitCode 网络请求客户端
1. 为什么要做统一封装?
在实际项目中,网络请求通常会面临几个共性问题:
-
多个接口都需要携带相同的认证信息
-
错误处理逻辑高度重复
-
后期 token 可能需要动态更新(登录 / 刷新)
如果每个接口都直接使用 axios.get 或 fetch,这些问题会迅速放大。
因此,这里我选择在 src/api 目录下,单独抽出一个 client.ts,作为整个 App 的网络请求入口。
2. 项目目录结构回顾
根据上一篇我们创建的目录结构:
├── src/ # 业务代码主目录
│ ├── api/ # 网络请求层(Axios 封装、接口模块)
│ ├── assets/ # 静态资源(图片、图标、字体、Lottie 动画等)
│ ├── components/ # 公共 UI 组件(按钮、卡片、头部栏等可复用组件)
│ ├── hooks/ # 自定义 React Hooks(如 useAuth、useRequest)
│ ├── navigation/ # 导航配置(Stack、Tab、Drawer 路由)
│ ├── screens/ # 页面(HomeScreen、LoginScreen、DetailScreen)
│ ├── store/ # 状态管理(Redux/Zustand/MobX 等)
│ └── utils/ # 工具函数(格式转换、常量、日期工具、权限工具)
其中,所有与 GitCode API 相关的请求,都统一放在 api 目录中。
3. client.ts:Axios 客户端封装
在 api 目录下新建 client.ts,用于完成以下几件事情:
-
统一设置 GitCode API 的
baseURL -
自动在请求头中注入
private-token -
对网络异常进行统一格式化
import axios from 'axios';
/**
* 统一的 Axios 客户端:
* - 基地址指向 GitCode v5 API
* - 请求拦截器自动注入 `private-token` 头,便于后续请求复用
* - 响应拦截器透传数据/错误,错误统一由 `getErrorMessage` 格式化
* - 提供 `setPrivateToken` 在运行时动态更新私有令牌
*/
// 默认令牌(仅用于开发调试),正式环境建议改为安全的配置或环境变量注入
const DEFAULT_TOKEN = '令牌';
let privateToken = DEFAULT_TOKEN;
export function setPrivateToken(token?: string) {
if (token) {
privateToken = token;
}
}
export const http = axios.create({
baseURL: 'https://api.gitcode.com/api/v5/',
timeout: 10000,
});
// 在每次请求前注入统一的头信息
http.interceptors.request.use(config => {
const headers = config.headers ?? {};
headers['private-token'] = privateToken;
config.headers = headers;
return config;
});
// 透传响应;错误在调用处统一处理
http.interceptors.response.use(
res => res,
err => Promise.reject(err),
);
// 将错误对象转换为用户可读的文案
export function getErrorMessage(error: unknown): string {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const data = error.response?.data as any;
const msg =
typeof data === 'string'
? data
: data?.message || data?.error || error.message;
return status ? `${status} ${msg}` : msg;
}
const e = error as any;
return String(e?.message || e);
}
这样一来:
-
所有接口自动携带 token
-
错误信息在 UI 层可直接展示
-
后续如果接入登录流程,只需要在登录成功后调用
setPrivateToken
三、封装用户相关 API 接口
有了统一的客户端后,具体接口就可以变得非常“干净”。
在 user.ts 中,我们只关注接口本身的语义:
import {http} from './client';
import {UserProfile} from '../types/user';
const PROFILE_PATH = 'users/qiaomu8559968';
export async function fetchUserProfile(): Promise<UserProfile> {
const res = await http.get<UserProfile>(PROFILE_PATH);
return res.data;
}
export async function fetchStarred(username: string): Promise<any[]> {
const res = await http.get<any[]>(`users/${username}/starred`);
return res.data;
}
此时:
-
不需要再关心 token
-
不需要关心 baseURL
-
接口文件只负责“数据获取”
四、首页联调:用户信息 + Star 仓库列表展示
1. 并行请求的设计思路
在首页 Home.tsx 中,我选择使用 Promise.all 同时请求:
-
用户基本信息
-
已 Star 仓库列表
这样可以减少整体等待时间,也更贴近真实 App 的加载体验。
Promise.all([fetchUserProfile(), fetchStarred('qiaomu8559968')])
同时通过 mounted 标记,避免组件卸载后仍然更新状态,这是 React Native 中一个非常实用的小细节。
2. UI 展示策略
UI 上的思路比较简单:
-
顶部展示用户头像、昵称、简介
-
下方以卡片形式展示已 Star 仓库
-
每个仓库支持跳转到 GitCode 页面
即使样式还比较朴素,但已经具备了完整的产品雏形。
我们在user.ts中也添加了本次要用到的“users/${username}/starred”接口来获取用户star的仓库列表,然后再改造首页Home.tsx
import React, {useEffect, useState} from 'react';
import {
View,
Text,
StyleSheet,
Image,
ActivityIndicator,
ScrollView,
Pressable,
Linking,
} from 'react-native';
import {fetchUserProfile, fetchStarred} from '../api';
import {UserProfile} from '../types/user';
import {getErrorMessage} from '../api/client';
export default function Home(): JSX.Element {
const [data, setData] = useState<UserProfile | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [starred, setStarred] = useState<any[]>([]);
useEffect(() => {
let mounted = true;
setLoading(true);
setError('');
Promise.all([fetchUserProfile(), fetchStarred('qiaomu8559968')])
.then(([d, s]) => {
if (!mounted) {
return;
}
setData(d);
setStarred(Array.isArray(s) ? s : []);
})
.catch(e => {
if (!mounted) {
return;
}
setError(getErrorMessage(e));
})
.finally(() => {
if (!mounted) {
return;
}
setLoading(false);
});
return () => {
mounted = false;
};
}, []);
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator />
<Text style={styles.loadingText}>加载中</Text>
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>请求失败:{error}</Text>
</View>
);
}
if (!data) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>暂无数据</Text>
</View>
);
}
return (
<ScrollView contentContainerStyle={styles.scrollContent}>
<Image source={{uri: data.avatar_url}} style={styles.avatar} />
<Text style={styles.title}>{data.name || data.login}</Text>
<Text style={styles.subtitle}>类型:{data.type}</Text>
<Text style={styles.subtitle}>
粉丝:{data.followers},关注:{data.following}
</Text>
{Boolean(data.bio) && <Text style={styles.bio}>{data.bio}</Text>}
<Pressable
onPress={() => Linking.openURL(data.html_url)}
style={styles.linkButton}>
<Text style={styles.linkText}>打开主页</Text>
</Pressable>
<View style={styles.listHeader}>
<Text style={styles.listHeaderText}>已 Star 的仓库</Text>
</View>
{starred.map((item, idx) => {
const name =
item?.name || item?.path || item?.project_name || '未知仓库';
const desc = item?.description || '';
const link = item?.html_url || item?.web_url || item?.url || '';
return (
<View key={`${name}-${idx}`} style={styles.repoCard}>
<Text style={styles.repoName}>{name}</Text>
{!!desc && <Text style={styles.repoDesc}>{desc}</Text>}
{!!link && (
<Pressable
onPress={() => Linking.openURL(link)}
style={styles.repoLinkBtn}>
<Text style={styles.repoLinkText}>访问仓库</Text>
</Pressable>
)}
</View>
);
})}
</ScrollView>
);
}
const styles = StyleSheet.create({
center: {flex: 1, alignItems: 'center', justifyContent: 'center'},
loadingText: {marginTop: 8, fontSize: 14, color: '#666'},
errorText: {fontSize: 14, color: '#d00'},
scrollContent: {alignItems: 'center', paddingVertical: 24},
avatar: {width: 120, height: 120, borderRadius: 60, backgroundColor: '#eee'},
title: {marginTop: 16, fontSize: 24, fontWeight: '700'},
subtitle: {marginTop: 8, fontSize: 16, color: '#666'},
bio: {
marginTop: 12,
fontSize: 14,
color: '#333',
paddingHorizontal: 24,
textAlign: 'center',
},
linkButton: {
marginTop: 16,
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 6,
backgroundColor: '#007aff',
},
linkText: {color: '#fff', fontSize: 14, fontWeight: '600'},
listHeader: {width: '100%', paddingHorizontal: 24, paddingTop: 24},
listHeaderText: {fontSize: 18, fontWeight: '600'},
repoCard: {
width: '92%',
marginTop: 12,
padding: 12,
borderRadius: 8,
backgroundColor: '#f7f7f7',
},
repoName: {fontSize: 16, fontWeight: '600'},
repoDesc: {marginTop: 6, fontSize: 14, color: '#555'},
repoLinkBtn: {
marginTop: 10,
alignSelf: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 6,
backgroundColor: '#34c759',
},
repoLinkText: {color: '#fff', fontSize: 14, fontWeight: '600'},
});
就可以在首页加载出当前用户已经star的仓库列表了,加上之前我们做的用户信息展示,就已经很不错了呢。
五、效果与阶段性成果
到这里为止,我们已经完成了:
-
GitCode 授权接口的实际使用
-
统一网络请求封装
-
React Native 在 HarmonyOS 下的接口联调
-
首页数据完整展示
相比最初只能跑起一个页面,现在这个 App 已经真正“活”了起来。

✿✿ヽ(°▽°)ノ✿
总结
本篇主要围绕网络请求与认证接口联调展开,重点包括:
-
统一请求头(private-token)的设计
-
网络异常的集中处理
-
Star 仓库列表接口的完整落地
完成这些之后,「GitCode 口袋工具 App」已经不再只是一个 Demo,而是开始具备工具型应用的基本形态了。
下一步,就可以继续向分页、缓存、组件拆分、状态管理等更真实的业务方向演进了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)