这个搜索结果页面采用了模块化的组件架构,通过清晰的职责分离实现了代码的复用性和可维护性。核心组件 SearchCard 作为独立的可复用单元,负责单个搜索结果的展示和交互,而 SearchResultPage 作为主容器,负责搜索功能、搜索历史管理和结果列表的渲染。

可复用 SearchCard 组件

const SearchCard: React.FC<{ 
  result: any, 
  onBookmark: (id: number) => void, 
  onShare: (id: number) => void, 
  onPress: (id: number) => void 
}> = ({ result, onBookmark, onShare, onPress }) => {
  // 组件实现...
};

SearchCard 组件设计为高度可配置的通用组件,通过 props 接收搜索结果数据和三个回调函数:收藏、分享和点击详情。这种设计使得组件可以在不同场景中灵活使用,只需传入不同的参数即可实现不同的功能。组件内部包含了结果的图片、分类、标题、摘要、作者信息和操作按钮等完整的展示内容。

数据结构

const SEARCH_RESULTS = [
  {
    id: 1,
    title: '科技发展新趋势:人工智能引领未来',
    summary: '人工智能技术正在快速发展,为各行各业带来前所未有的变化和机遇。',
    author: '张记者',
    time: '2小时前',
    category: '科技',
    readTime: '5分钟',
    image: 'https://picsum.photos/300/200?random=1',
    comments: 24,
    isBookmarked: false,
  },
  // 更多结果...
];

数据结构设计全面而详细,包含了文章的基本信息、内容摘要、作者信息、阅读时间、评论数和收藏状态等。这种结构化的数据设计为后续的功能扩展和数据管理提供了便利,同时也使得搜索功能的实现变得简单直接。

状态管理

const [results, setResults] = useState(SEARCH_RESULTS);
const [searchQuery, setSearchQuery] = useState('');
const [searchHistory, setSearchHistory] = useState(['科技', '教育', '健康']);

使用 useState Hook 管理三个状态:

  1. results:搜索结果列表,初始值为模拟数据 SEARCH_RESULTS
  2. searchQuery:搜索查询,初始值为空字符串
  3. searchHistory:搜索历史,初始值为 [‘科技’, ‘教育’, ‘健康’]

这种状态管理方式简洁明了,适合处理用户交互产生的状态变化。

搜索功能

const handleSearch = (query: string) => {
  if (query.trim() !== '') {
    // 在实际应用中,这里应该调用API进行搜索
    // 暂时过滤现有数据作为演示
    const filtered = SEARCH_RESULTS.filter(item =>
      item.title.toLowerCase().includes(query.toLowerCase()) || 
      item.summary.toLowerCase().includes(query.toLowerCase()) || 
      item.category.toLowerCase().includes(query.toLowerCase())
    );
    setResults(filtered);
    
    // 添加到搜索历史
    if (!searchHistory.includes(query)) {
      setSearchHistory(prev => [query, ...prev.slice(0, 4)]);
    }
  } else {
    setResults(SEARCH_RESULTS);
  }
};

搜索功能通过 filter 方法实现,根据搜索查询过滤结果列表,支持按标题、摘要和分类进行搜索。同时,搜索功能还会将新的搜索查询添加到搜索历史中,并限制历史记录的数量为5条。这种实现方式模拟了真实应用中的搜索功能,为后续的API集成做好了准备。

交互

const handleBookmark = (id: number) => {
  setResults(results.map(item =>
    item.id === id ? { ...item, isBookmarked: !item.isBookmarked } : item
  ));
};

const handleShare = (id: number) => {
  const article = results.find(n => n.id === id);
  Alert.alert('分享', `分享文章: ${article?.title}`);
};

const handleArticlePress = (id: number) => {
  Alert.alert('阅读', `阅读文章 ID: ${id}`);
};

交互功能包括收藏、分享和查看详情:

  • handleBookmark 函数通过 map 方法更新指定文章的收藏状态
  • handleShare 函数通过 Alert 显示分享确认信息
  • handleArticlePress 函数通过 Alert 显示查看详情的确认信息

这些交互功能为用户提供了完整的操作体验,同时也为后续的功能扩展(如跳转到详情页、集成系统分享)做好了准备。

搜索结果

<View style={styles.searchCard}>
  <TouchableOpacity onPress={() => onPress(result.id)}>
    <Image source={{ uri: result.image }} style={styles.resultImage} />
  </TouchableOpacity>
  <View style={styles.resultContent}>
    {/* 内容 */}
  </View>
</View>

搜索结果卡片采用了左右布局结构,左侧是文章图片,右侧是详细信息。这种布局方式在新闻和资讯类应用中非常常见,能够清晰地展示信息,同时保持界面的整洁和有序。

响应式

const { width } = Dimensions.get('window');

通过 Dimensions.get('window') 获取屏幕宽度,为后续的响应式布局计算提供基础。虽然在当前实现中没有直接使用这个值进行动态计算,但这种模式为后续的布局调整(如适配不同屏幕尺寸)预留了扩展空间。

  1. 组件映射

    • SafeAreaViewSafeAreaFlex 容器
    • ScrollViewListScroll 组件
    • TouchableOpacityButtonText + 手势事件
    • ImageImage 组件
    • ViewFlex 容器
    • TextText 组件
    • TextInputInput 组件
    • AlertDialog 组件
  2. 图片加载适配

    • React Native 中使用 source={{ uri: result.image }} 加载网络图片
    • 鸿蒙系统中可以使用 Image 组件的 src 属性,语法类似但可能需要调整

这个搜索结果页面展示了 React Native 跨端开发的核心技术要点,通过合理的组件设计、清晰的状态管理、强大的搜索功能和细致的样式管理,实现了一个功能完整、用户体验良好的搜索结果页面。在鸿蒙系统适配方面,通过组件映射、样式适配和布局调整,可以快速实现跨端迁移,保持功能和视觉效果的一致性。


本次解析的代码是一套基于React Native原生API开发的资讯类应用搜索结果页核心实现,聚焦资讯类产品的搜索核心场景,打造了智能搜索栏+搜索历史/推荐双态展示+资讯卡片+底部导航的标准化移动端页面结构,同时整合了关键词搜索、历史快捷选择、收藏、分享、无结果兜底等资讯类应用高频交互能力。项目全程采用函数式组件+useState轻量状态管理,遵循组件化解耦状态驱动视图核心思想,无任何第三方UI/业务库依赖,所有实现均基于React Native跨平台原生能力。

本项目的技术选型围绕跨端兼容性原生体验工程可维护性三大核心原则展开,所有选用的组件、API、语法均为React Native与鸿蒙平台的通用能力,无任何平台专属特性,为鸿蒙跨端落地奠定了极简的工程基础,同时贴合资讯类应用“轻量、高效、流畅”的产品特性,核心技术基底设计如下:

1. 原生组件

选用SafeAreaViewViewTextTouchableOpacityScrollViewImageTextInput等React Native核心原生组件,这类组件均已完成React Native for HarmonyOS的深度桥接,底层会被编译为对应平台的原生组件(鸿蒙Component、Android View、iOS UIView),基于原生渲染引擎执行,彻底规避WebView渲染的性能损耗。其中TextInput的输入交互、Image的远程URI加载、ScrollView的纵向滚动、TouchableOpacity的触摸反馈,在鸿蒙平台均能实现原生级体验;SafeAreaView会自动桥接鸿蒙的安全区域适配能力,兼容鸿蒙设备的刘海屏、折叠屏、底部导航栏,无需手动计算安全区域间距,多端布局适配一步到位。

2. 状态管理

采用React Hooks的useState实现全页面的状态管理,包括资讯原始数据、搜索关键词、搜索历史三大核心状态,严格遵循React状态不可变单向数据流原则。这种轻量状态管理方式与鸿蒙ArkUI的@State/@Prop状态管理理念高度同源——均通过状态变化驱动视图重渲染,无复杂的状态流转逻辑。在鸿蒙跨端开发中,无需重构状态管理代码,即可保证多端的状态与视图一致性,大幅降低跨端开发的学习与迁移成本,同时轻量的状态设计让资讯类应用的页面渲染更高效,适配鸿蒙设备的性能要求。


本页面的核心设计亮点是基于搜索关键词的双态页面展示——无搜索关键词时展示「搜索历史+推荐内容」,有搜索关键词时展示「精准搜索结果」,两种状态的切换完全由searchQuery状态驱动,同时整合了关键词模糊搜索搜索历史快捷选择无结果兜底等资讯类搜索核心能力,所有逻辑均为通用JavaScript代码,在鸿蒙平台的JS引擎中可直接执行,无跨端差异,核心实现要点如下:

1. 核心状态初始化

通过三个独立的useState钩子初始化核心状态,实现资讯数据、搜索关键词、搜索历史的解耦管理,每个状态对应独立的业务维度,拥有专属的状态更新方法,便于后续拓展单一维度的逻辑,核心代码如下:

// 资讯原始数据,直接复用模拟数据,内置收藏等交互状态
const [results, setResults] = useState(SEARCH_RESULTS);
// 搜索关键词,控制页面双态切换的核心状态
const [searchQuery, setSearchQuery] = useState('');
// 搜索历史,存储用户过往搜索关键词,限制最大条数
const [searchHistory, setSearchHistory] = useState(['科技', '教育', '健康']);

独立的状态设计让视图与状态的绑定更清晰,在鸿蒙平台重渲染时,仅会更新关联的视图区域,避免全量重渲染,提升鸿蒙设备的渲染性能,同时符合资讯类应用“快速响应”的产品需求。

2. 双态页面切换

通过三元表达式判断searchQuery是否为空,实现「搜索历史+推荐内容」与「精准搜索结果」的页面双态切换,核心渲染逻辑如下:

{searchQuery ? (
  // 有搜索关键词:展示搜索结果模块
  <>...</>
) : (
  // 无搜索关键词:展示搜索历史+推荐内容模块
  <>...</>
)}

这种实现方式完全基于React的状态驱动视图思想,无任何手动DOM操作,所有视图切换均由searchQuery状态变化触发。在鸿蒙平台中,三元表达式为通用JavaScript语法,视图切换的底层由React Native与鸿蒙的桥接层处理,转换为鸿蒙ArkUI的条件渲染逻辑,保证多端的切换体验一致。

3. 关键词模糊搜索

定义handleSearch方法实现关键词模糊搜索,贴合资讯类应用用户的搜索习惯,支持对资讯的标题、摘要、分类三个维度进行关键词匹配,核心逻辑如下:

const filtered = SEARCH_RESULTS.filter(item => 
  item.title.toLowerCase().includes(query.toLowerCase()) ||
  item.summary.toLowerCase().includes(query.toLowerCase()) ||
  item.category.toLowerCase().includes(query.toLowerCase())
);
setResults(filtered);

通过toLowerCase()实现大小写不敏感搜索,提升用户搜索体验;采用Array.prototype.filter通用数组方法实现筛选,无平台专属API,在鸿蒙、Android、iOS的JS引擎中均能正常执行。同时方法中预留了真实API调用的注释,后续对接后端搜索接口时,仅需替换筛选逻辑,无需修改其他业务代码,工程可维护性强。

4. 搜索历史管理

handleSearch方法中整合搜索历史管理逻辑,实现关键词去重与最大长度限制(最多保存5条),贴合资讯类应用的实际产品需求,核心逻辑如下:

if (!searchHistory.includes(query)) {
  setSearchHistory(prev => [query, ...prev.slice(0, 4)]);
}

通过!searchHistory.includes(query)实现关键词去重,避免历史记录重复;通过扩展运算符...将新关键词添加到历史头部,再通过slice(0,4)截取前4条原有历史,保证搜索历史最多保存5条。所有操作均严格遵循状态不可变原则,通过复制原有状态并修改的方式更新,避免直接操作原数组导致的状态异常,这种实现方式与鸿蒙ArkUI的状态更新规范一致,跨端无兼容问题。

5. 搜索历史快捷选择

为搜索历史项绑定点击事件,实现一键填充关键词+触发搜索的快捷操作,核心逻辑如下:

const selectFromHistory = (query: string) => {
  setSearchQuery(query);
  handleSearch(query);
};

用户点击历史关键词时,先将关键词填充到搜索输入框(更新searchQuery状态),再自动触发搜索方法,无需用户手动输入与提交,大幅提升资讯类应用的搜索操作效率。该逻辑为通用JavaScript方法,在鸿蒙平台中,TouchableOpacity的点击事件会桥接为鸿蒙的原生触摸事件,方法调用无跨端差异。

6. 无结果兜底

在搜索结果模块中添加无结果兜底逻辑,当筛选后的结果长度为0时,展示友好的无结果提示,核心渲染逻辑如下:

{results.length > 0 ? (
  results.map(result => <SearchCard key={result.id} {...props} />)
) : (
  <View style={styles.noResultContainer}>
    <Text style={styles.noResultText}>未找到相关结果</Text>
    <Text style={styles.noResultSubtext}>尝试使用其他关键词搜索</Text>
  </View>
)}

无结果兜底是资讯类应用的必备能力,该实现方式通过判断数组长度实现,无平台专属特性,在鸿蒙平台的渲染效果与其他端一致,同时友好的提示文案能提升用户的搜索体验,避免无反馈的尴尬场景。

7. 搜索输入交互

为搜索输入框TextInput整合回车提交搜索清空按钮两大高频交互能力,贴合移动端用户的输入习惯:

  • 回车提交:通过onSubmitEditing事件实现,用户在输入框中按下回车时自动触发handleSearch方法,无需点击额外的搜索按钮;
  • 清空按钮:通过条件渲染实现,当searchQuery不为空时展示清空按钮,点击后调用clearSearch方法,重置searchQuery为空并恢复原始资讯数据,实现一键清空搜索。

TextInput的所有事件与属性均为React Native原生能力,且已完成鸿蒙桥接,在鸿蒙平台中,回车事件会桥接为鸿蒙的原生键盘回车事件,清空按钮的触摸反馈为鸿蒙原生效果,多端交互体验高度一致。


真实演示案例代码:




// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Image, Dimensions, Alert, TextInput } from 'react-native';

// 图标库
const ICONS = {
  home: '🏠',
  search: '🔍',
  user: '👤',
  news: '📰',
  bookmark: '🔖',
  share: '📤',
  time: '🕒',
  close: '❌',
};

const { width } = Dimensions.get('window');

// 模拟搜索结果数据
const SEARCH_RESULTS = [
  {
    id: 1,
    title: '科技发展新趋势:人工智能引领未来',
    summary: '人工智能技术正在快速发展,为各行各业带来前所未有的变化和机遇。',
    author: '张记者',
    time: '2小时前',
    category: '科技',
    readTime: '5分钟',
    image: 'https://picsum.photos/300/200?random=1',
    comments: 24,
    isBookmarked: false,
  },
  {
    id: 2,
    title: '环保政策新动向:绿色能源成焦点',
    summary: '政府发布新的环保政策,推动绿色能源产业发展,助力碳中和目标。',
    author: '李编辑',
    time: '4小时前',
    category: '环保',
    readTime: '3分钟',
    image: 'https://picsum.photos/300/200?random=2',
    comments: 18,
    isBookmarked: true,
  },
  {
    id: 3,
    title: '教育改革新举措:素质教育全面推行',
    summary: '教育部宣布全面推行素质教育,注重学生全面发展和创新能力培养。',
    author: '王老师',
    time: '6小时前',
    category: '教育',
    readTime: '4分钟',
    image: 'https://picsum.photos/300/200?random=3',
    comments: 32,
    isBookmarked: false,
  },
  {
    id: 4,
    title: '健康生活:运动与营养的重要性',
    summary: '专家强调运动与均衡营养对健康的重要性,提出科学生活方式建议。',
    author: '陈医生',
    time: '8小时前',
    category: '健康',
    readTime: '6分钟',
    image: 'https://picsum.photos/300/200?random=4',
    comments: 15,
    isBookmarked: false,
  },
  {
    id: 5,
    title: '经济发展:新兴产业蓬勃发展',
    summary: '新兴产业如新能源汽车、生物医药等领域呈现强劲增长态势。',
    author: '刘分析师',
    time: '10小时前',
    category: '经济',
    readTime: '7分钟',
    image: 'https://picsum.photos/300/200?random=5',
    comments: 27,
    isBookmarked: true,
  },
  {
    id: 6,
    title: '文化传承:传统艺术焕发新生',
    summary: '传统文化与现代技术结合,让传统艺术形式焕发新的生命力。',
    author: '赵学者',
    time: '12小时前',
    category: '文化',
    readTime: '4分钟',
    image: 'https://picsum.photos/300/200?random=6',
    comments: 9,
    isBookmarked: false,
  },
  {
    id: 7,
    title: '国际关系:全球合作应对挑战',
    summary: '各国加强合作,共同应对气候变化、疫情等全球性挑战。',
    author: '外交专家',
    time: '14小时前',
    category: '国际',
    readTime: '5分钟',
    image: 'https://picsum.photos/300/200?random=7',
    comments: 16,
    isBookmarked: false,
  },
  {
    id: 8,
    title: '科技创新:量子计算新突破',
    summary: '科学家在量子计算领域取得重大进展,为未来计算技术奠定基础。',
    author: '科技日报',
    time: '16小时前',
    category: '科技',
    readTime: '6分钟',
    image: 'https://picsum.photos/300/200?random=8',
    comments: 31,
    isBookmarked: true,
  },
];

const SearchCard: React.FC<{ 
  result: any, 
  onBookmark: (id: number) => void,
  onShare: (id: number) => void,
  onPress: (id: number) => void
}> = ({ result, onBookmark, onShare, onPress }) => {
  return (
    <View style={styles.searchCard}>
      <TouchableOpacity onPress={() => onPress(result.id)}>
        <Image source={{ uri: result.image }} style={styles.resultImage} />
      </TouchableOpacity>
      <View style={styles.resultContent}>
        <View style={styles.resultHeader}>
          <Text style={styles.category}>{result.category}</Text>
          <View style={styles.timeRow}>
            <Text style={styles.timeIcon}>{ICONS.time}</Text>
            <Text style={styles.readTime}>{result.readTime}</Text>
          </View>
        </View>
        <TouchableOpacity onPress={() => onPress(result.id)}>
          <Text style={styles.title}>{result.title}</Text>
          <Text style={styles.summary}>{result.summary}</Text>
        </TouchableOpacity>
        <View style={styles.resultFooter}>
          <View style={styles.authorInfo}>
            <Text style={styles.author}>{ICONS.user} {result.author}</Text>
            <Text style={styles.postTime}>{result.time}</Text>
          </View>
          <View style={styles.actions}>
            <TouchableOpacity style={styles.actionButton} onPress={() => onBookmark(result.id)}>
              <Text style={styles.actionIcon}>{result.isBookmarked ? '★' : ICONS.bookmark}</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.actionButton} onPress={() => onShare(result.id)}>
              <Text style={styles.actionIcon}>{ICONS.share}</Text>
            </TouchableOpacity>
            <View style={styles.commentRow}>
              <Text style={styles.commentIcon}>💬</Text>
              <Text style={styles.commentCount}>{result.comments}</Text>
            </View>
          </View>
        </View>
      </View>
    </View>
  );
};

const SearchResultPage: React.FC = () => {
  const [results, setResults] = useState(SEARCH_RESULTS);
  const [searchQuery, setSearchQuery] = useState('');
  const [searchHistory, setSearchHistory] = useState(['科技', '教育', '健康']);

  const handleSearch = (query: string) => {
    if (query.trim() !== '') {
      // 在实际应用中,这里应该调用API进行搜索
      // 暂时过滤现有数据作为演示
      const filtered = SEARCH_RESULTS.filter(item => 
        item.title.toLowerCase().includes(query.toLowerCase()) ||
        item.summary.toLowerCase().includes(query.toLowerCase()) ||
        item.category.toLowerCase().includes(query.toLowerCase())
      );
      setResults(filtered);
      
      // 添加到搜索历史
      if (!searchHistory.includes(query)) {
        setSearchHistory(prev => [query, ...prev.slice(0, 4)]);
      }
    } else {
      setResults(SEARCH_RESULTS);
    }
  };

  const handleBookmark = (id: number) => {
    setResults(results.map(item => 
      item.id === id ? { ...item, isBookmarked: !item.isBookmarked } : item
    ));
  };

  const handleShare = (id: number) => {
    const article = results.find(n => n.id === id);
    Alert.alert('分享', `分享文章: ${article?.title}`);
  };

  const handleArticlePress = (id: number) => {
    Alert.alert('阅读', `阅读文章 ID: ${id}`);
  };

  const clearSearch = () => {
    setSearchQuery('');
    setResults(SEARCH_RESULTS);
  };

  const selectFromHistory = (query: string) => {
    setSearchQuery(query);
    handleSearch(query);
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部搜索栏 */}
      <View style={styles.header}>
        <View style={styles.searchContainer}>
          <Text style={styles.searchIcon}>{ICONS.search}</Text>
          <TextInput
            style={styles.searchInput}
            placeholder="搜索新闻、文章或话题..."
            value={searchQuery}
            onChangeText={setSearchQuery}
            onSubmitEditing={() => handleSearch(searchQuery)}
          />
          {searchQuery ? (
            <TouchableOpacity style={styles.clearButton} onPress={clearSearch}>
              <Text style={styles.clearIcon}>{ICONS.close}</Text>
            </TouchableOpacity>
          ) : null}
        </View>
      </View>

      {/* 搜索结果 */}
      {searchQuery ? (
        <>
          <Text style={styles.sectionTitle}>搜索结果</Text>
          <Text style={styles.sectionSubtitle}>找到 {results.length} 条相关结果</Text>
          
          <ScrollView style={styles.content}>
            {results.length > 0 ? (
              results.map(result => (
                <SearchCard 
                  key={result.id}
                  result={result}
                  onBookmark={handleBookmark}
                  onShare={handleShare}
                  onPress={handleArticlePress}
                />
              ))
            ) : (
              <View style={styles.noResultContainer}>
                <Text style={styles.noResultText}>未找到相关结果</Text>
                <Text style={styles.noResultSubtext}>尝试使用其他关键词搜索</Text>
              </View>
            )}
          </ScrollView>
        </>
      ) : (
        <>
          <Text style={styles.sectionTitle}>热门搜索</Text>
          <Text style={styles.sectionSubtitle}>大家都在搜什么</Text>
          
          <ScrollView style={styles.content}>
            <View style={styles.historyContainer}>
              {searchHistory.map((item, index) => (
                <TouchableOpacity 
                  key={index} 
                  style={styles.historyItem}
                  onPress={() => selectFromHistory(item)}
                >
                  <Text style={styles.historyText}>{item}</Text>
                </TouchableOpacity>
              ))}
            </View>
            
            <Text style={styles.recommendTitle}>推荐内容</Text>
            {SEARCH_RESULTS.slice(0, 4).map(result => (
              <SearchCard 
                key={result.id}
                result={result}
                onBookmark={handleBookmark}
                onShare={handleShare}
                onPress={handleArticlePress}
              />
            ))}
          </ScrollView>
        </>
      )}

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={[styles.navIcon, styles.activeNavIcon]}>{ICONS.home}</Text>
          <Text style={[styles.navText, styles.activeNavText]}>首页</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.news}</Text>
          <Text style={styles.navText}>资讯</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.bookmark}</Text>
          <Text style={styles.navText}>收藏</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>{ICONS.user}</Text>
          <Text style={styles.navText}>我的</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  searchContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f1f5f9',
    borderRadius: 20,
    paddingHorizontal: 12,
  },
  searchIcon: {
    fontSize: 18,
    color: '#64748b',
    marginRight: 8,
  },
  searchInput: {
    flex: 1,
    paddingVertical: 12,
    fontSize: 16,
    color: '#1e293b',
  },
  clearButton: {
    padding: 4,
  },
  clearIcon: {
    fontSize: 18,
    color: '#94a3b8',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  sectionTitle: {
    fontSize: 22,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
    marginLeft: 16,
  },
  sectionSubtitle: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 16,
    marginLeft: 16,
  },
  searchCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    marginBottom: 16,
    overflow: 'hidden',
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  resultImage: {
    width: '100%',
    height: 160,
  },
  resultContent: {
    padding: 16,
  },
  resultHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  category: {
    fontSize: 12,
    color: '#ffffff',
    backgroundColor: '#3b82f6',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
  },
  timeRow: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  timeIcon: {
    fontSize: 12,
    color: '#94a3b8',
    marginRight: 4,
  },
  readTime: {
    fontSize: 12,
    color: '#94a3b8',
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
    lineHeight: 22,
  },
  summary: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 18,
    marginBottom: 12,
  },
  resultFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  authorInfo: {
    flex: 1,
  },
  author: {
    fontSize: 12,
    color: '#94a3b8',
    marginBottom: 4,
  },
  postTime: {
    fontSize: 12,
    color: '#94a3b8',
  },
  actions: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  actionButton: {
    marginLeft: 12,
  },
  actionIcon: {
    fontSize: 16,
    color: '#94a3b8',
  },
  commentRow: {
    flexDirection: 'row',
    alignItems: 'center',
    marginLeft: 12,
  },
  commentIcon: {
    fontSize: 14,
    color: '#94a3b8',
    marginRight: 4,
  },
  commentCount: {
    fontSize: 12,
    color: '#94a3b8',
  },
  historyContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginBottom: 24,
  },
  historyItem: {
    backgroundColor: '#e2e8f0',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    marginRight: 8,
    marginBottom: 8,
  },
  historyText: {
    fontSize: 14,
    color: '#475569',
  },
  recommendTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 16,
  },
  noResultContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    paddingVertical: 40,
  },
  noResultText: {
    fontSize: 18,
    color: '#64748b',
    marginBottom: 8,
  },
  noResultSubtext: {
    fontSize: 14,
    color: '#94a3b8',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
  },
});

export default SearchResultPage;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个基于React Native的资讯类应用搜索结果页面实现方案。采用函数式组件和useState轻量状态管理,通过原生组件构建了包含智能搜索栏、搜索历史、资讯卡片和底部导航的标准页面结构。核心功能包括关键词搜索、历史记录管理、收藏/分享交互等,全部基于跨平台原生能力实现,无第三方依赖。特别设计了搜索关键词的双态展示逻辑,支持搜索结果与推荐内容的智能切换。该方案遵循组件化解耦原则,采用原生渲染引擎保障性能,并针对鸿蒙平台适配进行了组件映射设计,确保跨端兼容性。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

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

更多推荐