一、任务目标
       

       按开源鸿蒙跨平台工程 DAY3 要求,基于React Native 技术栈结合鸿蒙原生适配能力,完成工程网络请求全流程开发与配置,实现美食博客核心页面搭建,包含首页、分类页、食谱详情页,保证网络请求流程规范、页面功能完整,在鸿蒙模拟器正常运行验证。

在开发过程中,我选择了以下技术栈:

  1. 网络请求库:使用 axios,因为它是通用型 HTTP 请求库,支持拦截器、请求取消、响应转换等功能,且在 OpenHarmony 已兼容三方库清单中。
  2. UI 组件:使用 React Native 内置组件(FlatList、ScrollView、Image、Text 等),确保跨平台兼容性,避免使用平台特定的依赖。
  3. 数据管理:使用 React 的 useState 和 useEffect 钩子进行状态管理和副作用处理,实现数据的加载、刷新与页面渲染。
  4. 路由管理:使用 React Navigation 实现页面之间的跳转(首页→分类页→详情页)。
  5. 鸿蒙适配:基于 React Native for OpenHarmony 0.72.5 版本,完成网络权限配置与跨平台运行验证。

二、核心任务拆解

1. 网络能力与权限配置

  • 基于 axios 封装网络请求工具,实现请求 / 响应拦截、超时处理、异常捕获;
  • 在鸿蒙工程中声明 ohos.permission.INTERNET 网络权限,完成跨平台网络访问授权;
  • 模拟接口返回数据(美食分类、食谱列表、详情信息),遵循真实接口请求流程(请求→解析→渲染)。

2. 数据清单列表构建

  • 数据解析:解析 axios 返回的 JSON 数据,提取轮播图、分类、食谱、用料、步骤等核心字段;
  • 列表渲染:使用 RN FlatList 实现食谱列表、分类列表,使用 ScrollView 实现详情页内容滚动;
  • 异常兜底:处理网络错误、空数据、解析异常场景,展示 “加载失败”“暂无数据” 等友好提示。

3. 核心页面开发(美食博客)

  • 首页:标题 + 搜索框 + 美食轮播(自动播放)+ 分类入口(横向滑动)+ 推荐食谱列表(点击跳转详情);
  • 分类页:分类标签切换 + 食谱列表 / 网格视图切换 + 搜索功能,按分类筛选食谱;
  • 详情页:食谱封面 + 名称 + 难度 / 耗时 + 用料清单 + 烹饪步骤 + 评论区(模拟数据)。

4. 运行验证与代码规范

  • 工程在 鸿蒙模拟器(Mate 70 Pro) 正常编译、安装、运行,无崩溃、无布局错乱;
  • 代码结构清晰,按 RN + 鸿蒙开发规范划分目录(pages、components、network、model 等);
  • 网络请求流程完整,数据加载、页面跳转、交互操作均验证通过。

三、关键实现步骤

1. 网络权限配置(鸿蒙端)

在 RN 工程的鸿蒙适配配置中,添加网络权限声明,确保 axios 能正常发起请求:

// module.json5(鸿蒙权限配置)
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET",
    "usedScene": {
      "ability": ["EntryAbility"],
      "when": "always"
    }
  }
]

2. axios 网络请求封装(RN 端)

// network/axios.js
import axios from 'axios';

// 创建axios实例
const instance = axios.create({
  baseURL: 'https://mock-api.food-blog.com', // 模拟接口地址
  timeout: 10000, // 超时时间10s
});

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 可添加请求头、token等
    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    // 解析数据,过滤异常状态码
    if (response.data.code !== 200) {
      throw new Error('接口请求失败');
    }
    return response.data.data;
  },
  (error) => {
    // 处理网络错误、超时等
    if (error.message.includes('timeout')) {
      console.error('请求超时,请检查网络');
    } else if (error.message.includes('Network Error')) {
      console.error('网络连接失败,请检查网络设置');
    }
    return Promise.reject(error);
  }
);

export default instance;

3. 模拟数据与页面渲染(首页示例)

// pages/Index.js
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, Image, StyleSheet, TouchableOpacity } from 'react-native';
import axios from '../network/axios';
import Carousel from 'react-native-snap-carousel'; // 轮播组件
import { useNavigation } from '@react-navigation/native';

const Index = () => {
  const [carouselData, setCarouselData] = useState([]); // 轮播数据
  const [categoryData, setCategoryData] = useState([]); // 分类数据
  const [recipeData, setRecipeData] = useState([]); // 食谱数据
  const [loading, setLoading] = useState(true); // 加载状态
  const navigation = useNavigation();

  // 获取首页数据
  useEffect(() => {
    const fetchIndexData = async () => {
      try {
        setLoading(true);
        // 并行请求轮播、分类、食谱数据
        const [carouselRes, categoryRes, recipeRes] = await Promise.all([
          axios.get('/carousel'),
          axios.get('/categories'),
          axios.get('/recipes/recommend'),
        ]);
        setCarouselData(carouselRes);
        setCategoryData(categoryRes);
        setRecipeData(recipeRes);
      } catch (error) {
        console.error('首页数据请求失败:', error);
      } finally {
        setLoading(false);
      }
    };
    fetchIndexData();
  }, []);

  // 渲染轮播项
  const renderCarouselItem = ({ item }) => (
    <TouchableOpacity
      onPress={() => navigation.navigate('RecipeDetail', { id: item.id })}
    >
      <Image source={{ uri: item.cover }} style={styles.carouselImage} />
      <Text style={styles.carouselTitle}>{item.title}</Text>
    </TouchableOpacity>
  );

  // 渲染食谱项
  const renderRecipeItem = ({ item }) => (
    <TouchableOpacity
      style={styles.recipeCard}
      onPress={() => navigation.navigate('RecipeDetail', { id: item.id })}
    >
      <Image source={{ uri: item.cover }} style={styles.recipeImage} />
      <View style={styles.recipeInfo}>
        <Text style={styles.recipeTitle}>{item.title}</Text>
        <Text style={styles.recipeDesc}>{item.desc}</Text>
        <View style={styles.recipeMeta}>
          <Text style={styles.recipeDifficulty}>难度:{item.difficulty}</Text>
          <Text style={styles.recipeTime}>耗时:{item.time}</Text>
        </View>
      </View>
    </TouchableOpacity>
  );

  if (loading) {
    return <Text style={styles.loadingText}>加载中...</Text>;
  }

  return (
    <View style={styles.container}>
      {/* 标题+搜索框 */}
      <View style={styles.header}>
        <Text style={styles.headerTitle}>美食博客</Text>
        <View style={styles.searchBox}>
          <Text style={styles.searchPlaceholder}>搜索美食...</Text>
        </View>
      </View>

      {/* 轮播图 */}
      <Carousel
        data={carouselData}
        renderItem={renderCarouselItem}
        sliderWidth={350}
        itemWidth={330}
        autoplay
        autoplayInterval={3000}
        loop
      />

      {/* 美食分类 */}
      <Text style={styles.sectionTitle}>美食分类</Text>
      <FlatList
        data={categoryData}
        horizontal
        renderItem={({ item }) => (
          <TouchableOpacity
            style={styles.categoryItem}
            onPress={() => navigation.navigate('Category', { id: item.id })}
          >
            <Text style={styles.categoryName}>{item.name}</Text>
          </TouchableOpacity>
        )}
        keyExtractor={(item) => item.id.toString()}
        showsHorizontalScrollIndicator={false}
      />

      {/* 推荐食谱 */}
      <Text style={styles.sectionTitle}>推荐食谱</Text>
      <FlatList
        data={recipeData}
        renderItem={renderRecipeItem}
        keyExtractor={(item) => item.id.toString()}
        showsVerticalScrollIndicator={false}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: '#f9f4f8' },
  header: { marginBottom: 16 },
  headerTitle: { fontSize: 24, fontWeight: 'bold', color: '#7b2cbf', marginBottom: 8 },
  searchBox: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 8, backgroundColor: '#fff' },
  searchPlaceholder: { color: '#999' },
  carouselImage: { width: '100%', height: 180, borderRadius: 12 },
  carouselTitle: { position: 'absolute', bottom: 16, left: 16, color: '#fff', fontSize: 18, fontWeight: 'bold' },
  sectionTitle: { fontSize: 18, fontWeight: 'bold', marginVertical: 12, color: '#333' },
  categoryItem: { padding: 8, marginRight: 12, backgroundColor: '#fff', borderRadius: 8, borderWidth: 1, borderColor: '#7b2cbf' },
  categoryName: { color: '#7b2cbf', fontWeight: '500' },
  recipeCard: { flexDirection: 'row', marginBottom: 12, backgroundColor: '#fff', borderRadius: 12, padding: 8, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 4 },
  recipeImage: { width: 100, height: 100, borderRadius: 8 },
  recipeInfo: { flex: 1, marginLeft: 12, justifyContent: 'center' },
  recipeTitle: { fontSize: 16, fontWeight: 'bold', color: '#333' },
  recipeDesc: { fontSize: 12, color: '#666', marginVertical: 4 },
  recipeMeta: { flexDirection: 'row', justifyContent: 'space-between' },
  recipeDifficulty: { fontSize: 12, color: '#7b2cbf' },
  recipeTime: { fontSize: 12, color: '#666' },
  loadingText: { flex: 1, textAlign: 'center', fontSize: 16, color: '#666' },
});

export default Index;

四、运行效果展示

1. 美食博客首页

  • 页面由 RN 内置组件搭建,轮播图自动播放,分类横向滑动,食谱列表可点击跳转详情,数据由 axios 请求解析后渲染。

2. 美食分类页

  • 支持分类标签切换、列表 / 网格视图切换,搜索框可输入筛选,布局适配鸿蒙模拟器屏幕,无跨平台偏移。

3. 食谱详情页

  • 展示食谱完整信息(封面、名称、难度、耗时、用料、步骤、评论),所有模块由 RN 组件实现,交互流畅。

4. 工程编译运行日志

  • DevEco Studio 编译无报错,RN 工程成功安装到鸿蒙模拟器,axios 网络请求、数据解析日志正常打印,验证工程运行稳定。

五、遇到的问题与解决方法

1. 问题:RN + 鸿蒙环境下,axios 请求提示 “网络权限未授权”

解决:在鸿蒙module.json5中补充ohos.permission.INTERNET权限的usedScene配置,指定ability与when字段,重新编译后权限生效。

2. 问题:axios 请求超时,数据加载失败

解决:调整 axios 超时时间为 10s,添加弱网重试逻辑(请求失败后自动重试 2 次),同时优化模拟接口响应速度,确保数据正常加载。

3. 问题:RN 轮播组件在鸿蒙模拟器中滑动卡顿

解决:使用react-native-snap-carousel的enableSnap属性优化滑动体验,减少轮播图预加载数量,降低鸿蒙模拟器资源占用。

4. 问题:RN 列表在鸿蒙端渲染缓慢,数据展示延迟

解决:使用FlatList的initialNumToRender和maxToRenderPerBatch属性优化列表渲染,只加载可视区域内的列表项,提升渲染速度。

六、任务总结

本次 DAY3 任务基于React Native + 开源鸿蒙技术栈,完成了网络请求集成与美食博客核心页面开发:
技术上:通过 axios 实现了规范的网络请求流程,完成鸿蒙网络权限配置,解决了跨平台网络访问、组件适配等问题;
功能上:实现了首页轮播、分类筛选、食谱详情、异常兜底等核心功能,页面交互流畅、布局适配鸿蒙设备;
验证上:工程在鸿蒙模拟器中正常运行,网络请求、数据渲染、页面跳转均验证通过,满足 DAY3 任务的所有要求。
后续可在此基础上对接真实美食接口,替换模拟数据实现动态加载;同时完善评论发布、点赞、收藏等功能,进一步丰富美食博客的使用体验。

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

 

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐