鸿蒙应用开发实践:网络数据列表构建完整方案

# 鸿蒙应用开发实践:网络数据列表构建完整方案


在网络应用开发中,数据列表展示是基础且关键的场景。基于鸿蒙平台的网络数据列表应用开发,需要综合考虑网络请求、数据处理、性能优化等多个方面。本文将提供完整的实战解决方案。


## 架构设计与网络层实现


### 网络请求管理层


```jsx

import { http } from '@ohos/net.http';

import { Preferences } from '@ohos/data.preferences';

import { BusinessError } from '@ohos.base';


// 网络请求配置类

class NetworkConfig {

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

  static TIMEOUT = 30000;

  static RETRY_COUNT = 3;

  static RETRY_DELAY = 1000;

}


// 请求拦截器

class RequestInterceptor {

  static async intercept(request) {

    // 添加公共请求头

    const headers = {

      'Content-Type': 'application/json',

      'Accept': 'application/json',

      'User-Agent': 'HarmonyOS-App/1.0',

      ...request.headers

    };


    // 添加认证令牌

    const token = await this.getAuthToken();

    if (token) {

      headers['Authorization'] = `Bearer ${token}`;

    }


    return { ...request, headers };

  }


  static async getAuthToken() {

    try {

      const prefs = await Preferences.getPreferences('auth_store');

      return await prefs.get('access_token', '');

    } catch (error) {

      console.warn('获取认证令牌失败:', error);

      return null;

    }

  }

}


// 响应拦截器

class ResponseInterceptor {

  static async intercept(response) {

    if (response.responseCode === 401) {

      // Token过期,尝试刷新

      const refreshed = await this.refreshToken();

      if (refreshed) {

        throw new Error('RETRY_WITH_NEW_TOKEN');

      }

    }


    if (response.responseCode >= 400) {

      const error = new Error(this.getErrorMessage(response.responseCode));

      error.code = response.responseCode;

      error.data = response.result;

      throw error;

    }


    return this.parseResponse(response);

  }


  static parseResponse(response) {

    try {

      const result = JSON.parse(response.result);

      return {

        data: result.data,

        pagination: result.pagination,

        code: result.code,

        message: result.message

      };

    } catch (error) {

      return { data: response.result };

    }

  }

}


// 网络请求核心类

class NetworkService {

  constructor() {

    this.http = http.createHttp();

    this.cache = new Map();

    this.pendingRequests = new Map();

  }


  async request(config) {

    const {

      url,

      method = 'GET',

      data = null,

      params = {},

      headers = {},

      cacheKey = null,

      useCache = true,

      retry = NetworkConfig.RETRY_COUNT

    } = config;


    // 构造完整URL

    const fullUrl = this.buildUrl(url, params);

    const requestKey = `${method}:${fullUrl}`;


    // 检查缓存

    if (useCache && cacheKey) {

      const cached = this.cache.get(cacheKey);

      if (cached && !this.isCacheExpired(cached.timestamp)) {

        return cached.data;

      }

    }


    // 合并重复请求

    if (this.pendingRequests.has(requestKey)) {

      return this.pendingRequests.get(requestKey);

    }


    const requestPromise = this.executeRequest({

      url: fullUrl,

      method,

      data,

      headers,

      retry

    });


    this.pendingRequests.set(requestKey, requestPromise);


    try {

      // 拦截请求

      const interceptedRequest = await RequestInterceptor.intercept({

        url: fullUrl,

        method,

        data,

        headers

      });


      const options = {

        method: interceptedRequest.method,

        header: interceptedRequest.headers,

        connectTimeout: NetworkConfig.TIMEOUT,

        readTimeout: NetworkConfig.TIMEOUT

      };


      if (interceptedRequest.data) {

        options.extraData = JSON.stringify(interceptedRequest.data);

      }


      const response = await this.http.request(fullUrl, options);


      // 拦截响应

      const parsedResponse = await ResponseInterceptor.intercept(response);


      // 缓存响应

      if (useCache && cacheKey) {

        this.cache.set(cacheKey, {

          data: parsedResponse,

          timestamp: Date.now()

        });

      }


      return parsedResponse;

    } catch (error) {

      if (error.message === 'RETRY_WITH_NEW_TOKEN') {

        // 使用新token重试请求

        return this.request({ ...config, retry: retry - 1 });

      }


      throw error;

    } finally {

      this.pendingRequests.delete(requestKey);

    }

  }


  async executeRequest(requestConfig, retryCount = 0) {

    try {

      return await this.makeHttpRequest(requestConfig);

    } catch (error) {

      if (retryCount < requestConfig.retry) {

        await this.delay(NetworkConfig.RETRY_DELAY * Math.pow(2, retryCount));

        return this.executeRequest(requestConfig, retryCount + 1);

      }

      throw error;

    }

  }


  buildUrl(path, params) {

    let url = path.startsWith('http') ? path : `${NetworkConfig.BASE_URL}${path}`;

    

    if (Object.keys(params).length > 0) {

      const queryString = new URLSearchParams(params).toString();

      url += `?${queryString}`;

    }

    

    return url;

  }


  isCacheExpired(timestamp, ttl = 5 * 60 * 1000) {

    return Date.now() - timestamp > ttl;

  }


  delay(ms) {

    return new Promise(resolve => setTimeout(resolve, ms));

  }


  clearCache(key = null) {

    if (key) {

      this.cache.delete(key);

    } else {

      this.cache.clear();

    }

  }

}


// 使用示例

const networkService = new NetworkService();


export const fetchProductList = async (page = 1, limit = 20) => {

  return networkService.request({

    url: '/api/products',

    method: 'GET',

    params: { page, limit },

    cacheKey: `products_${page}_${limit}`,

    useCache: page === 1 // 仅缓存第一页

  });

};

```


## 数据列表组件实现


### 核心列表组件


```jsx

import React, { useState, useEffect, useCallback, useRef } from 'react';

import { View, FlatList, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';

import { NetworkService, fetchProductList } from '../services/NetworkService';


const DataListApp = () => {

  const [state, setState] = useState({

    data: [],

    loading: false,

    refreshing: false,

    loadingMore: false,

    error: null,

    page: 1,

    hasMore: true,

    total: 0

  });


  const networkServiceRef = useRef(new NetworkService());

  const flatListRef = useRef(null);


  // 初始化加载

  useEffect(() => {

    loadInitialData();

  }, []);


  // 监听网络状态变化

  useEffect(() => {

    const handleNetworkChange = (status) => {

      if (!status.isConnected && state.data.length === 0) {

        setState(prev => ({ ...prev, error: '网络连接不可用' }));

      }

    };


    // 鸿蒙网络状态监听

    if (typeof ohos !== 'undefined' && ohos.net.connection) {

      ohos.net.connection.on('change', handleNetworkChange);

    }


    return () => {

      if (typeof ohos !== 'undefined' && ohos.net.connection) {

        ohos.net.connection.off('change', handleNetworkChange);

      }

    };

  }, [state.data.length]);


  // 加载初始数据

  const loadInitialData = useCallback(async () => {

    if (state.loading) return;


    setState(prev => ({ ...prev, loading: true, error: null }));


    try {

      const result = await fetchProductList(1, 20);

      

      setState(prev => ({

        ...prev,

        data: result.data || [],

        total: result.pagination?.total || 0,

        page: 1,

        hasMore: result.pagination?.hasMore || false,

        loading: false

      }));


      // 鸿蒙平台触感反馈

      if (typeof ohos !== 'undefined' && ohos.vibrator) {

        ohos.vibrator.vibrate({ duration: 10 });

      }

    } catch (error) {

      console.error('加载数据失败:', error);

      setState(prev => ({

        ...prev,

        error: error.message || '加载失败',

        loading: false

      }));

    }

  }, [state.loading]);


  // 下拉刷新

  const handleRefresh = useCallback(async () => {

    if (state.refreshing) return;


    setState(prev => ({ ...prev, refreshing: true, error: null }));


    try {

      const result = await fetchProductList(1, 20);

      

      setState(prev => ({

        ...prev,

        data: result.data || [],

        page: 1,

        hasMore: result.pagination?.hasMore || false,

        refreshing: false

      }));

    } catch (error) {

      setState(prev => ({

        ...prev,

        error: error.message,

        refreshing: false

      }));

    }

  }, [state.refreshing]);


  // 加载更多

  const handleLoadMore = useCallback(async () => {

    if (!state.hasMore || state.loadingMore || state.refreshing) return;


    setState(prev => ({ ...prev, loadingMore: true }));


    try {

      const nextPage = state.page + 1;

      const result = await fetchProductList(nextPage, 20);

      

      setState(prev => ({

        ...prev,

        data: [...prev.data, ...(result.data || [])],

        page: nextPage,

        hasMore: result.pagination?.hasMore || false,

        loadingMore: false

      }));

    } catch (error) {

      setState(prev => ({

        ...prev,

        error: error.message,

        loadingMore: false

      }));

    }

  }, [state.hasMore, state.loadingMore, state.refreshing, state.page]);


  // 渲染列表项

  const renderItem = useCallback(({ item, index }) => (

   

      item={item} 

      index={index}

      => handleItemPress(item)}

    />

  ), []);


  // 空状态

  const renderEmpty = useCallback(() => (

   

      {state.error ? (

        <>

          {state.error}

         

            style={styles.retryButton}

            >

          >

            重试

         

       

      ) : (

        暂无数据

      )}

   

  ), [state.error, loadInitialData]);

<"p5.j9k5.org.cn"><"m1.j9k5.org.cn"><"a8.j9k5.org.cn">

  // 底部加载更多

  const renderFooter = useCallback(() => {

    if (!state.hasMore && state.data.length > 0) {

      return (

       

          没有更多数据了

       

      );

    }


    if (state.loadingMore) {

      return (

       

         

          加载中...

       

      );

    }


    return null;

  }, [state.hasMore, state.loadingMore, state.data.length]);


  // 错误边界

  if (state.error && state.data.length === 0) {

    return (

     

        加载失败

       

          style={styles.errorButton}

          >

        >

          重新加载

       

     

    );

  }


  return (

   

      {/* 顶部统计信息 */}

      {state.total > 0 && (

       

         

            共 {state.total} 条数据

         

       

      )}


      {/* 主列表 */}

     

        ref={flatListRef}

        data={state.data}

        renderItem={renderItem}

        keyExtractor={item => `item_${item.id}`}

        ListEmptyComponent={renderEmpty}

        ListFooterComponent={renderFooter}

        refreshing={state.refreshing}

        >

        >

        >

        scrollEventThrottle={16}

        initialNumToRender={10}

        maxToRenderPerBatch={10}

        windowSize={5}

        removeClippedSubviews={true}

        contentContainerStyle={state.data.length === 0 ? styles.emptyContent : null}

      />


      {/* 全局加载指示器 */}

      {state.loading && (

       

         

       

      )}

   

  );

};


const styles = StyleSheet.create({

  container: {

    flex: 1,

    backgroundColor: '#F5F5F5'

  },

  header: {

    padding: 12,

    backgroundColor: '#FFFFFF',

    borderBottomWidth: 1,

    borderBottomColor: '#E0E0E0'

  },

  headerText: {

    fontSize: 14,

    color: '#666666'

  },

  emptyContainer: {

    flex: 1,

    alignItems: 'center',

    justifyContent: 'center',

    paddingVertical: 60

  },

  emptyText: {

    fontSize: 16,

    color: '#999999',

    textAlign: 'center',

    marginBottom: 12

  },

  retryButton: {

    paddingHorizontal: 20,

    paddingVertical: 10,

    backgroundColor: '#007AFF',

    borderRadius: 6

  },

  retryButtonText: {

    color: '#FFFFFF',

    fontSize: 14,

    fontWeight: '500'

  },

  footer: {

    paddingVertical: 20,

    alignItems: 'center',

    justifyContent: 'center',

    flexDirection: 'row'

  },

  footerText: {

    fontSize: 14,

    color: '#666666',

    marginLeft: 8

  },

  errorContainer: {

    flex: 1,

    alignItems: 'center',

    justifyContent: 'center',

    backgroundColor: '#F5F5F5'

  },

  errorText: {

    fontSize: 18,

    color: '#FF3B30',

    marginBottom: 16

  },

  errorButton: {

    paddingHorizontal: 24,

    paddingVertical: 12,

    backgroundColor: '#007AFF',

    borderRadius: 8

  },

  errorButtonText: {

    color: '#FFFFFF',

    fontSize: 16,

    fontWeight: '600'

  },

  loadingOverlay: {

    ...StyleSheet.absoluteFillObject,

    backgroundColor: 'rgba(255, 255, 255, 0.8)',

    alignItems: 'center',

    justifyContent: 'center',

    zIndex: 1000

  },

  emptyContent: {

    flexGrow: 1

  }

});


export default DataListApp;

```


## 数据缓存与离线支持


### 本地数据存储


```jsx

import { Preferences } from '@ohos/data.preferences';


// 数据存储管理器

class DataStorage {

  static instance = null;

  

  static getInstance() {

    if (!DataStorage.instance) {

      DataStorage.instance = new DataStorage();

    }

    return DataStorage.instance;

  }

  

  constructor() {

    this.stores = new Map();

  }

  

  async getStore(name) {

    if (!this.stores.has(name)) {

      try {

        const store = await Preferences.getPreferences(name);

        this.stores.set(name, store);

        return store;

      } catch (error) {

        console.error(`创建存储 ${name} 失败:`, error);

        throw error;

      }

    }

    return this.stores.get(name);

  }

  

  // 存储列表数据

  async saveListData(key, data, options = {}) {

    try {

      const store = await this.getStore('list_cache');

      const cacheData = {

        data,

        timestamp: Date.now(),

        ...options

      };

      

      await store.put(key, JSON.stringify(cacheData));

      await store.flush();

      

      console.log(`数据已缓存: ${key}`);

    } catch (error) {

      console.error(`保存缓存失败 ${key}:`, error);

    }

  }

  

  // 获取缓存的列表数据

  async getListData(key, maxAge = 5 * 60 * 1000) {

    try {

      const store = await this.getStore('list_cache');

      const cached = await store.get(key, '');

      

      if (!cached) return null;

      

      const cacheData = JSON.parse(cached);

      const isExpired = Date.now() - cacheData.timestamp > maxAge;

      

      if (isExpired) {

        await this.remove(key);

        return null;

      }

      

      return cacheData.data;

    } catch (error) {

      console.error(`读取缓存失败 ${key}:`, error);

      return null;

    }

  }

  

  // 清理过期数据

  async cleanupExpiredData() {

    try {

      const store = await this.getStore('list_cache');

      const allKeys = await store.getAllKeys();

      const now = Date.now();

      

      for (const key of allKeys) {

        const value = await store.get(key, '');

        if (value) {

          const cacheData = JSON.parse(value);

          if (now - cacheData.timestamp > 24 * 60 * 60 * 1000) {

            await store.delete(key);

          }

        }

      }

      

      await store.flush();

    } catch (error) {

      console.error('清理过期数据失败:', error);

    }

  }

}


// 离线支持的数据获取器

class OfflineDataFetcher {

  constructor(storage) {

    this.storage = storage || DataStorage.getInstance();

  }

  

  async fetchWithOfflineSupport(url, params, options = {}) {

    const cacheKey = this.generateCacheKey(url, params);

    

    // 首先尝试从缓存获取

    if (options.useCache !== false) {

      const cachedData = await this.storage.getListData(cacheKey);

      if (cachedData) {

        console.log('使用缓存数据:', cacheKey);

        return { 

          data: cachedData, 

          fromCache: true,

          timestamp: Date.now()

        };

      }

    }

    

    try {

      // 从网络获取

      const networkData = await this.fetchFromNetwork(url, params);

      

      // 存储到缓存

      if (options.shouldCache !== false) {

        await this.storage.saveListData(cacheKey, networkData, {

          url,

          params,

          cachedAt: new Date().toISOString()

        });

      }

      

      return { 

        data: networkData, 

        fromCache: false,

        timestamp: Date.now()

      };

    } catch (error) {

      // 网络失败时,如果允许使用过期的缓存,则返回缓存

      if (options.allowStaleCache) {

        const staleData = await this.storage.getListData(cacheKey, 24 * 60 * 60 * 1000);

        if (staleData) {

          console.warn('网络失败,使用过期缓存:', cacheKey);

          return { 

            data: staleData, 

            fromCache: true,

            isStale: true,

            timestamp: Date.now()

          };

        }

      }

      <"d0.j9k5.org.cn"><"h4.j9k5.org.cn"><"v6.j9k5.org.cn">

      throw error;

    }

  }

  

  generateCacheKey(url, params) {

    const paramString = JSON.stringify(params);

    return `${url}_${paramString}`.replace(/[^a-zA-Z0-9]/g, '_');

  }

}


// 在列表组件中使用

const OfflineSupportedList = () => {

  const [data, setData] = useState([]);

  const [isOffline, setIsOffline] = useState(false);

  

  const fetcherRef = useRef(new OfflineDataFetcher());

  

  const loadData = useCallback(async () => {

    try {

      const result = await fetcherRef.current.fetchWithOfflineSupport(

        '/api/products',

        { page: 1, limit: 20 },

        {

          useCache: true,

          shouldCache: true,

          allowStaleCache: true

        }

      );

      

      setData(result.data);

      setIsOffline(result.fromCache);

      

      if (result.fromCache) {

        // 在后台更新数据

        updateDataInBackground();

      }

    } catch (error) {

      console.error('加载数据失败:', error);

    }

  }, []);

  

  const updateDataInBackground = async () => {

    try {

      const freshData = await fetchProductList(1, 20);

      await fetcherRef.current.storage.saveListData(

        fetcherRef.current.generateCacheKey('/api/products', { page: 1, limit: 20 }),

        freshData

      );

      

      // 如果当前正在显示缓存数据,则静默更新

      setData(freshData);

      setIsOffline(false);

    } catch (error) {

      console.warn('后台更新失败:', error);

    }

  };

  

  return (

   

      {isOffline && (

       

          当前处于离线模式

       

      )}

      

     

        data={data}

        // ... 其他属性

      />

   

  );

};

```


## 性能优化与监控


### 虚拟化列表优化


```jsx

// 高性能列表项渲染

const OptimizedListItem = React.memo(({ item, onPress }) => {

  const [imageLoaded, setImageLoaded] = useState(false);

  

  const handlePress = useCallback(() => {

    onPress(item);

  }, [item, onPress]);

  

  return (

   

      style={styles.listItem}

      >

      activeOpacity={0.7}

    >

      {/* 异步图片加载 */}

     

        {!imageLoaded && (

         

           

         

        )}

       

          source={{ uri: item.image }}

          style={[

            styles.image,

            { opacity: imageLoaded ? 1 : 0 }

          ]}

          => setImageLoaded(true)}

          => setImageLoaded(false)}

          resizeMode="cover"

        />

     

      

     

       

          style={styles.title}

          numberOfLines={2}

          ellipsizeMode="tail"

        >

          {item.name}

       

        

       

          style={styles.description}

          numberOfLines={3}

          ellipsizeMode="tail"

        >

          {item.description}

       

        

       

          ¥{item.price}

         

            库存: {item.stock}件

         

       

     

   

  );

}, (prevProps, nextProps) => {

  // 自定义比较函数,避免不必要的重新渲染

  return prevProps.item.id === nextProps.item.id &&

         prevProps.item.name === nextProps.item.name &&

         prevProps.item.price === nextProps.item.price &&

         prevProps.item.stock === nextProps.item.stock;

});


// 性能监控组件

const PerformanceMonitor = ({ componentName }) => {

  const renderCount = useRef(0);

  const lastRenderTime = useRef(performance.now());

  

  useEffect(() => {

    renderCount.current += 1;

    const currentTime = performance.now();

    const renderTime = currentTime - lastRenderTime.current;

    

    if (renderTime > 16) { // 超过一帧时间

      console.warn(`组件 ${componentName} 渲染耗时: ${renderTime.toFixed(2)}ms`);

    }

    

    lastRenderTime.current = currentTime;

    

    // 鸿蒙性能监控上报

    if (typeof ohos !== 'undefined' && ohos.hiviewdfx.hiAppEvent) {

      ohos.hiviewdfx.hiAppEvent.write({

        domain: 'performance',

        name: 'component_render',

        params: {

          component: componentName,

          renderTime,

          renderCount: renderCount.current

        }

      });

    }

  });

  

  return null; // 这是一个无UI的监控组件

};


// 在列表项中使用

const MonitoredListItem = (props) => {

  return (

    <>

     

     

   

  );

};

```


## 总结与最佳实践


基于鸿蒙平台的网络数据列表应用开发,总结以下关键实践:


1. **网络层优化**:

   - 实现请求拦截器和响应拦截器

   - 支持请求重试和缓存策略

   - 处理网络状态变化


2. **数据管理策略**:

   - 实现本地数据缓存

   - 支持离线模式运行

   - 智能数据同步机制


3. **性能优化重点**:

   - 使用虚拟化列表提升性能

   - 优化图片加载策略

   - 减少不必要的重新渲染


4. **用户体验提升**:

   - 提供清晰的状态反馈

   - 实现平滑的加载动画

   - 支持下拉刷新和上拉加载


5. **鸿蒙特性集成**:

   - 利用鸿蒙存储能力

   - 集成平台网络API

   - 支持分布式数据


通过系统化的架构设计和细致的优化措施,可以在鸿蒙平台上构建出既高效又稳定的网络数据列表应用。建议在实际开发中根据具体业务需求调整和扩展这些实现方案。


请使用浏览器的分享功能分享到微信等