🚀【RN鸿蒙教学|第10课时】应用异常处理+性能优化实战:夯实稳定性,备战打包部署

📋 一、课前准备(5分钟)

🛠️ 1.1 环境与工程确认

  • ✅ 确认 rnHarmonyDemo 工程可正常运行,RN版本 0.72.7,依赖无冲突;
  • ✅ 鸿蒙真机开启开发者模式,开发板(DAYU200)连接正常;
  • ✅ 工具:DevEco Studio、VSCode、Git Bash 均正常启动。

🔀 1.2 分支管理(新手友好命令)

# 切换到主分支并拉取最新代码
git checkout main
git pull

# 新建feature分支(规范命名)
git checkout -b feature-exception-handle-performance-optimize

📚 1.3 预习核心API速查

功能 核心API/方法 用途
渲染异常捕获 ErrorBoundary(类组件) 捕获组件渲染异常
同步代码异常 try-catch 捕获缓存/表单/函数异常
网络异常处理 Axios拦截器(interceptors) 统一处理接口请求异常
列表性能优化 FlatList(removeClippedSubviews等) 提升列表渲染流畅度
组件懒加载 React.lazy + Suspense 减少启动加载压力

💡 二、核心知识点讲解(15分钟)

(保留原文档2.1-2.4内容,补充以下关键提醒)

💡 关键提醒:

  1. ErrorBoundary 仅能捕获类组件/函数组件的渲染异常,无法捕获异步代码(如接口请求)、事件处理函数中的异常,这类异常需用try-catch;
  2. 📱 鸿蒙开发板性能较弱,列表渲染数据量建议控制在5条以内,且禁用所有动效;
  3. 🔑 鸿蒙权限配置后需重新编译工程,真机需手动开启应用权限。

🛠️ 三、实操步骤(50分钟,完整可复制代码+关键标注)

🚨 3.1 步骤1:实现应用异常处理(20分钟)

🧩 3.1.1 封装ErrorBoundary异常捕获组件(修复原代码语法错误)

原代码问题:函数组件无法使用static getDerivedStateFromError,需改为类组件

// src/components/ErrorBoundary.js
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';

// 引入自定义Toast组件(补充完整Toast代码,确保依赖存在)
import CustomToast from './CustomToast';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      errorMsg: ''
    };
  }

  // 捕获渲染异常,更新状态(类组件专属静态方法)
  static getDerivedStateFromError(error) {
    return { 
      hasError: true, 
      errorMsg: error.message || '未知渲染异常' 
    };
  }

  // 记录异常日志(可扩展为上传日志)
  componentDidCatch(error, errorInfo) {
    console.error('【RN鸿蒙】渲染异常详情:', {
      error: error.message,
      stack: error.stack,
      info: errorInfo.componentStack,
      time: new Date().toLocaleString()
    });
  }

  // 重置异常状态
  resetError = () => {
    this.setState({ hasError: false, errorMsg: '' });
  };

  render() {
    // 异常状态下显示降级UI
    if (this.state.hasError) {
      return (
        <View style={styles.errorContainer}>
          <Text style={styles.errorTitle}>应用出现异常</Text>
          <Text style={styles.errorMsg}>{this.state.errorMsg}</Text>
          <TouchableOpacity 
            style={styles.resetBtn} 
            onPress={this.resetError}
          >
            <Text style={styles.resetBtnText}>重新加载</Text>
          </TouchableOpacity>
          {/* 鸿蒙多终端适配:Toast提示字体/位置适配 */}
          <CustomToast 
            visible={true} 
            type="error" 
            message={`渲染异常:${this.state.errorMsg}`} 
            onClose={this.resetError} 
            // 鸿蒙开发板适配:增大Toast字体
            style={{ fontSize: 16 }} 
          />
        </View>
      );
    }

    // 正常状态下渲染子组件
    return this.props.children;
  }
}

// 样式(重点标注鸿蒙多终端适配点)
const styles = StyleSheet.create({
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#fff'
  },
  errorTitle: {
    fontSize: 20, // 📱 鸿蒙开发板适配:默认字体增大
    fontWeight: 'bold',
    marginBottom: 10,
    color: '#e53935'
  },
  errorMsg: {
    fontSize: 16, // 📱 鸿蒙开发板适配:字体增大
    color: '#666',
    marginBottom: 20,
    textAlign: 'center'
  },
  resetBtn: {
    backgroundColor: '#1890ff',
    paddingHorizontal: 20,
    paddingVertical: 10,
    borderRadius: 8
  },
  resetBtnText: {
    color: '#fff',
    fontSize: 16 // 📱 鸿蒙开发板适配:按钮字体增大
  }
});

export default ErrorBoundary;
🌐 3.1.2 全局引入ErrorBoundary(修改App.js)
// App.js
import React from 'react';
import { View, StyleSheet } from 'react-native';
import ErrorBoundary from './src/components/ErrorBoundary';
import HomePage from './src/pages/HomePage';
import FormPage from './src/pages/FormPage';

const App = () => {
  return (
    {/* 全局包裹ErrorBoundary,捕获所有页面渲染异常 */}
    <ErrorBoundary>
      <View style={styles.container}>
        <HomePage />
        {/* 其他页面组件 */}
      </View>
    </ErrorBoundary>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

export default App;
📡 3.1.3 网络异常处理(Axios拦截器完整代码)
// src/api/request.js
import axios from 'axios';
import { View, Text } from 'react-native';
import CustomToast from '../components/CustomToast';

// 1. 创建axios实例(关键:定义service,修复原代码缺失问题)
const service = axios.create({
  baseURL: 'http://your-api-base-url.com', // 替换为实际接口地址
  timeout: 10000, // 📱 鸿蒙真机适配:超时时间延长至10s
  headers: {
    'Content-Type': 'application/json'
  }
});

// 2. 请求拦截器(添加加载提示)
service.interceptors.request.use(
  (config) => {
    // 可添加加载中Toast(鸿蒙适配:简化加载提示)
    CustomToast.show({
      visible: true,
      type: 'loading',
      message: '请求中...',
      style: { fontSize: 14 } // 📱 开发板适配
    });
    return config;
  },
  (error) => {
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '请求参数异常',
      style: { fontSize: 14 }
    });
    return Promise.reject(error);
  }
);

// 3. 响应拦截器(统一处理网络异常)
service.interceptors.response.use(
  (response) => {
    // 关闭加载提示
    CustomToast.show({ visible: false });
    return response.data;
  },
  (error) => {
    // 关闭加载提示
    CustomToast.show({ visible: false });

    // 分类处理网络异常
    let errorMsg = '';
    if (!error.response) {
      errorMsg = '网络异常,请检查网络连接'; // 断网/超时
    } else if (error.response.status === 404) {
      errorMsg = '接口不存在,请检查地址';
    } else if (error.response.status === 500) {
      errorMsg = '服务器异常,请稍后重试';
    } else if (error.response.status === 401) {
      errorMsg = '登录失效,请重新登录';
    } else {
      errorMsg = `请求失败:${error.message || '未知错误'}`;
    }

    // 记录异常日志
    console.error('【RN鸿蒙】网络异常:', {
      url: error.config?.url,
      method: error.config?.method,
      status: error.response?.status,
      message: errorMsg,
      time: new Date().toLocaleString()
    });

    // 📱 鸿蒙多终端适配:异常提示样式
    CustomToast.show({
      visible: true,
      type: 'error',
      message: errorMsg,
      style: {
        fontSize: 16, // 📱 开发板字体增大
        padding: 10, // 避免提示框遮挡
        position: 'bottom' // 📱 真机/开发板:底部显示,不遮挡核心操作
      }
    });

    // 📱 鸿蒙适配:添加请求重试(最多2次)
    const { config } = error;
    if (!config || !config.retryCount) {
      config.retryCount = 0;
    }
    if (config.retryCount < 2) {
      config.retryCount += 1;
      const delay = new Promise((resolve) => {
        setTimeout(resolve, 1000); // 延迟1s重试
      });
      return delay.then(() => service(config));
    }

    return Promise.reject(error);
  }
);

export default service;
🗂️ 3.1.4 缓存异常处理(storage.js完整代码)
// src/utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
import CustomToast from '../components/CustomToast';

/**
 * 读取缓存(带异常捕获)
 * @param {string} key 缓存键名
 * @returns {Promise<any>} 缓存值
 */
export const getStorage = async (key) => {
  try {
    if (!key) {
      throw new Error('缓存键名不能为空');
    }
    const value = await AsyncStorage.getItem(key);
    // 解析JSON数据(处理格式异常)
    if (value) {
      if (value.startsWith('{') || value.startsWith('[')) {
        return JSON.parse(value);
      }
      return value;
    }
    return null;
  } catch (err) {
    console.error('【RN鸿蒙】缓存读取失败:', {
      key,
      error: err.message,
      time: new Date().toLocaleString()
    });
    // 📱 鸿蒙适配:异常提示
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '缓存读取失败',
      style: { fontSize: 16 }
    });
    return null;
  }
};

/**
 * 存储缓存(带异常捕获)
 * @param {string} key 缓存键名
 * @param {any} value 缓存值(自动序列化JSON)
 * @param {number} expireTime 过期时间(毫秒,可选)
 * @returns {Promise<boolean>} 是否成功
 */
export const setStorage = async (key, value, expireTime) => {
  try {
    if (!key) {
      throw new Error('缓存键名不能为空');
    }
    // 添加过期时间(鸿蒙适配:便于后续清理)
    const data = expireTime 
      ? { value, expireTime: Date.now() + expireTime } 
      : value;
    const storedValue = typeof data === 'object' 
      ? JSON.stringify(data) 
      : data;
    await AsyncStorage.setItem(key, storedValue);
    return true;
  } catch (err) {
    console.error('【RN鸿蒙】缓存存储失败:', {
      key,
      error: err.message,
      time: new Date().toLocaleString()
    });
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '缓存存储失败',
      style: { fontSize: 16 }
    });
    return false;
  }
};

/**
 * 删除缓存(带异常捕获)
 * @param {string} key 缓存键名
 * @returns {Promise<boolean>} 是否成功
 */
export const removeStorage = async (key) => {
  try {
    if (!key) {
      throw new Error('缓存键名不能为空');
    }
    await AsyncStorage.removeItem(key);
    return true;
  } catch (err) {
    console.error('【RN鸿蒙】缓存删除失败:', {
      key,
      error: err.message,
      time: new Date().toLocaleString()
    });
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '缓存删除失败',
      style: { fontSize: 16 }
    });
    return false;
  }
};

/**
 * 清理过期缓存(鸿蒙性能优化核心)
 * @returns {Promise<boolean>} 是否成功
 */
export const clearExpiredStorage = async () => {
  try {
    const keys = await AsyncStorage.getAllKeys();
    const now = Date.now();
    let clearedCount = 0;

    for (const key of keys) {
      const value = await getStorage(key);
      // 仅清理带过期时间且已过期的缓存
      if (value?.expireTime && value.expireTime < now) {
        await removeStorage(key);
        clearedCount++;
      }
    }

    console.log(`【RN鸿蒙】清理过期缓存完成,共清理${clearedCount}条`);
    CustomToast.show({
      visible: true,
      type: 'success',
      message: `清理${clearedCount}条过期缓存`,
      style: { fontSize: 16 }
    });
    return true;
  } catch (err) {
    console.error('【RN鸿蒙】清理过期缓存失败:', err.message);
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '缓存清理失败',
      style: { fontSize: 16 }
    });
    return false;
  }
};

/**
 * 清空所有缓存(谨慎使用)
 * @returns {Promise<boolean>} 是否成功
 */
export const clearAllStorage = async () => {
  try {
    await AsyncStorage.clear();
    console.log('【RN鸿蒙】所有缓存已清空');
    CustomToast.show({
      visible: true,
      type: 'success',
      message: '所有缓存已清空',
      style: { fontSize: 16 }
    });
    return true;
  } catch (err) {
    console.error('【RN鸿蒙】清空缓存失败:', err.message);
    CustomToast.show({
      visible: true,
      type: 'error',
      message: '清空缓存失败',
      style: { fontSize: 16 }
    });
    return false;
  }
};
📝 3.1.5 代码错误处理(表单提交示例)
// src/pages/FormPage.js
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { setStorage } from '../utils/storage';
import service from '../api/request';
import CustomToast from '../components/CustomToast';

const FormPage = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');

  // 表单验证
  const validateForm = () => {
    if (!name) {
      CustomToast.show({ visible: true, type: 'warning', message: '姓名不能为空' });
      return false;
    }
    if (!email.includes('@')) {
      CustomToast.show({ visible: true, type: 'warning', message: '邮箱格式错误' });
      return false;
    }
    return true;
  };

  // 表单提交(添加try-catch捕获异常)
  const handleSubmit = async () => {
    try {
      const isValid = validateForm();
      if (!isValid) return;

      // 1. 提交接口
      const res = await service.post('/user/add', { name, email, phone });
      if (res.code === 200) {
        // 2. 存储缓存(过期时间1小时)
        await setStorage(`user_${res.data.id}`, res.data, 3600000);
        CustomToast.show({
          visible: true,
          type: 'success',
          message: '提交成功'
        });
        // 重置表单
        setName('');
        setEmail('');
        setPhone('');
      }
    } catch (err) {
      // 捕获所有代码/接口/缓存异常
      console.error('【RN鸿蒙】表单提交异常:', err.message);
      CustomToast.show({
        visible: true,
        type: 'error',
        message: `提交失败:${err.message}`,
        style: { fontSize: 16 } // 📱 鸿蒙适配
      });
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="请输入姓名"
        value={name}
        onChangeText={setName}
        placeholderTextColor="#999"
      />
      <TextInput
        style={styles.input}
        placeholder="请输入邮箱"
        value={email}
        onChangeText={setEmail}
        placeholderTextColor="#999"
        keyboardType="email-address"
      />
      <TextInput
        style={styles.input}
        placeholder="请输入电话"
        value={phone}
        onChangeText={setPhone}
        placeholderTextColor="#999"
        keyboardType="phone-pad"
      />
      <TouchableOpacity style={styles.submitBtn} onPress={handleSubmit}>
        <Text style={styles.submitBtnText}>提交</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#fff'
  },
  input: {
    height: 50, // 📱 鸿蒙开发板适配:增大输入框高度
    borderWidth: 1,
    borderColor: '#eee',
    borderRadius: 8,
    paddingHorizontal: 15,
    marginBottom: 15,
    fontSize: 16 // 📱 开发板适配:增大字体
  },
  submitBtn: {
    backgroundColor: '#1890ff',
    height: 50, // 📱 开发板适配:增大按钮高度
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 10
  },
  submitBtnText: {
    color: '#fff',
    fontSize: 18 // 📱 开发板适配:增大按钮字体
  }
});

export default FormPage;
✅ 3.1.6 异常处理验证步骤
  1. 🚨 故意修改列表组件代码(如undefinedVariable),确认ErrorBoundary捕获并显示降级UI;
  2. 📡 断开网络,提交表单,确认网络异常提示正常;
  3. 🗂️ 手动修改缓存数据(如存入非JSON字符串),调用getStorage,确认缓存异常提示正常;
  4. 📱 多终端验证:开发板/真机/模拟器的异常提示样式、位置均正常。

⚡ 3.2 步骤2:核心性能优化实战(15分钟)

📜 3.2.1 列表性能优化(FlatList完整代码)
// src/pages/HomePage.js
import React, { useState, useEffect, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Dimensions } from 'react-native';
import { getStorage, clearExpiredStorage } from '../utils/storage';
import service from '../api/request';
import CustomToast from '../components/CustomToast';

// 1. 提取列表项组件(React.memo避免重渲染)
const ListItem = React.memo(({ item }) => {
  // 📱 鸿蒙开发板适配:简化列表项样式,删除冗余元素
  return (
    <View style={styles.listItem}>
      <Text style={styles.listName}>{item.name}</Text>
      {/* 📱 开发板适配:仅保留核心信息,减少渲染压力 */}
      <Text style={styles.listInfo}>邮箱:{item.email}</Text>
    </View>
  );
});

// 2. 获取设备信息(鸿蒙多终端适配)
const { width, height } = Dimensions.get('window');
const isTablet = width > 768; // 📱 平板判断
const isDevBoard = height < 800; // 📱 开发板判断(DAYU200屏幕较小)

const HomePage = () => {
  const [listData, setListData] = useState([]);
  const [refreshing, setRefreshing] = useState(false);

  // 3. 防抖处理:避免频繁刷新(性能优化核心)
  const loadListData = useCallback(async (isRefresh = false) => {
    try {
      setRefreshing(true);
      // 📱 开发板适配:限制数据量为5条
      const limit = isDevBoard ? 5 : 10;
      // 优先读取缓存,缓存无数据则请求接口
      const cacheData = await getStorage('user_list');
      if (cacheData && !isRefresh) {
        setListData(cacheData.slice(0, limit));
        setRefreshing(false);
        return;
      }
      // 接口请求
      const res = await service.get('/user/list', { params: { limit } });
      setListData(res.data);
      // 缓存数据(过期时间30分钟)
      await setStorage('user_list', res.data, 1800000);
    } catch (err) {
      console.error('【RN鸿蒙】列表加载异常:', err.message);
      CustomToast.show({
        visible: true,
        type: 'error',
        message: '列表加载失败'
      });
    } finally {
      setRefreshing(false);
    }
  }, []);

  // 4. 启动优化:延迟加载列表数据(避免阻塞启动)
  useEffect(() => {
    // 清理过期缓存(性能优化:减少内存占用)
    clearExpiredStorage();
    // 延迟100ms加载,提升启动速度
    const timer = setTimeout(() => loadListData(), 100);
    // 组件卸载时清除定时器(避免内存泄漏)
    return () => clearTimeout(timer);
  }, [loadListData]);

  // 5. 渲染列表项(平板双列布局适配)
  const renderListItem = ({ item }) => {
    if (isTablet) {
      // 📱 平板双列布局
      return (
        <View style={styles.tabletListItem}>
          <ListItem item={item} />
        </View>
      );
    }
    return <ListItem item={item} />;
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>用户列表</Text>
      {/* FlatList性能优化核心配置 */}
      <FlatList
        data={listData}
        renderItem={renderListItem}
        keyExtractor={(item) => item.id.toString()}
        refreshing={refreshing}
        onRefresh={() => loadListData(true)}
        // ⚡ 性能优化:开启列表项复用(鸿蒙核心优化)
        removeClippedSubviews={true}
        // 📱 开发板适配:每次仅渲染5条
        maxToRenderPerBatch={isDevBoard ? 5 : 8}
        // 可视区域上下各渲染3屏(平衡流畅度与性能)
        windowSize={3}
        // 📱 禁用滚动动画(开发板适配)
        scrollEnabled={!isDevBoard || listData.length > 5}
        // 📱 平板适配:设置列数
        numColumns={isTablet ? 2 : 1}
        // 📱 开发板适配:禁用下拉刷新动画
        refreshControl={
          <View style={styles.refreshView}>
            <Text>{refreshing ? '正在刷新...' : ''}</Text>
          </View>
        }
        // 空数据提示
        ListEmptyComponent={() => (
          <Text style={styles.emptyText}>暂无数据</Text>
        )}
      />
      {/* 🗂️ 手动清理缓存按钮(性能优化扩展) */}
      <TouchableOpacity style={styles.clearCacheBtn} onPress={clearExpiredStorage}>
        <Text style={styles.clearCacheBtnText}>清理过期缓存</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
    backgroundColor: '#fff'
  },
  title: {
    fontSize: isDevBoard ? 20 : 18, // 📱 开发板字体增大
    fontWeight: 'bold',
    marginBottom: 10,
    textAlign: 'center'
  },
  listItem: {
    height: isDevBoard ? 60 : 50, // 📱 开发板增大列表项高度(触控适配)
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
    marginHorizontal: 5,
    justifyContent: 'center'
  },
  listName: {
    fontSize: isDevBoard ? 18 : 16, // 📱 开发板字体增大
    fontWeight: 'bold'
  },
  listInfo: {
    fontSize: isDevBoard ? 14 : 12,
    color: '#666',
    marginTop: 2
  },
  // 📱 平板双列布局样式
  tabletListItem: {
    flex: 1,
    margin: 5
  },
  refreshView: {
    height: 40,
    justifyContent: 'center',
    alignItems: 'center'
  },
  emptyText: {
    textAlign: 'center',
    marginTop: 20,
    color: '#999',
    fontSize: 16
  },
  clearCacheBtn: {
    backgroundColor: '#f5f5f5',
    height: 50,
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 10
  },
  clearCacheBtnText: {
    color: '#1890ff',
    fontSize: 16
  }
});

export default HomePage;
🧩 3.2.2 组件懒加载(App.js修改)
// App.js
import React, { lazy, Suspense } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import ErrorBoundary from './src/components/ErrorBoundary';
import HomePage from './src/pages/HomePage';

// ⚡ 懒加载非核心组件(性能优化:启动时不加载)
const FormPage = lazy(() => import('./src/pages/FormPage'));
const SettingPage = lazy(() => import('./src/pages/SettingPage'));
const DetailPage = lazy(() => import('./src/pages/DetailPage'));

const App = () => {
  return (
    <ErrorBoundary>
      <View style={styles.container}>
        <HomePage />
        {/* 懒加载组件包裹Suspense,添加加载占位符 */}
        <Suspense 
          fallback={
            <View style={styles.suspenseFallback}>
              <ActivityIndicator size="large" color="#1890ff" />
              <Text style={styles.loadingText}>页面加载中...</Text>
            </View>
          }
        >
          <FormPage />
          {/* <SettingPage />
          <DetailPage /> */}
        </Suspense>
      </View>
    </ErrorBoundary>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  suspenseFallback: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666'
  }
});

export default App;
✅ 3.2.3 性能优化验证步骤
  1. ⚡ 运行工程,观察启动时间(优化后应<2s);
  2. 📜 滑动列表,确认无卡顿、掉帧(开发板需重点验证);
  3. 🗂️ 调用“清理过期缓存”按钮,查看控制台日志,确认缓存清理成功;
  4. 🔍 检查React DevTools(需安装),确认列表项无不必要的重渲染。

📱 3.3 步骤3:鸿蒙多终端性能适配与测试(10分钟)

🖥️ 3.3.1 开发板适配关键修改(已在上述代码中标注)
  1. 📜 简化列表项:仅保留核心信息,删除冗余文本/样式;
  2. 🖲️ 增大触控区域:列表项高度≥60px,按钮高度≥50px,字体≥16px;
  3. 📊 限制数据量:列表仅渲染5条数据;
  4. 🎬 禁用动效:下拉刷新替换为文本提示,禁用滚动动画;
  5. 🧹 内存优化:启动时清理过期缓存,限制缓存数据量。
📱 3.3.2 真机/平板适配关键修改
  1. 📱 平板双列布局:通过Dimensions判断屏幕宽度,设置numColumns={2}
  2. 📡 网络适配:Axios超时时间延长至10s,添加2次重试机制;
  3. 🔑 权限适配:鸿蒙真机需手动开启“存储/网络”权限;
  4. 🎨 布局适配:平板端调整间距,避免内容溢出。
✅ 3.3.3 多终端测试用例
终端类型 测试重点 验收标准
🖥️ 模拟器 功能完整性、异常提示 所有功能正常,异常提示样式正确
📱 鸿蒙真机 启动速度、列表流畅度、网络 启动<2s,列表滑动无卡顿,网络请求稳定
🖥️ DAYU200开发板 触控体验、内存占用、稳定性 触控灵敏,内存占用<100MB,运行10分钟无崩溃

📦 3.4 步骤4:打包部署前置准备(5分钟)

⚙️ 3.4.1 工程配置检查
  1. package.json检查
{
  "name": "rnHarmonyDemo",
  "version": "1.0.0",
  "main": "index.js", // 🎯 入口文件必须正确
  "scripts": {
    "start": "react-native start",
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "harmony": "react-native run-harmony" // 📱 鸿蒙运行命令
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.19.3",
    "axios": "^1.5.1",
    "react": "18.2.0",
    "react-native": "0.72.7"
  }
}
  1. app.json检查
{
  "name": "rnHarmonyDemo",
  "displayName": "RN鸿蒙示例",
  "version": "1.0.0",
  "permissions": [
    "internet",
    "storage"
  ]
}
🧹 3.4.2 依赖清理(命令行)
# 清理未使用的依赖
npm prune

# 清理缓存
npm cache clean --force

# 删除临时文件
rm -rf node_modules/.cache
rm -rf android/build
rm -rf harmony/build
🔑 3.4.3 鸿蒙权限配置(config.json)

路径:harmony/app/src/main/config.json

{
  "app": {
    "bundleName": "com.example.rnharmonydemo",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0"
  },
  "module": {
    "package": "com.example.rnharmonydemo",
    "name": ".MyApplication",
    "mainAbility": ".MainAbility",
    "deviceType": [
      "phone",
      "tablet",
      "tv",
      "wearable",
      "liteWearable",
      "smartVision"
    ],
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET", // 📡 网络权限
        "reason": "用于接口请求",
        "usedScene": {
          "abilities": [".MainAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE", // 🗂️ 存储写权限
        "reason": "用于缓存数据存储",
        "usedScene": {
          "abilities": [".MainAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.READ_USER_STORAGE", // 🗂️ 存储读权限
        "reason": "用于缓存数据读取",
        "usedScene": {
          "abilities": [".MainAbility"],
          "when": "always"
        }
      }
    ],
    "abilities": [
      {
        "name": ".MainAbility",
        "srcPath": "MainAbility",
        "description": "$string:mainability_description",
        "icon": "$media:icon",
        "label": "$string:app_name",
        "type": "page",
        "launchType": "standard"
      }
    ]
  }
}
✅ 3.4.4 前置准备验证
  1. 📦 运行npm install,确认无依赖冲突;
  2. 📱 运行react-native run-harmony,确认应用正常启动,无权限报错;
  3. 🧹 检查工程目录,确认test目录、临时文件已删除。

❌ 四、常见问题与解决方案(10分钟)

问题现象 原因分析 解决方案
🚨 ErrorBoundary无法捕获异常 1. 函数组件使用static方法;2. 异步代码异常 1. 改为类组件;2. 异步代码添加try-catch
📜 开发板列表仍卡顿 1. 数据量过大;2. 样式复杂 1. 限制数据量≤5条;2. 删除所有动效/冗余样式
🗂️ 缓存清理无效 缓存数据无expireTime字段 存储时添加expireTime,格式为时间戳
📱 真机提示“权限不足” 1. config.json未配置;2. 未手动开启 1. 检查权限配置;2. 真机设置→应用→当前应用→手动开启存储/网络权限
⚡ 应用启动缓慢 1. 启动时同步加载组件;2. 依赖冗余 1. 非核心组件懒加载;2. 延迟加载列表数据;3. 清理冗余依赖

📝 五、课堂小结(5分钟)

🎯 核心要点回顾

  1. 异常处理 🚨:通过ErrorBoundary捕获渲染异常、try-catch捕获同步异常、Axios拦截器捕获网络异常,核心是“捕获-提示-降级-日志”,适配鸿蒙多终端样式;
  2. 性能优化 ⚡:列表优化(removeClippedSubviews/memo)、组件懒加载、缓存清理是核心,开发板需“简化+降级”适配;
  3. 打包前置 📦:重点检查工程配置、清理依赖、配置鸿蒙权限,确保打包无冲突。

✅ 六、课后任务(必做)

  1. 🛠️ 独立完成异常处理+性能优化全流程,验证多终端运行效果;
  2. 🚀 扩展功能:
    • 📝 新增异常日志本地存储(写入文件);
    • ⚡ 列表上拉加载更多添加防抖(延迟500ms);
    • 🗂️ 设置页添加“手动清理所有缓存”按钮;
  3. 📱 完善适配:
    • 🖥️ 开发板添加“加载中”文本提示(无动效);
    • 📱 平板端优化双列布局间距与样式;
  4. 📚 预习:RN鸿蒙应用打包流程、签名配置相关知识。

🚀 实操遇到问题可留言,下一节课将讲解应用打包部署,完成从0到1的开发闭环!

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

Logo

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

更多推荐