React Native鸿蒙跨平台新闻应用实现将主应用组件与新闻列表、功能按钮等子组件分离,实现关注点分离
本文分析了基于React Native的新闻应用实现方案,重点探讨了架构设计、状态管理和数据持久化策略。应用采用组件化架构,将主应用组件与新闻列表、功能按钮等子组件分离,实现关注点分离。状态管理使用useState钩子和TypeScript类型定义,确保数据结构的类型安全。通过AsyncStorage实现数据持久化,在组件挂载时加载本地数据,并同步更新内存状态和本地存储。文章还介绍了新闻管理功能、
在移动应用开发中,新闻应用是一种常见的应用类型,需要考虑内容展示、用户交互、数据持久化等多个方面。本文将深入分析一个功能完备的 React Native 新闻应用实现,探讨其架构设计、状态管理、数据持久化以及跨端兼容性策略。
组件化架
该实现采用了清晰的组件化架构,主要包含以下部分:
- 主应用组件 (
NewsApp) - 负责整体布局和状态管理 - 新闻列表渲染 - 负责渲染新闻卡片列表
- 功能按钮 - 提供点赞、评论、分享、收藏等功能
- 标签页导航 - 实现新闻、收藏、历史记录的切换
这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。
状态管理
NewsApp 组件使用 useState 钩子管理多个关键状态:
const [newsItems, setNewsItems] = useState<NewsItem[]>(initialNewsItems);
const [favoriteNews, setFavoriteNews] = useState<string[]>([]);
const [readHistory, setReadHistory] = useState<string[]>([]);
const [activeTab, setActiveTab] = useState<'news' | 'favorites' | 'history'>('news');
这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了新闻的展示、点赞、收藏等功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。
本地存储
应用集成了 AsyncStorage 实现数据持久化:
// 加载本地存储的数据
useEffect(() => {
loadStoredData();
}, []);
const loadStoredData = async () => {
try {
const storedNews = await AsyncStorage.getItem(STORAGE_KEYS.NEWS_ITEMS);
if (storedNews) {
setNewsItems(JSON.parse(storedNews));
}
const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITE_NEWS);
if (storedFavorites) {
setFavoriteNews(JSON.parse(storedFavorites));
}
const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.READ_HISTORY);
if (storedHistory) {
setReadHistory(JSON.parse(storedHistory));
}
} catch (error) {
console.error('加载本地数据失败:', error);
}
};
// 保存数据到本地存储
const saveToStorage = async (key: string, data: any) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
} catch (error) {
console.error('保存数据失败:', error);
}
};
这种实现方式确保了应用数据在应用重启后不会丢失,提高了用户体验。通过 useEffect 钩子在组件挂载时加载数据,确保了应用启动时能够显示之前的状态。
数据同步
应用实现了数据同步机制,当用户进行点赞、收藏等操作时,会同时更新内存中的状态和本地存储:
const handleLike = (id: string) => {
const updatedNews = newsItems.map(item =>
item.id === id
? { ...item, likes: item.isLiked ? item.likes - 1 : item.likes + 1, isLiked: !item.isLiked }
: item
);
setNewsItems(updatedNews);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, updatedNews);
};
这种实现方式确保了内存状态和本地存储的一致性,避免了数据不同步的问题。
新闻管理功能
应用实现了完整的新闻管理功能:
- 新闻列表展示 - 显示新闻标题、摘要、作者、发布时间等信息
- 标签页切换 - 在新闻、收藏、历史记录之间切换
- 点赞功能 - 允许用户点赞新闻,更新点赞数
- 收藏功能 - 允许用户收藏新闻,方便以后查看
- 阅读历史 - 记录用户阅读过的新闻
这些功能覆盖了新闻应用的基本需求,为用户提供了便捷的新闻浏览体验。
交互设计
应用实现了直观的交互设计:
- 点赞 - 点击点赞按钮切换点赞状态
- 收藏 - 点击收藏按钮切换收藏状态
- 评论 - 点击评论按钮查看或添加评论
- 分享 - 点击分享按钮分享新闻
- 标签页切换 - 点击标签页切换不同的内容视图
这些交互设计元素共同构成了良好的用户体验,使得新闻浏览和交互操作简单直观。
类型定义
该实现使用 TypeScript 定义了两个核心数据类型:
- NewsItem - 新闻类型,包含新闻的完整信息,如标题、摘要、内容、作者、发布时间、分类、阅读时间、点赞数、评论数、分享数、点赞状态、收藏状态和突发新闻标记
- Category - 分类类型,包含分类的 ID、名称和图标
这些类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。
数据
应用数据按照功能模块进行组织:
- newsItems - 新闻列表
- favoriteNews - 收藏的新闻 ID 列表
- readHistory - 阅读历史的新闻 ID 列表
- activeTab - 当前激活的标签页
这种数据组织方式使得数据管理更加清晰,易于扩展和维护。
渲染
- 列表优化 - 使用 FlatList 渲染新闻列表,提高长列表的渲染性能
- 条件渲染 - 只在需要时渲染特定的 UI 元素,减少不必要的渲染
- 组件拆分 - 将新闻卡片和功能按钮等拆分为独立的组件,提高渲染性能
- 样式复用 - 通过样式数组和条件样式,复用样式定义,减少样式计算开销
状态管理
- 批量状态更新 - 避免频繁的状态更新,尽量批量更新状态
- 不可变数据 - 使用不可变数据模式更新状态,确保状态更新的可预测性
- 状态清理 - 在组件卸载时清理不必要的状态,减少内存使用
存储
- 按需加载 - 只在需要时加载本地存储的数据,避免启动时加载过多数据
- 增量更新 - 只更新变化的数据,避免每次都保存完整的数据集
- 错误处理 - 对存储操作进行错误处理,确保应用的稳定性
配置化
该实现采用了高度配置化的设计,通过数据结构可以灵活配置应用的行为:
- 新闻数据 - 通过修改
initialNewsItems数组,可以轻松添加或修改新闻 - 本地存储 - 通过修改
STORAGE_KEYS对象,可以灵活配置本地存储的键 - 标签页 - 通过修改标签页相关的状态和渲染逻辑,可以轻松添加或修改标签页
这种设计使得应用能够轻松适应不同的使用场景,无需修改核心代码结构。
1. FlatList 渲染新闻列表
当前实现使用 FlatList 渲染新闻列表,这是一个好的做法,但可以进一步优化:
// 优化前
<FlatList
data={newsItems}
renderItem={({ item }) => (
<NewsCard news={item} />
)}
keyExtractor={item => item.id}
/>
// 优化后
<FlatList
data={newsItems}
renderItem={({ item }) => (
<NewsCard news={item} />
)}
keyExtractor={item => item.id}
initialNumToRender={5} // 初始渲染的项目数
maxToRenderPerBatch={10} // 每批渲染的最大项目数
windowSize={10} // 可见区域外渲染的项目数
removeClippedSubviews={true} // 移除不可见的子视图
updateCellsBatchingPeriod={100} // 单元格更新的批处理周期
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT, // 预计算的项目高度
offset: ITEM_HEIGHT * index,
index
})}
/>
2. 状态管理
当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:
// 优化前
const [newsItems, setNewsItems] = useState<NewsItem[]>(initialNewsItems);
const [favoriteNews, setFavoriteNews] = useState<string[]>([]);
const [readHistory, setReadHistory] = useState<string[]>([]);
const [activeTab, setActiveTab] = useState<'news' | 'favorites' | 'history'>('news');
// 优化后
type AppState = {
newsItems: NewsItem[];
favoriteNews: string[];
readHistory: string[];
activeTab: 'news' | 'favorites' | 'history';
};
type AppAction =
| { type: 'SET_NEWS_ITEMS'; payload: NewsItem[] }
| { type: 'SET_FAVORITE_NEWS'; payload: string[] }
| { type: 'SET_READ_HISTORY'; payload: string[] }
| { type: 'SET_ACTIVE_TAB'; payload: 'news' | 'favorites' | 'history' }
| { type: 'TOGGLE_LIKE'; payload: string }
| { type: 'TOGGLE_BOOKMARK'; payload: string }
| { type: 'ADD_TO_HISTORY'; payload: string };
const initialState: AppState = {
newsItems: initialNewsItems,
favoriteNews: [],
readHistory: [],
activeTab: 'news'
};
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'SET_NEWS_ITEMS':
return { ...state, newsItems: action.payload };
case 'SET_FAVORITE_NEWS':
return { ...state, favoriteNews: action.payload };
case 'SET_READ_HISTORY':
return { ...state, readHistory: action.payload };
case 'SET_ACTIVE_TAB':
return { ...state, activeTab: action.payload };
case 'TOGGLE_LIKE':
return {
...state,
newsItems: state.newsItems.map(item =>
item.id === action.payload
? { ...item, likes: item.isLiked ? item.likes - 1 : item.likes + 1, isLiked: !item.isLiked }
: item
)
};
case 'TOGGLE_BOOKMARK':
return {
...state,
newsItems: state.newsItems.map(item =>
item.id === action.payload
? { ...item, isBookmarked: !item.isBookmarked }
: item
),
favoriteNews: state.newsItems.find(item => item.id === action.payload)?.isBookmarked
? state.favoriteNews.filter(id => id !== action.payload)
: [...state.favoriteNews, action.payload]
};
case 'ADD_TO_HISTORY':
return {
...state,
readHistory: [action.payload, ...state.readHistory.filter(id => id !== action.payload)].slice(0, 50)
};
default:
return state;
}
};
const [state, dispatch] = useReducer(appReducer, initialState);
3. 网络请求
当前实现使用静态数据,可以考虑集成网络请求获取实时新闻:
import axios from 'axios';
const fetchNews = async () => {
try {
const response = await axios.get('https://api.example.com/news');
const news = response.data;
setNewsItems(news);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, news);
} catch (error) {
console.error('获取新闻失败:', error);
// 使用本地存储的缓存数据
}
};
useEffect(() => {
fetchNews();
const interval = setInterval(fetchNews, 60000); // 每分钟刷新一次
return () => clearInterval(interval);
}, []);
4. 导航系统
可以集成 React Navigation 实现新闻详情页面的导航:
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="NewsList"
component={NewsApp}
options={{ title: '新闻' }}
/>
<Stack.Screen
name="NewsDetail"
component={NewsDetailScreen}
options={({ route }) => ({ title: route.params?.newsTitle || '新闻详情' })}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
const NewsDetailScreen = ({ route }: { route: any }) => {
const { newsId } = route.params;
// 获取新闻详情并渲染
return (
<View style={styles.detailContainer}>
{/* 新闻详情内容 */}
</View>
);
};
本文深入分析了一个功能完备的 React Native 新闻应用实现,从架构设计、状态管理、数据持久化到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。
理解这个功能完整的 React Native 新闻资讯应用的技术实现细节,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与实践方案。该应用具备新闻展示、收藏、阅读历史、本地存储等完整功能,是典型的内容类应用架构,涵盖了移动端开发的核心场景与技术要点。
1. 应用架构
该新闻资讯应用采用分层设计+状态驱动的现代化 React Native 架构,核心数据模型设计体现了内容类应用的典型特征:
// 新闻核心数据模型 - 覆盖资讯类应用全维度属性
type NewsItem = {
id: string; // 唯一标识,用于CRUD操作
title: string; // 新闻标题
summary: string; // 新闻摘要
content: string; // 完整内容
author: string; // 作者
publishTime: string; // 发布时间
category: string; // 分类
readTime: string; // 预估阅读时长
likes: number; // 点赞数
comments: number; // 评论数
shares: number; // 分享数
isLiked: boolean; // 是否已点赞(用户交互状态)
isBookmarked: boolean; // 是否已收藏
isBreaking: boolean; // 是否热点新闻
};
// 分类数据模型
type Category = {
id: string; // 分类ID
name: string; // 分类名称
icon: string; // 分类图标
};
// 本地存储键常量 - 统一管理存储标识
const STORAGE_KEYS = {
NEWS_ITEMS: '@news_items',
FAVORITE_NEWS: '@favorite_news',
READ_HISTORY: '@read_history',
};
数据模型设计亮点:
- 业务完整性:包含新闻资讯所需的全部核心字段,兼顾内容展示与用户交互;
- 状态标识清晰:通过布尔值标识用户交互状态(点赞/收藏),简化状态管理;
- 业务特征明显:
isBreaking标识热点新闻,readTime提供阅读体验预估; - 存储规范化:通过常量管理本地存储键名,避免硬编码错误。
2. 状态管理
应用通过 React Hooks 实现精细化的状态管理,多状态协同完成新闻内容的全生命周期管理:
// 核心状态定义
const [newsItems, setNewsItems] = useState<NewsItem[]>(initialNewsItems); // 新闻列表数据源
const [favoriteNews, setFavoriteNews] = useState<string[]>([]); // 收藏新闻ID列表
const [readHistory, setReadHistory] = useState<string[]>([]); // 阅读历史ID列表
const [activeTab, setActiveTab] = useState<'news' | 'favorites' | 'history'>('news'); // 标签页状态
(1)本地存储管理
// 组件挂载时加载本地数据
useEffect(() => {
loadStoredData();
}, []);
// 加载本地存储数据
const loadStoredData = async () => {
try {
const storedNews = await AsyncStorage.getItem(STORAGE_KEYS.NEWS_ITEMS);
if (storedNews) setNewsItems(JSON.parse(storedNews));
const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITE_NEWS);
if (storedFavorites) setFavoriteNews(JSON.parse(storedFavorites));
const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.READ_HISTORY);
if (storedHistory) setReadHistory(JSON.parse(storedHistory));
} catch (error) {
console.error('加载本地数据失败:', error);
}
};
// 通用存储方法封装
const saveToStorage = async (key: string, data: any) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
} catch (error) {
console.error('保存数据失败:', error);
}
};
(2)点赞交互逻辑(含状态同步)
const handleLike = (id: string) => {
// 不可变更新新闻状态
const updatedNews = newsItems.map(item =>
item.id === id
? { ...item, likes: item.isLiked ? item.likes - 1 : item.likes + 1, isLiked: !item.isLiked }
: item
);
setNewsItems(updatedNews);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, updatedNews); // 同步到本地存储
};
(3)收藏管理
const handleBookmark = (id: string) => {
// 更新新闻收藏状态
const updatedNews = newsItems.map(item =>
item.id === id
? { ...item, isBookmarked: !item.isBookmarked }
: item
);
setNewsItems(updatedNews);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, updatedNews);
// 同步更新收藏列表
const updatedFavorites = [...favoriteNews];
if (updatedFavorites.includes(id)) {
const index = updatedFavorites.indexOf(id);
updatedFavorites.splice(index, 1); // 取消收藏
} else {
updatedFavorites.push(id); // 添加收藏
}
setFavoriteNews(updatedFavorites);
saveToStorage(STORAGE_KEYS.FAVORITE_NEWS, updatedFavorites);
};
(4)阅读历史管理
const handleReadMore = (news: NewsItem) => {
// 更新阅读历史,保持最多20条记录
const updatedHistory = [news.id, ...readHistory.filter(id => id !== news.id)].slice(0, 20);
setReadHistory(updatedHistory);
saveToStorage(STORAGE_KEYS.READ_HISTORY, updatedHistory);
Alert.alert('阅读新闻', `正在阅读: ${news.title}\n\n${news.content}`);
};
应用采用组件化拆分思想,将UI拆分为多个可复用组件,提升代码可维护性:
(1)新闻卡片组件
核心展示组件,封装了新闻内容展示、交互按钮等完整功能:
const NewsCard = ({
news,
onLike,
onBookmark,
onReadMore
}: {
news: NewsItem;
onLike: (id: string) => void;
onBookmark: (id: string) => void;
onReadMore: (news: NewsItem) => void;
}) => {
return (
<View style={styles.newsCard}>
{/* 分类标签与热点标识 */}
<View style={styles.newsHeader}>
<View style={styles.categoryBadge}>
<Text style={styles.categoryText}>{news.category}</Text>
</View>
{news.isBreaking && (
<View style={styles.breakingBadge}>
<Text style={styles.breakingText}>🔥 热点</Text>
</View>
)}
</View>
{/* 新闻标题与摘要 */}
<TouchableOpacity onPress={() => onReadMore(news)}>
<Text style={styles.newsTitle}>{news.title}</Text>
<Text style={styles.newsSummary} numberOfLines={2}>{news.summary}</Text>
</TouchableOpacity>
{/* 元信息 */}
<View style={styles.newsMeta}>
{/* 作者、时间、阅读时长 */}
</View>
{/* 交互按钮 */}
<View style={styles.newsActions}>
<TouchableOpacity style={styles.actionButton} onPress={() => onLike(news.id)}>
<Text style={[styles.actionIcon, news.isLiked && styles.likedIcon]}>👍</Text>
<Text style={styles.actionText}>{news.likes}</Text>
</TouchableOpacity>
{/* 评论、收藏、分享按钮 */}
</View>
</View>
);
};
(2)列表渲染优化
使用 FlatList 替代基础 ScrollView 实现长列表渲染,提升性能:
<FlatList
data={newsItems}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<NewsCard
news={item}
onLike={handleLike}
onBookmark={handleBookmark}
onReadMore={handleReadMore}
/>
)}
showsVerticalScrollIndicator={false}
/>
(3)标签页切换
通过 activeTab 状态实现内容区域的条件渲染:
{activeTab === 'news' && (/* 最新新闻内容 */)}
{activeTab === 'favorites' && (/* 收藏内容 */)}
{activeTab === 'history' && (/* 阅读历史 */)}
应用采用 StyleSheet 实现样式与逻辑分离,遵循移动端设计规范:
- 卡片式设计:所有内容模块使用圆角卡片,提升视觉层次感;
- 状态视觉反馈:点赞/收藏按钮有明确的选中态样式;
- 响应式布局:使用
Dimensions获取屏幕尺寸,适配不同设备; - 滚动优化:水平滚动容器隐藏滚动条,提升用户体验;
- 空状态处理:收藏/历史页面提供友好的空状态提示。
将该 React Native 新闻应用适配到鸿蒙平台,核心是将 React 的状态管理、组件体系、存储机制映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。
1. 核心技术栈映射
| React Native 核心能力 | 鸿蒙 ArkTS 对应实现 | 适配关键说明 |
|---|---|---|
useState 状态管理 |
@State/@Link/@StorageProp |
状态声明语法替换,逻辑一致 |
useEffect 生命周期 |
onPageShow/aboutToAppear |
生命周期方法替换 |
AsyncStorage 本地存储 |
Preferences 数据存储 |
存储API替换,数据格式兼容 |
FlatList 列表渲染 |
List + LazyForEach |
虚拟列表渲染,性能更优 |
ScrollView 滚动容器 |
Scroll 组件 |
滚动容器替换 |
TouchableOpacity |
Button + stateEffect(false) |
可点击组件替换 |
StyleSheet.create |
@Styles/@Extend + 内联样式 |
样式体系重构 |
Alert.alert |
AlertDialog 组件 |
弹窗交互替换 |
| 条件渲染(三元运算符) | if/else 条件渲染 |
视图控制语法适配 |
| 不可变状态更新 | 直接状态赋值 + 强制更新 | 状态更新机制调整 |
2. 鸿蒙端
// index.ets - 鸿蒙端入口文件
import router from '@ohos.router';
import preferences from '@ohos.data.preferences';
@Entry
@Component
struct NewsApp {
// 核心状态定义(对应 React Native 的 useState)
@State newsItems: NewsItem[] = [
{
id: '1',
title: '全球气候变化峰会达成历史性协议',
summary: '各国领导人就减排目标达成一致,承诺在未来十年内大幅减少碳排放。这一协议被认为是对抗气候变化的重要里程碑。',
content: '全球气候变化峰会在经过两周的紧张谈判后,各国代表终于达成了一项历史性的协议。根据协议内容,所有签署国承诺在未来十年内将其温室气体排放量减少50%,并投入大量资金用于绿色能源的研发和推广。专家认为,这项协议将对全球气候产生深远影响。',
author: '张记者',
publishTime: '2小时前',
category: '国际',
readTime: '5',
likes: 245,
comments: 42,
shares: 18,
isLiked: false,
isBookmarked: true,
isBreaking: true
},
// 其他初始化数据...
];
@State favoriteNews: string[] = [];
@State readHistory: string[] = [];
@State activeTab: 'news' | 'favorites' | 'history' = 'news';
// 存储管理器实例
private prefManager: preferences.Preferences | null = null;
// 类型定义(完全复用 React Native 的类型)
type NewsItem = {
id: string;
title: string;
summary: string;
content: string;
author: string;
publishTime: string;
category: string;
readTime: string;
likes: number;
comments: number;
shares: number;
isLiked: boolean;
isBookmarked: boolean;
isBreaking: boolean;
};
type Category = {
id: string;
name: string;
icon: string;
};
// 本地存储键(复用 React Native 定义)
private STORAGE_KEYS = {
NEWS_ITEMS: 'news_items',
FAVORITE_NEWS: 'favorite_news',
READ_HISTORY: 'read_history',
};
// 通用样式封装 - 替代 React Native 的 StyleSheet
@Styles
cardShadow() {
.shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
}
// 生命周期方法(对应 React 的 useEffect)
async aboutToAppear() {
await this.initPreferences();
await this.loadStoredData();
}
// 初始化偏好存储
private async initPreferences() {
try {
this.prefManager = await preferences.getPreferences(getContext(), 'news_app_prefs');
} catch (error) {
console.error('初始化存储失败:', error);
}
}
// 加载本地存储数据(对应 React Native 的 loadStoredData)
private async loadStoredData() {
if (!this.prefManager) return;
try {
// 读取新闻数据
const storedNews = await this.prefManager.get(this.STORAGE_KEYS.NEWS_ITEMS, '');
if (storedNews !== '') {
this.newsItems = JSON.parse(storedNews as string);
}
// 读取收藏数据
const storedFavorites = await this.prefManager.get(this.STORAGE_KEYS.FAVORITE_NEWS, '');
if (storedFavorites !== '') {
this.favoriteNews = JSON.parse(storedFavorites as string);
}
// 读取阅读历史
const storedHistory = await this.prefManager.get(this.STORAGE_KEYS.READ_HISTORY, '');
if (storedHistory !== '') {
this.readHistory = JSON.parse(storedHistory as string);
}
} catch (error) {
console.error('加载本地数据失败:', error);
}
}
// 保存数据到本地存储(对应 React Native 的 saveToStorage)
private async saveToStorage(key: string, data: any) {
if (!this.prefManager) return;
try {
await this.prefManager.put(key, JSON.stringify(data));
await this.prefManager.flush(); // 立即刷盘
} catch (error) {
console.error('保存数据失败:', error);
}
}
// 点赞处理(核心逻辑复用)
private handleLike(id: string) {
this.newsItems = this.newsItems.map(item =>
item.id === id
? { ...item, likes: item.isLiked ? item.likes - 1 : item.likes + 1, isLiked: !item.isLiked }
: item
);
this.saveToStorage(this.STORAGE_KEYS.NEWS_ITEMS, this.newsItems);
}
// 收藏处理(核心逻辑复用)
private handleBookmark(id: string) {
// 更新新闻收藏状态
this.newsItems = this.newsItems.map(item =>
item.id === id
? { ...item, isBookmarked: !item.isBookmarked }
: item
);
this.saveToStorage(this.STORAGE_KEYS.NEWS_ITEMS, this.newsItems);
// 更新收藏列表
const updatedFavorites = [...this.favoriteNews];
if (updatedFavorites.includes(id)) {
const index = updatedFavorites.indexOf(id);
updatedFavorites.splice(index, 1);
} else {
updatedFavorites.push(id);
}
this.favoriteNews = updatedFavorites;
this.saveToStorage(this.STORAGE_KEYS.FAVORITE_NEWS, updatedFavorites);
}
// 阅读新闻处理(核心逻辑复用)
private handleReadMore(news: NewsItem) {
// 更新阅读历史
const updatedHistory = [news.id, ...this.readHistory.filter(id => id !== news.id)].slice(0, 20);
this.readHistory = updatedHistory;
this.saveToStorage(this.STORAGE_KEYS.READ_HISTORY, updatedHistory);
// 鸿蒙弹窗替代 Alert.alert
AlertDialog.show({
title: '阅读新闻',
message: `正在阅读: ${news.title}\n\n${news.content}`,
confirm: { value: '关闭' }
});
}
// 搜索处理
private handleSearch() {
AlertDialog.show({
title: '搜索',
message: '打开新闻搜索功能',
confirm: { value: '确定' }
});
}
// 获取收藏的新闻
private getFavoriteNews(): NewsItem[] {
return this.newsItems.filter(item => this.favoriteNews.includes(item.id));
}
// 获取阅读历史
private getReadHistory(): NewsItem[] {
return this.newsItems.filter(item => this.readHistory.includes(item.id));
}
build() {
Column({ space: 0 }) {
// 头部组件
this.Header();
// 内容区域
Scroll() {
Column({ space: 16 }) {
// 搜索栏
this.SearchBar();
// 标签页切换
this.TabContainer();
// 内容区域 - 条件渲染
if (this.activeTab === 'news') {
this.NewsContent();
} else if (this.activeTab === 'favorites') {
this.FavoriteContent();
} else if (this.activeTab === 'history') {
this.HistoryContent();
}
// 推荐新闻
this.RecommendationSection();
// 使用说明
this.InfoCard();
}
.padding(16)
.width('100%');
}
.flex(1)
.width('100%');
// 底部导航
this.BottomNav();
}
.width('100%')
.height('100%')
.backgroundColor('#f8fafc')
.safeArea(true);
}
// 头部组件
@Builder
Header() {
Row({ space: 0 }) {
Text('今日资讯')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Row({ space: 12 }) {
// 搜索按钮
Button() {
Text('🔍')
.fontSize(18)
.fontColor('#64748b');
}
.width(36)
.height(36)
.borderRadius(18)
.backgroundColor('#f1f5f9')
.stateEffect(false)
.onClick(() => this.handleSearch());
// 通知按钮
Button() {
Text('🔔')
.fontSize(18)
.fontColor('#64748b');
}
.width(36)
.height(36)
.borderRadius(18)
.backgroundColor('#f1f5f9')
.stateEffect(false);
}
.marginLeft('auto');
}
.padding(20)
.backgroundColor('#ffffff')
.borderBottom({ width: 1, color: '#e2e8f0' })
.width('100%');
}
// 搜索栏组件
@Builder
SearchBar() {
Row({ space: 12 }) {
Text('🔍')
.fontSize(18)
.fontColor('#64748b');
Text('搜索您关心的新闻')
.fontSize(14)
.fontColor('#94a3b8')
.flex(1);
}
.backgroundColor('#ffffff')
.borderRadius(20)
.padding({ top: 12, bottom: 12, left: 16, right: 16 })
.cardShadow()
.width('100%');
}
// 标签页容器
@Builder
TabContainer() {
Row({ space: 4 }) {
// 最新新闻标签
Button() {
Text('最新新闻')
.fontSize(14)
.fontColor(this.activeTab === 'news' ? '#ffffff' : '#64748b')
.fontWeight(this.activeTab === 'news' ? FontWeight.Medium : FontWeight.Normal);
}
.flex(1)
.paddingVertical(10)
.borderRadius(16)
.backgroundColor(this.activeTab === 'news' ? '#3b82f6' : '#ffffff')
.stateEffect(false)
.onClick(() => this.activeTab = 'news');
// 收藏标签
Button() {
Text('收藏')
.fontSize(14)
.fontColor(this.activeTab === 'favorites' ? '#ffffff' : '#64748b')
.fontWeight(this.activeTab === 'favorites' ? FontWeight.Medium : FontWeight.Normal);
}
.flex(1)
.paddingVertical(10)
.borderRadius(16)
.backgroundColor(this.activeTab === 'favorites' ? '#3b82f6' : '#ffffff')
.stateEffect(false)
.onClick(() => this.activeTab = 'favorites');
// 阅读历史标签
Button() {
Text('阅读历史')
.fontSize(14)
.fontColor(this.activeTab === 'history' ? '#ffffff' : '#64748b')
.fontWeight(this.activeTab === 'history' ? FontWeight.Medium : FontWeight.Normal);
}
.flex(1)
.paddingVertical(10)
.borderRadius(16)
.backgroundColor(this.activeTab === 'history' ? '#3b82f6' : '#ffffff')
.stateEffect(false)
.onClick(() => this.activeTab = 'history');
}
.backgroundColor('#ffffff')
.borderRadius(20)
.padding(4)
.width('100%');
}
// 新闻内容区域
@Builder
NewsContent() {
Column({ space: 16 }) {
// 热点新闻标题
Text('热点新闻')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
// 热点新闻横向滚动
Scroll({ direction: ScrollDirection.Horizontal }) {
Row({ space: 12 }) {
const trendingNews = this.newsItems.filter(item => item.isBreaking).slice(0, 2);
ForEach(trendingNews, (item: NewsItem) => {
this.TrendingNewsCard(item);
});
}
}
.scrollBar(BarState.Off); // 隐藏滚动条
// 新闻分类标题
Text('新闻分类')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
// 新闻分类横向滚动
Scroll({ direction: ScrollDirection.Horizontal }) {
Row({ space: 12 }) {
const categories: Category[] = [
{ id: '1', name: '推荐', icon: '🔥' },
{ id: '2', name: '国内', icon: '🇨🇳' },
// 其他分类...
];
ForEach(categories, (category: Category) => {
this.CategoryCard(category);
});
}
}
.scrollBar(BarState.Off);
// 最新新闻标题
Row({ space: 0 }) {
Text('最新新闻')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(`(${this.newsItems.length} 条新闻)`)
.fontSize(14)
.fontColor('#64748b')
.marginLeft('auto');
}
.width('100%');
// 新闻列表(鸿蒙 List 替代 FlatList)
List() {
LazyForEach(new MyDataSource(this.newsItems), (item: NewsItem) => {
ListItem() {
this.NewsCard(item);
}
.marginBottom(16);
});
}
.width('100%')
.scrollBar(BarState.Off);
}
.width('100%');
}
// 收藏内容区域
@Builder
FavoriteContent() {
Column({ space: 12 }) {
Text('收藏的新闻')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
if (this.getFavoriteNews().length > 0) {
List() {
LazyForEach(new MyDataSource(this.getFavoriteNews()), (item: NewsItem) => {
ListItem() {
this.NewsCard(item);
}
.marginBottom(16);
});
}
.width('100%')
.scrollBar(BarState.Off);
} else {
// 空状态
Column({ space: 8 }) {
Text('暂无收藏的新闻')
.fontSize(16)
.fontColor('#64748b');
Text('点击新闻右下角的星星按钮可收藏')
.fontSize(14)
.fontColor('#94a3b8');
}
.padding({ top: 40, bottom: 40 })
.width('100%')
.alignItems(Alignment.Center);
}
}
.width('100%');
}
// 阅读历史内容区域
@Builder
HistoryContent() {
Column({ space: 12 }) {
Text('阅读历史')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
if (this.getReadHistory().length > 0) {
List() {
LazyForEach(new MyDataSource(this.getReadHistory()), (item: NewsItem) => {
ListItem() {
this.NewsCard(item);
}
.marginBottom(16);
});
}
.width('100%')
.scrollBar(BarState.Off);
} else {
// 空状态
Column({ space: 8 }) {
Text('暂无阅读历史')
.fontSize(16)
.fontColor('#64748b');
Text('阅读过的新闻会在这里显示')
.fontSize(14)
.fontColor('#94a3b8');
}
.padding({ top: 40, bottom: 40 })
.width('100%')
.alignItems(Alignment.Center);
}
}
.width('100%');
}
// 推荐新闻区域
@Builder
RecommendationSection() {
Column({ space: 12 }) {
Text('为您推荐')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.width('100%');
Row({ space: 12 }) {
// 推荐新闻图片占位
Stack() {
Text('📰')
.fontSize(24)
.fontColor('#64748b');
}
.width(60)
.height(60)
.borderRadius(8)
.backgroundColor('#f1f5f9')
.alignItems(Alignment.Center)
.justifyContent(FlexAlign.Center);
// 推荐新闻信息
Column({ space: 4 }) {
Text('人工智能在医疗领域的应用前景广阔')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b');
Text('AI技术正在改变传统医疗模式,提高诊断效率和准确性')
.fontSize(12)
.fontColor('#64748b');
Row({ space: 0 }) {
Text('科技')
.fontSize(11)
.fontColor('#3b82f6')
.backgroundColor('#dbeafe')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(6);
Text('2小时前')
.fontSize(11)
.fontColor('#94a3b8')
.marginLeft('auto');
}
}
.flex(1);
}
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.width('100%');
}
// 使用说明卡片
@Builder
InfoCard() {
Column({ space: 8 }) {
Text('使用说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text('• 点赞新闻:点击👍按钮')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 收藏新闻:点击⭐按钮')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 阅读历史:自动保存最近阅读的新闻')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 本地存储:所有数据都保存在您的设备上')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.width('100%');
}
// 底部导航
@Builder
BottomNav() {
Row({ space: 0 }) {
// 首页
Button() {
Column({ space: 4 }) {
Text('🏠')
.fontSize(20)
.fontColor('#94a3b8');
Text('首页')
.fontSize(12)
.fontColor('#94a3b8');
}
}
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(false);
// 热点
Button() {
Column({ space: 4 }) {
Text('🔥')
.fontSize(20)
.fontColor('#94a3b8');
Text('热点')
.fontSize(12)
.fontColor('#94a3b8');
}
}
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(false);
// 收藏
Button() {
Column({ space: 4 }) {
Text('⭐')
.fontSize(20)
.fontColor('#94a3b8');
Text('收藏')
.fontSize(12)
.fontColor('#94a3b8');
}
}
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(false);
// 我的
Button() {
Column({ space: 4 }) {
Text('👤')
.fontSize(20)
.fontColor('#94a3b8');
Text('我的')
.fontSize(12)
.fontColor('#94a3b8');
}
}
.flex(1)
.backgroundColor(Color.Transparent)
.stateEffect(false);
}
.backgroundColor('#ffffff')
.borderTop({ width: 1, color: '#e2e8f0' })
.paddingVertical(12)
.width('100%');
}
// 新闻卡片组件
@Builder
NewsCard(news: NewsItem) {
Column({ space: 8 }) {
// 新闻头部(分类 + 热点)
Row({ space: 0 }) {
// 分类标签
Text(news.category)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor('#3b82f6')
.backgroundColor('#dbeafe')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(12);
// 热点标识
if (news.isBreaking) {
Text('🔥 热点')
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor('#dc2626')
.backgroundColor('#fed7d7')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(12)
.marginLeft('auto');
}
}
.width('100%');
// 新闻标题和摘要
Column({ space: 8 }) {
Text(news.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(news.summary)
.fontSize(14)
.fontColor('#64748b')
.lineHeight(20)
.maxLines(2);
}
.width('100%')
.onClick(() => this.handleReadMore(news));
// 元信息
Row({ space: 0 }) {
Column({ space: 0 }) {
Text(news.author)
.fontSize(12)
.fontColor('#64748b');
}
.marginRight(12);
Text(news.publishTime)
.fontSize(12)
.fontColor('#94a3b8')
.marginRight('auto');
Text(`${news.readTime} 分钟阅读`)
.fontSize(12)
.fontColor('#94a3b8');
}
.width('100%')
.paddingBottom(12)
.borderBottom({ width: 1, color: '#e2e8f0' });
// 操作按钮
Row({ space: 0 }) {
// 点赞按钮
Button() {
Row({ space: 4 }) {
Text('👍')
.fontSize(16)
.fontColor(news.isLiked ? '#ef4444' : '#64748b');
Text(news.likes.toString())
.fontSize(12)
.fontColor('#64748b');
}
}
.backgroundColor(Color.Transparent)
.stateEffect(false)
.onClick(() => this.handleLike(news.id));
// 评论按钮
Button() {
Row({ space: 4 }) {
Text('💬')
.fontSize(16)
.fontColor('#64748b');
Text(news.comments.toString())
.fontSize(12)
.fontColor('#64748b');
}
}
.backgroundColor(Color.Transparent)
.stateEffect(false)
.marginLeft('auto');
// 收藏按钮
Button() {
Row({ space: 4 }) {
Text(news.isBookmarked ? '⭐' : '☆')
.fontSize(16)
.fontColor(news.isBookmarked ? '#3b82f6' : '#64748b');
Text(news.shares.toString())
.fontSize(12)
.fontColor('#64748b');
}
}
.backgroundColor(Color.Transparent)
.stateEffect(false)
.onClick(() => this.handleBookmark(news.id));
// 分享按钮
Button() {
Text('🔄')
.fontSize(16)
.fontColor('#64748b');
}
.backgroundColor(Color.Transparent)
.stateEffect(false);
}
.width('100%');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.width('100%');
}
// 热点新闻卡片
@Builder
TrendingNewsCard(news: NewsItem) {
Row({ space: 12 }) {
// 图片占位
Stack() {
Text('📰')
.fontSize(30)
.fontColor('#64748b');
}
.width(80)
.height(80)
.borderRadius(8)
.backgroundColor('#f1f5f9')
.alignItems(Alignment.Center)
.justifyContent(FlexAlign.Center);
// 新闻内容
Column({ space: 4 }) {
Text(news.title)
.fontSize(14)
.fontWeight(FontWeight.SemiBold)
.fontColor('#1e293b')
.maxLines(2);
Text(news.summary)
.fontSize(12)
.fontColor('#64748b')
.maxLines(2)
.flex(1);
Row({ space: 0 }) {
Text(news.author)
.fontSize(11)
.fontColor('#94a3b8');
Text(news.publishTime)
.fontSize(11)
.fontColor('#94a3b8')
.marginLeft('auto');
}
}
.flex(1);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(12)
.cardShadow()
.width('80%')
.marginRight(12)
.onClick(() => this.handleReadMore(news));
}
// 分类卡片
@Builder
CategoryCard(category: Category) {
Button() {
Column({ space: 8 }) {
Stack() {
Text(category.icon)
.fontSize(16)
.fontColor('#3b82f6');
}
.width(32)
.height(32)
.borderRadius(16)
.backgroundColor('#dbeafe')
.alignItems(Alignment.Center)
.justifyContent(FlexAlign.Center);
Text(category.name)
.fontSize(12)
.fontColor('#1e293b')
.textAlign(TextAlign.Center);
}
}
.width(70)
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.marginRight(12)
.stateEffect(false)
.onClick(() => {
AlertDialog.show({
title: '类别筛选',
message: `显示 ${category.name} 类别的新闻`,
confirm: { value: '确定' }
});
});
}
}
// 自定义数据源(鸿蒙 LazyForEach 必需)
class MyDataSource implements IDataSource {
private data: NewsItem[];
private listener: DataChangeListener | null = null;
constructor(data: NewsItem[]) {
this.data = data;
}
totalCount(): number {
return this.data.length;
}
getData(index: number): NewsItem {
return this.data[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener;
}
unregisterDataChangeListener(): void {
this.listener = null;
}
}
(1)本地存储
React Native AsyncStorage 替换为鸿蒙 Preferences:
// React Native
await AsyncStorage.setItem(key, JSON.stringify(data));
// 鸿蒙
await this.prefManager.put(key, JSON.stringify(data));
await this.prefManager.flush();
(2)列表渲染
React Native FlatList 替换为鸿蒙 List + LazyForEach:
// React Native
<FlatList
data={newsItems}
renderItem={({ item }) => <NewsCard news={item} />}
/>
// 鸿蒙
List() {
LazyForEach(new MyDataSource(this.newsItems), (item: NewsItem) => {
ListItem() {
this.NewsCard(item);
}
});
}
(3)状态更新
// React Native(不可变更新)
const updatedNews = newsItems.map(item =>
item.id === id ? { ...item, isLiked: !item.isLiked } : item
);
setNewsItems(updatedNews);
// 鸿蒙(直接更新)
this.newsItems = this.newsItems.map(item =>
item.id === id ? { ...item, isLiked: !item.isLiked } : item
);
(4)生命周期
// React Native
useEffect(() => {
loadStoredData();
}, []);
// 鸿蒙
async aboutToAppear() {
await this.initPreferences();
await this.loadStoredData();
}
该新闻资讯应用的跨端适配实践验证了 React Native 向鸿蒙迁移的可行性与高效性,对于内容类应用,可实现较高的代码复用率,同时保证一致的用户体验和业务逻辑。这种适配模式特别适合需要快速覆盖多端的内容类应用开发。
真实演示案例代码:
// app.tsx
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList, AsyncStorage } from 'react-native';
// Base64 图标库
const ICONS_BASE64 = {
news: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
like: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
comment: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
share: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
bookmark: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
trending: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// 新闻类型
type NewsItem = {
id: string;
title: string;
summary: string;
content: string;
author: string;
publishTime: string;
category: string;
readTime: string;
likes: number;
comments: number;
shares: number;
isLiked: boolean;
isBookmarked: boolean;
isBreaking: boolean;
};
// 类别类型
type Category = {
id: string;
name: string;
icon: string;
};
// 本地存储键
const STORAGE_KEYS = {
NEWS_ITEMS: '@news_items',
FAVORITE_NEWS: '@favorite_news',
READ_HISTORY: '@read_history',
};
// 初始化新闻数据
const initialNewsItems: NewsItem[] = [
{
id: '1',
title: '全球气候变化峰会达成历史性协议',
summary: '各国领导人就减排目标达成一致,承诺在未来十年内大幅减少碳排放。这一协议被认为是对抗气候变化的重要里程碑。',
content: '全球气候变化峰会在经过两周的紧张谈判后,各国代表终于达成了一项历史性的协议。根据协议内容,所有签署国承诺在未来十年内将其温室气体排放量减少50%,并投入大量资金用于绿色能源的研发和推广。专家认为,这项协议将对全球气候产生深远影响。',
author: '张记者',
publishTime: '2小时前',
category: '国际',
readTime: '5',
likes: 245,
comments: 42,
shares: 18,
isLiked: false,
isBookmarked: true,
isBreaking: true
},
{
id: '2',
title: '科技创新大会聚焦人工智能发展',
summary: '多家科技巨头展示了最新的人工智能研究成果,引发了业界广泛关注。',
content: '在本届科技创新大会上,多家知名科技公司发布了其最新的人工智能研究成果。这些新技术涵盖了机器学习、自然语言处理等多个领域,预计将对未来的生活和工作方式产生重大影响。',
author: '李科技',
publishTime: '4小时前',
category: '科技',
readTime: '3',
likes: 189,
comments: 35,
shares: 22,
isLiked: true,
isBookmarked: false,
isBreaking: false
},
{
id: '3',
title: '体育界传来好消息:奥运会筹备进展顺利',
summary: '组委会宣布各项设施建设和准备工作均已按计划完成,预计将迎来史上最成功的奥运会。',
content: '国际奥委会主席在新闻发布会上表示,本届奥运会的各项筹备工作进展非常顺利。所有比赛场馆已基本完工,志愿者招募工作也已完成。此外,防疫措施也在不断完善,确保运动员和观众的安全。',
author: '王体育',
publishTime: '6小时前',
category: '体育',
readTime: '4',
likes: 156,
comments: 28,
shares: 15,
isLiked: false,
isBookmarked: false,
isBreaking: true
},
{
id: '4',
title: '健康生活:新研究揭示饮食习惯对长寿的影响',
summary: '科学家发现特定的饮食模式可以显著延长寿命,这一发现对公共卫生具有重要意义。',
content: '最新的科学研究表明,采用地中海饮食模式的人群平均寿命比普通人群高出约5年。这种饮食模式富含橄榄油、鱼类、坚果和蔬菜,有助于降低心血管疾病的风险。',
author: '赵医生',
publishTime: '8小时前',
category: '健康',
readTime: '6',
likes: 98,
comments: 17,
shares: 12,
isLiked: false,
isBookmarked: true,
isBreaking: false
},
{
id: '5',
title: '经济复苏势头强劲,失业率降至新低',
summary: '最新数据显示,经济复苏速度超出预期,就业市场表现亮眼。',
content: '国家统计局公布的最新数据显示,上个月失业率降至3.2%,为近十年来的最低水平。同时,新增就业岗位达到25万个,显示出经济复苏的强劲势头。经济学家预测,这一趋势有望在接下来的几个月内持续。',
author: '钱经济',
publishTime: '10小时前',
category: '财经',
readTime: '4',
likes: 134,
comments: 25,
shares: 19,
isLiked: true,
isBookmarked: false,
isBreaking: true
}
];
const NewsApp = () => {
const [newsItems, setNewsItems] = useState<NewsItem[]>(initialNewsItems);
const [favoriteNews, setFavoriteNews] = useState<string[]>([]);
const [readHistory, setReadHistory] = useState<string[]>([]);
const [activeTab, setActiveTab] = useState<'news' | 'favorites' | 'history'>('news');
// 加载本地存储的数据
useEffect(() => {
loadStoredData();
}, []);
const loadStoredData = async () => {
try {
const storedNews = await AsyncStorage.getItem(STORAGE_KEYS.NEWS_ITEMS);
if (storedNews) {
setNewsItems(JSON.parse(storedNews));
}
const storedFavorites = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITE_NEWS);
if (storedFavorites) {
setFavoriteNews(JSON.parse(storedFavorites));
}
const storedHistory = await AsyncStorage.getItem(STORAGE_KEYS.READ_HISTORY);
if (storedHistory) {
setReadHistory(JSON.parse(storedHistory));
}
} catch (error) {
console.error('加载本地数据失败:', error);
}
};
// 保存数据到本地存储
const saveToStorage = async (key: string, data: any) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
} catch (error) {
console.error('保存数据失败:', error);
}
};
const handleLike = (id: string) => {
const updatedNews = newsItems.map(item =>
item.id === id
? { ...item, likes: item.isLiked ? item.likes - 1 : item.likes + 1, isLiked: !item.isLiked }
: item
);
setNewsItems(updatedNews);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, updatedNews);
};
const handleBookmark = (id: string) => {
const updatedNews = newsItems.map(item =>
item.id === id
? { ...item, isBookmarked: !item.isBookmarked }
: item
);
setNewsItems(updatedNews);
saveToStorage(STORAGE_KEYS.NEWS_ITEMS, updatedNews);
// 更新收藏列表
const updatedFavorites = [...favoriteNews];
if (updatedFavorites.includes(id)) {
const index = updatedFavorites.indexOf(id);
updatedFavorites.splice(index, 1);
} else {
updatedFavorites.push(id);
}
setFavoriteNews(updatedFavorites);
saveToStorage(STORAGE_KEYS.FAVORITE_NEWS, updatedFavorites);
};
const handleReadMore = (news: NewsItem) => {
// 更新阅读历史
const updatedHistory = [news.id, ...readHistory.filter(id => id !== news.id)].slice(0, 20);
setReadHistory(updatedHistory);
saveToStorage(STORAGE_KEYS.READ_HISTORY, updatedHistory);
Alert.alert('阅读新闻', `正在阅读: ${news.title}\n\n${news.content}`);
};
const handleSearch = () => {
Alert.alert('搜索', '打开新闻搜索功能');
};
// 获取收藏的新闻
const getFavoriteNews = () => {
return newsItems.filter(item => favoriteNews.includes(item.id));
};
// 获取阅读历史
const getReadHistory = () => {
return newsItems.filter(item => readHistory.includes(item.id));
};
// 新闻卡片组件
const NewsCard = ({
news,
onLike,
onBookmark,
onReadMore
}: {
news: NewsItem;
onLike: (id: string) => void;
onBookmark: (id: string) => void;
onReadMore: (news: NewsItem) => void;
}) => {
return (
<View style={styles.newsCard}>
<View style={styles.newsHeader}>
<View style={styles.categoryBadge}>
<Text style={styles.categoryText}>{news.category}</Text>
</View>
{news.isBreaking && (
<View style={styles.breakingBadge}>
<Text style={styles.breakingText}>🔥 热点</Text>
</View>
)}
</View>
<TouchableOpacity onPress={() => onReadMore(news)}>
<Text style={styles.newsTitle}>{news.title}</Text>
<Text style={styles.newsSummary} numberOfLines={2}>{news.summary}</Text>
</TouchableOpacity>
<View style={styles.newsMeta}>
<View style={styles.authorInfo}>
<Text style={styles.authorName}>{news.author}</Text>
<Text style={styles.publishTime}>{news.publishTime}</Text>
</View>
<Text style={styles.readTime}>{news.readTime} 分钟阅读</Text>
</View>
<View style={styles.newsActions}>
<TouchableOpacity style={styles.actionButton} onPress={() => onLike(news.id)}>
<Text style={[styles.actionIcon, news.isLiked && styles.likedIcon]}>👍</Text>
<Text style={styles.actionText}>{news.likes}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionIcon}>💬</Text>
<Text style={styles.actionText}>{news.comments}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={() => onBookmark(news.id)}>
<Text style={[styles.actionIcon, news.isBookmarked && styles.bookmarkedIcon]}>
{news.isBookmarked ? '⭐' : '☆'}
</Text>
<Text style={styles.actionText}>{news.shares}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionIcon}>🔄</Text>
</TouchableOpacity>
</View>
</View>
);
};
// 类别卡片组件
const CategoryCard = ({
category,
onPress
}: {
category: Category;
onPress: () => void
}) => {
return (
<TouchableOpacity style={styles.categoryCard} onPress={onPress}>
<View style={styles.categoryIcon}>
<Text style={styles.categoryIconText}>{category.icon}</Text>
</View>
<Text style={styles.categoryName}>{category.name}</Text>
</TouchableOpacity>
);
};
// 热点新闻卡片组件
const TrendingNewsCard = ({
news,
onPress
}: {
news: NewsItem;
onPress: (news: NewsItem) => void
}) => {
return (
<TouchableOpacity style={styles.trendingCard} onPress={() => onPress(news)}>
<View style={styles.trendingImage}>
<Text style={styles.trendingImageText}>📰</Text>
</View>
<View style={styles.trendingContent}>
<Text style={styles.trendingTitle} numberOfLines={2}>{news.title}</Text>
<Text style={styles.trendingSummary} numberOfLines={2}>{news.summary}</Text>
<View style={styles.trendingMeta}>
<Text style={styles.trendingAuthor}>{news.author}</Text>
<Text style={styles.trendingTime}>{news.publishTime}</Text>
</View>
</View>
</TouchableOpacity>
);
};
const categories: Category[] = [
{ id: '1', name: '推荐', icon: '🔥' },
{ id: '2', name: '国内', icon: '🇨🇳' },
{ id: '3', name: '国际', icon: '🌍' },
{ id: '4', name: '科技', icon: '💻' },
{ id: '5', name: '体育', icon: '⚽' },
{ id: '6', name: '财经', icon: '💰' },
{ id: '7', name: '娱乐', icon: '🎬' },
{ id: '8', name: '健康', icon: '🏥' },
];
const trendingNews = newsItems.filter(item => item.isBreaking).slice(0, 2);
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>今日资讯</Text>
<View style={styles.headerActions}>
<TouchableOpacity style={styles.searchButton} onPress={handleSearch}>
<Text style={styles.searchIcon}>🔍</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bellButton}>
<Text style={styles.bellIcon}>🔔</Text>
</TouchableOpacity>
</View>
</View>
<ScrollView style={styles.content}>
{/* 搜索栏 */}
<View style={styles.searchContainer}>
<Text style={styles.searchIcon}>🔍</Text>
<Text style={styles.searchPlaceholder}>搜索您关心的新闻</Text>
</View>
{/* 标签页切换 */}
<View style={styles.tabContainer}>
<TouchableOpacity
style={[styles.tab, activeTab === 'news' && styles.activeTab]}
onPress={() => setActiveTab('news')}
>
<Text style={[styles.tabText, activeTab === 'news' && styles.activeTabText]}>最新新闻</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'favorites' && styles.activeTab]}
onPress={() => setActiveTab('favorites')}
>
<Text style={[styles.tabText, activeTab === 'favorites' && styles.activeTabText]}>收藏</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'history' && styles.activeTab]}
onPress={() => setActiveTab('history')}
>
<Text style={[styles.tabText, activeTab === 'history' && styles.activeTabText]}>阅读历史</Text>
</TouchableOpacity>
</View>
{/* 内容区域 */}
{activeTab === 'news' && (
<>
{/* 热点新闻轮播 */}
<Text style={styles.sectionTitle}>热点新闻</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.trendingContainer}>
<View style={styles.trendingList}>
{trendingNews.map(item => (
<TrendingNewsCard
key={item.id}
news={item}
onPress={handleReadMore}
/>
))}
</View>
</ScrollView>
{/* 新闻分类 */}
<Text style={styles.sectionTitle}>新闻分类</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.categoriesContainer}>
<View style={styles.categoriesList}>
{categories.map(category => (
<CategoryCard
key={category.id}
category={category}
onPress={() => Alert.alert('类别筛选', `显示 ${category.name} 类别的新闻`)}
/>
))}
</View>
</ScrollView>
{/* 新闻列表标题 */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>最新新闻</Text>
<Text style={styles.newsCount}>({newsItems.length} 条新闻)</Text>
</View>
{/* 新闻列表 */}
<FlatList
data={newsItems}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<NewsCard
news={item}
onLike={handleLike}
onBookmark={handleBookmark}
onReadMore={handleReadMore}
/>
)}
showsVerticalScrollIndicator={false}
/>
</>
)}
{activeTab === 'favorites' && (
<View>
<Text style={styles.sectionTitle}>收藏的新闻</Text>
{getFavoriteNews().length > 0 ? (
<FlatList
data={getFavoriteNews()}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<NewsCard
news={item}
onLike={handleLike}
onBookmark={handleBookmark}
onReadMore={handleReadMore}
/>
)}
showsVerticalScrollIndicator={false}
/>
) : (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>暂无收藏的新闻</Text>
<Text style={styles.emptyStateSubtext}>点击新闻右下角的星星按钮可收藏</Text>
</View>
)}
</View>
)}
{activeTab === 'history' && (
<View>
<Text style={styles.sectionTitle}>阅读历史</Text>
{getReadHistory().length > 0 ? (
<FlatList
data={getReadHistory()}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<NewsCard
news={item}
onLike={handleLike}
onBookmark={handleBookmark}
onReadMore={handleReadMore}
/>
)}
showsVerticalScrollIndicator={false}
/>
) : (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>暂无阅读历史</Text>
<Text style={styles.emptyStateSubtext}>阅读过的新闻会在这里显示</Text>
</View>
)}
</View>
)}
{/* 推荐新闻 */}
<View style={styles.recommendationSection}>
<Text style={styles.sectionTitle}>为您推荐</Text>
<View style={styles.recommendationItem}>
<View style={styles.recommendationImage}>
<Text style={styles.recommendationImageText}>📰</Text>
</View>
<View style={styles.recommendationInfo}>
<Text style={styles.recommendationTitle}>人工智能在医疗领域的应用前景广阔</Text>
<Text style={styles.recommendationSummary}>AI技术正在改变传统医疗模式,提高诊断效率和准确性</Text>
<View style={styles.recommendationMeta}>
<Text style={styles.recommendationCategory}>科技</Text>
<Text style={styles.recommendationTime}>2小时前</Text>
</View>
</View>
</View>
</View>
{/* 使用说明 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>使用说明</Text>
<Text style={styles.infoText}>• 点赞新闻:点击👍按钮</Text>
<Text style={styles.infoText}>• 收藏新闻:点击⭐按钮</Text>
<Text style={styles.infoText}>• 阅读历史:自动保存最近阅读的新闻</Text>
<Text style={styles.infoText}>• 本地存储:所有数据都保存在您的设备上</Text>
</View>
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>🏠</Text>
<Text style={styles.navText}>首页</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>🔥</Text>
<Text style={styles.navText}>热点</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>⭐</Text>
<Text style={styles.navText}>收藏</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>👤</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
headerActions: {
flexDirection: 'row',
alignItems: 'center',
},
searchButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
searchIcon: {
fontSize: 18,
color: '#64748b',
},
bellButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
},
bellIcon: {
fontSize: 18,
color: '#64748b',
},
content: {
flex: 1,
padding: 16,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
borderRadius: 20,
paddingVertical: 12,
paddingHorizontal: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
searchPlaceholder: {
fontSize: 14,
color: '#94a3b8',
marginLeft: 12,
flex: 1,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
marginVertical: 12,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
newsCount: {
fontSize: 14,
color: '#64748b',
},
trendingContainer: {
marginBottom: 16,
},
trendingList: {
flexDirection: 'row',
},
trendingCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
flexDirection: 'row',
padding: 12,
marginRight: 12,
width: width * 0.8,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
trendingImage: {
width: 80,
height: 80,
borderRadius: 8,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
trendingImageText: {
fontSize: 30,
},
trendingContent: {
flex: 1,
},
trendingTitle: {
fontSize: 14,
fontWeight: '600',
color: '#1e293b',
marginBottom: 4,
},
trendingSummary: {
fontSize: 12,
color: '#64748b',
marginBottom: 8,
flex: 1,
},
trendingMeta: {
flexDirection: 'row',
justifyContent: 'space-between',
},
trendingAuthor: {
fontSize: 11,
color: '#94a3b8',
},
trendingTime: {
fontSize: 11,
color: '#94a3b8',
},
categoriesContainer: {
marginBottom: 16,
},
categoriesList: {
flexDirection: 'row',
},
categoryCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginRight: 12,
alignItems: 'center',
width: 70,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
categoryIcon: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#dbeafe',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 8,
},
categoryIconText: {
fontSize: 16,
color: '#3b82f6',
},
categoryName: {
fontSize: 12,
color: '#1e293b',
textAlign: 'center',
},
newsCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
newsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
categoryBadge: {
backgroundColor: '#dbeafe',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
categoryText: {
fontSize: 12,
color: '#3b82f6',
fontWeight: '500',
},
breakingBadge: {
backgroundColor: '#fed7d7',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
breakingText: {
fontSize: 12,
color: '#dc2626',
fontWeight: '500',
},
newsTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 8,
},
newsSummary: {
fontSize: 14,
color: '#64748b',
lineHeight: 20,
marginBottom: 12,
},
newsMeta: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
authorInfo: {
flexDirection: 'row',
alignItems: 'center',
},
authorName: {
fontSize: 12,
color: '#64748b',
marginRight: 12,
},
publishTime: {
fontSize: 12,
color: '#94a3b8',
},
readTime: {
fontSize: 12,
color: '#94a3b8',
},
newsActions: {
flexDirection: 'row',
justifyContent: 'space-between',
},
actionButton: {
flexDirection: 'row',
alignItems: 'center',
},
actionIcon: {
fontSize: 16,
color: '#64748b',
marginRight: 4,
},
likedIcon: {
color: '#ef4444',
},
bookmarkedIcon: {
color: '#3b82f6',
},
actionText: {
fontSize: 12,
color: '#64748b',
},
recommendationSection: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
recommendationItem: {
flexDirection: 'row',
alignItems: 'center',
},
recommendationImage: {
width: 60,
height: 60,
borderRadius: 8,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
recommendationImageText: {
fontSize: 24,
},
recommendationInfo: {
flex: 1,
},
recommendationTitle: {
fontSize: 14,
fontWeight: '500',
color: '#1e293b',
marginBottom: 4,
},
recommendationSummary: {
fontSize: 12,
color: '#64748b',
marginBottom: 6,
},
recommendationMeta: {
flexDirection: 'row',
justifyContent: 'space-between',
},
recommendationCategory: {
fontSize: 11,
color: '#3b82f6',
backgroundColor: '#dbeafe',
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 6,
},
recommendationTime: {
fontSize: 11,
color: '#94a3b8',
},
infoCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
infoTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 12,
},
infoText: {
fontSize: 14,
color: '#64748b',
lineHeight: 22,
marginBottom: 8,
},
emptyState: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 40,
},
emptyStateText: {
fontSize: 16,
color: '#64748b',
marginBottom: 8,
},
emptyStateSubtext: {
fontSize: 14,
color: '#94a3b8',
},
tabContainer: {
flexDirection: 'row',
backgroundColor: '#ffffff',
borderRadius: 20,
padding: 4,
marginBottom: 16,
},
tab: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderRadius: 16,
},
activeTab: {
backgroundColor: '#3b82f6',
},
tabText: {
fontSize: 14,
color: '#64748b',
},
activeTabText: {
color: '#ffffff',
fontWeight: '500',
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6',
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
activeNavText: {
color: '#3b82f6',
fontWeight: '500',
},
});
export default NewsApp;

打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

本文分析了基于React Native的新闻应用实现方案,重点探讨了架构设计、状态管理和数据持久化策略。应用采用组件化架构,将主应用组件与新闻列表、功能按钮等子组件分离,实现关注点分离。状态管理使用useState钩子和TypeScript类型定义,确保数据结构的类型安全。通过AsyncStorage实现数据持久化,在组件挂载时加载本地数据,并同步更新内存状态和本地存储。文章还介绍了新闻管理功能、交互设计优化以及FlatList渲染优化等性能提升策略,为开发功能完备的跨平台新闻应用提供了参考方案。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)