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


在移动应用开发领域,招聘应用因其信息密度高、交互复杂度大,成为技术选型的重要考量对象。本文将深入解读一个基于 React Native 开发的招聘应用代码片段,剖析其架构设计、状态管理策略以及在鸿蒙系统上的跨端实现考量。

数据模型

代码首先通过 TypeScript 定义了四个核心数据模型:JobPositionCompanyMessageApplication。这种强类型设计不仅提升了代码的可读性和可维护性,更为跨端开发奠定了坚实基础。在鸿蒙系统的 ArkTS 环境中,类型系统的一致性尤为重要,它确保了数据在不同平台间传递时的准确性,减少了运行时错误的可能性。

// 职位类型
type JobPosition = {
  id: string;
  title: string;
  company: string;
  salary: string;
  location: string;
  experience: string;
  education: string;
  tags: string[];
  postedDate: string;
  logo: string;
  applied: boolean;
};

特别值得注意的是 JobPosition 类型中包含了 tags 数组,这种结构化的数据模型设计反映了职位信息的复杂性,为后续的 UI 渲染和业务逻辑处理提供了清晰的数据结构。

状态管理

应用采用 React Hooks 中的 useState 进行状态管理,这是 React Native 开发中的标准实践。对于职位列表、公司列表、消息列表和投递记录等静态数据,使用 useState 进行初始化后不再修改,这种模式非常适合那些初始化后相对稳定的数据。

const [jobs] = useState<JobPosition[]>([
  {
    id: '1',
    title: '前端开发工程师',
    company: '科技有限公司',
    // ... 其他属性
  },
  // ... 更多职位数据
]);

而对于 activeTab 这样的动态状态,则同时保存了状态值和更新函数。这种状态管理策略在跨端场景下表现出色,因为它不依赖于特定平台的状态管理方案,而是使用了 React 生态的标准 API,确保了在 React Native 和鸿蒙系统上的一致性表现。


响应式

代码通过 Dimensions.get('window') 获取屏幕宽度,为后续的响应式布局提供了基础:

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

这种方式在 React Native 中非常常见,但在鸿蒙系统的跨端开发中,需要特别注意不同设备尺寸的适配。鸿蒙系统的自适应布局能力虽然强大,但与 React Native 的布局模型存在差异,因此在实际开发中需要进行针对性调整。

组件化

应用的布局结构清晰,主要包含职位卡片、消息列表和投递记录三个核心部分。职位卡片通过 renderJobItem 函数实现,消息列表通过 renderMessageItem 函数实现,投递记录通过 renderApplicationItem 函数实现。

const renderJobItem = ({ item }: { item: JobPosition }) => (
  <View style={styles.jobCard}>
    {/* 职位头部 */}
    <View style={styles.jobHeader}>
      {/* ... 职位头部内容 */}
    </View>
    {/* 职位详情 */}
    <View style={styles.jobDetails}>
      {/* ... 职位详情内容 */}
    </View>
    {/* 职位标签 */}
    <View style={styles.jobTags}>
      {/* ... 职位标签内容 */}
    </View>
    {/* 职位底部 */}
    <View style={styles.jobFooter}>
      {/* ... 职位底部内容 */}
    </View>
  </View>
);

这种组件化的布局结构在鸿蒙系统上可以很好地映射到 ArkTS 的组件树中,为跨端开发提供了便利。

代码中使用了 FlatList 组件来渲染职位列表、消息列表和投递记录,这是 React Native 中用于长列表渲染的高性能组件,它通过虚拟化技术只渲染可见区域的内容,大大减少了内存消耗和渲染时间。

在鸿蒙系统上,FlatList 的性能表现同样重要。由于鸿蒙系统对原生组件的渲染速度较快,但对 JavaScript 执行的性能要求较高,因此合理使用 FlatList 的虚拟化机制,减少 JavaScript 线程的计算负担,是确保应用流畅运行的关键。


事件处理

应用实现了职位投递功能,通过 applyForJob 函数处理用户交互:

const applyForJob = (jobId: string) => {
  Alert.alert('投递成功', '职位申请已提交,等待企业回复');
};

这种基于回调函数的事件处理方式,在 React Native 和鸿蒙系统中都能很好地工作。但需要注意的是,在鸿蒙系统中,事件响应的优先级和处理机制可能与 React Native 有所不同,需要进行适当的调整以确保用户体验的一致性。

条件渲染

应用通过条件渲染和样式实现了投递状态的可视化反馈,例如根据职位是否已投递显示不同的按钮样式:

<TouchableOpacity
  style={[styles.applyButton, item.applied && styles.appliedButton]}
  onPress={() => applyForJob(item.id)}
>
  <Text style={[styles.applyButtonText, item.applied && styles.appliedButtonText]}>
    {item.applied ? '已投递' : '投递'}
  </Text>
</TouchableOpacity>

这种方式在 React Native 和鸿蒙系统中都能很好地工作,为用户提供了直观的交互反馈。

标签渲染

应用通过 map 函数渲染职位标签:

<View style={styles.jobTags}>
  {item.tags.map((tag, index) => (
    <View key={index} style={styles.tag}>
      <Text style={styles.tagText}>{tag}</Text>
    </View>
  ))}
</View>

这种方式在 React Native 和鸿蒙系统中都能很好地工作,为用户提供了直观的职位信息。


React Native 的核心组件(如 ViewTextTouchableOpacityFlatList 等)在鸿蒙系统上都有对应的实现,但在某些属性和行为上可能存在差异。例如,FlatList 的滚动性能、TouchableOpacity 的点击反馈等,都需要在鸿蒙系统上进行测试和优化。

在鸿蒙系统上,React Native 应用的性能优化需要考虑以下几点:

  1. 渲染性能:鸿蒙系统对原生组件的渲染速度较快,但对 JavaScript 执行的性能要求较高。因此,应尽量减少 JavaScript 线程的计算负担,将复杂的计算逻辑移至原生层。

  2. 内存管理:招聘应用通常会加载大量职位信息和公司信息,内存消耗较大。在鸿蒙系统上,需要特别注意内存的分配和释放,避免内存泄漏。

  3. 网络请求:招聘应用的网络请求频繁,应合理使用缓存策略,减少网络请求次数,提高应用响应速度。


类型安全

代码使用了 TypeScript 进行类型定义,这不仅提高了代码的可读性,也为跨端开发提供了类型安全保障。在鸿蒙系统的 ArkTS 环境中,类型系统的一致性尤为重要,它确保了数据在不同平台间传递时的准确性。

应用的代码结构清晰,将数据模型、组件逻辑、样式等分离,便于维护和扩展。这种模块化设计在跨端开发中尤为重要,因为它使得平台特定的代码修改可以被隔离在最小范围内。

代码中使用了 Alert 进行用户反馈,这是一种简单有效的错误处理方式。在实际开发中,还应考虑添加更全面的错误处理机制,如网络请求错误、数据解析错误等,以提高应用的稳定性。


应用使用了 React Hooks 中的 useState 进行状态管理,这是 React Native 开发中的标准实践。对于静态数据,使用 useState 进行初始化后不再修改;对于动态状态,则同时保存状态值和更新函数。这种状态管理策略简洁明了,易于理解和维护。

应用通过 renderJobItemrenderMessageItemrenderApplicationItem 函数实现了组件化渲染,这种方式不仅提高了代码的可维护性,也为跨端开发提供了便利。在鸿蒙系统上,这些函数的实现方式可能需要调整,以适应鸿蒙系统的组件生命周期,但核心逻辑是一致的。

  1. 使用核心组件:优先使用 React Native 的核心组件,这些组件在鸿蒙系统上有较好的兼容性。

  2. 避免平台特定代码:尽量避免使用平台特定的 API,如需要使用,应通过条件判断进行隔离。

  3. 性能优化:针对不同平台的性能特点,进行有针对性的优化。

  4. 用户体验:确保在不同平台上的用户体验一致性,包括交互方式、视觉效果等。

  5. 代码质量:使用 TypeScript 进行类型定义,提高代码的可读性和可维护性。

通过对这个 React Native 招聘应用代码片段的深入解读,我们可以看到,一个优秀的跨端应用需要在架构设计、状态管理、性能优化等多个方面进行精心考量。特别是在 React Native 与鸿蒙系统的跨端开发中,需要充分了解两个平台的特性,才能开发出性能优异、用户体验一致的应用。


求职招聘类应用是垂直领域的核心场景,其核心诉求是结构化的职位信息展示、状态驱动的投递交互、多维度的数据呈现,同时对UI的专业性和信息层级的清晰度有极高要求。本文以一个完整的 React Native 招聘页面为例,拆解其数据建模、组件化渲染、状态管理的核心逻辑,并深入探讨该页面向鸿蒙(HarmonyOS)生态迁移的技术要点,为跨端招聘类应用开发提供可落地的实践参考。

该招聘页面采用 React Native 函数式组件+TypeScript 开发范式,融合了招聘场景特有的强类型数据建模、多类型列表渲染、状态驱动的投递交互、差异化的状态样式设计等核心技术点,完全贴合招聘类应用的开发特性。

1. 数据类型

页面通过 TypeScript 定义了 JobPositionCompanyMessageApplication 四类核心招聘业务类型,构建了完整的招聘数据模型:

  • 职位数据精准建模JobPosition 包含职位标题、公司、薪资、地点、经验要求、学历要求、技能标签、发布时间、投递状态等全维度信息,通过 applied: boolean 标记投递状态,tags: string[] 存储技能标签,满足职位展示的核心需求;Application 中通过字面量类型 '已投递' | '已查看' | '面试中' | '已录用' | '未通过' 约束投递状态,保证状态展示的规范性。这类字面量类型在鸿蒙 ArkTS 中可通过 enum 枚举无缝适配,核心数据校验逻辑完全一致。
  • 关联型数据设计:所有类型均包含 id 字段作为唯一标识,保证列表渲染的 key 唯一性;Company 中通过 hot: boolean 标记热门公司,Message 中通过 unread: boolean 标记未读消息,满足招聘场景的差异化展示需求。这种关联型数据结构在鸿蒙端可直接复用,仅需将 TypeScript 的 type 改为 ArkTS 的 interface,核心字段定义无需修改。
  • 类型安全的渲染逻辑renderJobItemrenderMessageItemrenderApplicationItem 等渲染函数通过泛型约束入参类型(如 { item }: { item: Application }),确保渲染逻辑仅处理合规的招聘数据,这种类型约束在鸿蒙端可直接映射为 ForEach 渲染的数据源类型校验,大幅降低跨端适配的调试成本。

2. 列表渲染:

页面采用 FlatList 渲染职位列表、消息列表、投递记录列表,结合 ScrollView horizontal 实现热门公司的横向展示,体现了招聘类应用的列表渲染策略:

  • 虚拟化列表保证性能:职位、消息、投递记录均采用 FlatList 而非基础 ScrollView 渲染,即使后续数据量增大(如数百条职位信息),也能保证滚动流畅性;通过 showsVerticalScrollIndicator={false} 隐藏滚动指示器,提升视觉体验。这种虚拟化渲染策略在鸿蒙端可通过 List 组件实现,FlatListkeyExtractor 对应鸿蒙 ForEach 的第三个参数(唯一标识),核心渲染逻辑完全对齐。
  • 数据截断优化体验:消息列表通过 messages.slice(0, 2) 仅展示前两条,配合“查看全部”按钮引导用户查看完整数据,这种“摘要+完整”的展示策略在鸿蒙端可直接复用,仅需将数组截断逻辑(slice)迁移至 ArkTS 即可。
  • 横向列表展示热门公司:热门公司采用 ScrollView horizontal 实现横向滚动展示,通过固定宽度(width: 140)保证每个公司卡片的一致性,这种横向展示策略在鸿蒙端可通过 Scroll 组件 + direction: Axis.Horizontal 实现,核心布局逻辑无需调整。

3. 状态管理:

页面采用 React 内置的 useState 管理两类核心状态,结合差异化样式实现交互反馈:

  • 全局状态管理:父组件通过 activeTab 管理底部导航的选中状态,点击导航项时更新状态并驱动UI重渲染,这种全局状态在鸿蒙端可通过 @State 装饰器实现,状态更新逻辑(setActiveTab('message')this.activeTab = 'message')完全对齐。
  • 投递状态交互设计:通过 applyForJob 函数处理职位投递逻辑,结合 item.applied 状态控制投递按钮的样式(applyButton/appliedButton)和文字(“投递”/“已投递”),实现“未投递-已投递”的状态切换反馈;投递记录通过 styles[status_${item.status}] 实现不同投递状态的差异化样式,这种动态样式设计在鸿蒙端可通过条件渲染(if/else)或动态样式绑定实现,核心逻辑完全复用。

4. 布局体系:

页面布局完全基于 React Native 的 Flex 布局体系实现,结合招聘场景的信息层级要求,实现专业化的UI展示:

  • 信息层级构建:通过 borderRadius(职位卡片12px、搜索框20px)区分核心组件的视觉权重;利用 elevation(阴影)、backgroundColor 对比实现卡片的层级感;职位卡片内通过 flexDirection: 'row' 实现公司logo、职位信息、投递按钮的横向排列,flex: 1 保证职位信息区域自适应,符合招聘信息的展示规范。这种视觉层级设计在鸿蒙端可通过 shadow 属性和 borderRadius 无缝复刻,仅需将 elevation 替换为鸿蒙的 shadow 相关属性(shadowColor/shadowRadius/shadowOffset)。
  • 技能标签流式布局:职位技能标签通过 flexDirection: 'row' + flexWrap: 'wrap' 实现流式布局,自动换行展示所有技能标签,结合 marginRight/marginBottom 控制标签间距,保证技能标签的展示美观性。这种流式布局在鸿蒙端可通过 FlexDirection.Row + FlexWrap.Wrap 等价实现,核心布局逻辑无需调整。
  • 状态样式差异化:投递状态通过不同的背景色和文字色区分(如“面试中”为绿色系、“未通过”为红色系),未读消息通过 unreadDot 实现红点提示,这种差异化样式设计在鸿蒙端可通过 @Styles 封装不同状态的样式,结合条件渲染实现,核心样式逻辑完全复用。

将该招聘页面迁移至鸿蒙端,核心是“招聘逻辑复用、布局等价复刻、状态交互对齐”,以下从技术维度拆解关键适配点:

1. 技术栈

鸿蒙端基于 ArkTS(TypeScript 超集)开发,与 React Native 的 TypeScript 语法高度兼容,核心差异集中在组件定义与状态管理:

  • 类型定义迁移:React Native 的类型定义可直接转换为 ArkTS 接口,示例如下:
    // React Native TypeScript 类型
    type Application = {
      id: string;
      jobTitle: string;
      company: string;
      status: '已投递' | '已查看' | '面试中' | '已录用' | '未通过';
      applyDate: string;
    };
    
    // 鸿蒙 ArkTS 等价接口
    interface Application {
      id: string;
      jobTitle: string;
      company: string;
      status: '已投递' | '已查看' | '面试中' | '已录用' | '未通过';
      applyDate: string;
    }
    
    // 或通过枚举优化状态管理
    enum ApplicationStatus {
      DELIVERED = '已投递',
      VIEWED = '已查看',
      INTERVIEW = '面试中',
      HIRED = '已录用',
      REJECTED = '未通过'
    }
    
  • 组件结构转换:React Native 的函数式组件转换为鸿蒙的 @Entry @Component 装饰的 struct,状态管理从 useState 改为 @State 装饰器:
    // React Native 组件结构
    const RecruitmentApp: React.FC = () => {
      const [activeTab, setActiveTab] = useState<'home' | 'search' | 'message' | 'profile'>('home');
      // 数据定义、渲染函数...
      return (/* JSX 渲染 */);
    };
    
    // 鸿蒙 ArkTS 等价结构
    @Entry
    @Component
    struct RecruitmentApp {
      @State activeTab: 'home' | 'search' | 'message' | 'profile' = 'home';
      // 数据定义、渲染函数(完全复用)...
      
      build() {
        // ArkUI 渲染(复用 Flex 布局)
      }
    }
    
  • 投递交互逻辑复用applyForJob 函数的核心逻辑(提示投递成功)可直接迁移至鸿蒙端,仅需将 Alert.alert 替换为鸿蒙的 promptAction.showToast
    // React Native 投递逻辑
    const applyForJob = (jobId: string) => {
      Alert.alert('投递成功', '职位申请已提交,等待企业回复');
    };
    
    // 鸿蒙 ArkTS 等价逻辑
    applyForJob(jobId: string) {
      promptAction.showToast({
        message: '职位申请已提交,等待企业回复',
        duration: 2000
      });
    }
    

React Native 原生组件与鸿蒙 ArkUI 组件存在清晰的映射关系,是招聘类应用跨端迁移的核心落地环节:

React Native 组件 鸿蒙 ArkUI 组件 适配核心说明
SafeAreaView SafeArea 均用于适配刘海屏/底部安全区,属性一致
View Column/Row/Stack 鸿蒙通过布局组件替代通用容器,Flex 布局逻辑复用
Text Text 样式属性(fontSize/color等)仅命名规范差异,招聘信息展示完全一致
TouchableOpacity Button/Text(带点击态) 鸿蒙无直接等价组件,可通过 Button 去除默认样式(backgroundColor: Color.Transparent)实现,保证投递、导航点击交互一致性
FlatList List 鸿蒙 List 组件支持虚拟化渲染,keyExtractor 对应 ForEach 的第三个参数
ScrollView (horizontal) Scroll 鸿蒙 Scroll 组件 + direction: Axis.Horizontal 实现热门公司横向滚动
ScrollView (vertical) Scroll 鸿蒙 Scroll 组件 + direction: Axis.Vertical 实现页面主体滚动

以核心的职位列表渲染为例,React Native 实现与鸿蒙 ArkTS 实现的核心映射:

// React Native 职位列表渲染
<FlatList
  data={jobs}
  renderItem={renderJobItem}
  keyExtractor={item => item.id}
  showsVerticalScrollIndicator={false}
/>

// 鸿蒙 ArkTS 等价实现
<List
  data={this.jobs}
  scrollBar={ScrollBar.None}
  cachedCount={2} // 招聘数据预加载,保证滚动流畅
>
  {ForEach(this.jobs, (job) => {
    return this.renderJobItem(job);
  }, job => job.id)}
</List>

3. 样式体系

React Native 的 StyleSheet.create 封装样式的方式,在鸿蒙端可通过 @Styles/@Extend 装饰器实现等价封装,核心样式属性的适配规则如下:

  • 布局属性flex/flexDirection/justifyContent/alignItems 等 Flex 核心属性完全复用,仅鸿蒙需将字符串值改为枚举值(如 justifyContent: 'space-between'justifyContent: FlexAlign.SpaceBetween)。
  • 招聘卡片样式borderRadius 完全复用,保证职位卡片(12px)、搜索框(20px)、消息卡片(12px)的圆角一致性;backgroundColor 直接映射,招聘场景特有的蓝色系(#3b82f6)、绿色系(#10b981)、红色系(#ef4444)可 100% 复用。
  • 投递状态样式适配:React Native 的 styles[status_${item.status}] 动态样式绑定,在鸿蒙端可通过条件渲染实现:
    // 鸿蒙 ArkTS 投递状态样式
    <Text style={this.getStatusStyle(item.status)}>{item.status}</Text>
    
    // 状态样式函数
    getStatusStyle(status: string) {
      switch(status) {
        case '已投递':
          return { backgroundColor: '#dbeafe', color: '#3b82f6' };
        case '已查看':
          return { backgroundColor: '#fef3c7', color: '#f59e0b' };
        case '面试中':
          return { backgroundColor: '#d1fae5', color: '#10b981' };
        case '已录用':
          return { backgroundColor: '#ddd6fe', color: '#8b5cf6' };
        case '未通过':
          return { backgroundColor: '#fee2e2', color: '#ef4444' };
        default:
          return {};
      }
    }
    
  • 阴影与层级适配:React Native 的 elevation 替换为鸿蒙的 shadow 属性(shadowColor/shadowRadius/shadowOffset),保证职位卡片、消息卡片的阴影层级;position: 'absolute' 实现的未读红点(unreadDot)对应鸿蒙的 position: Position.Absolute,位置属性直接映射。

招聘类应用对滚动流畅度、交互响应速度要求极高,跨端迁移需重点保证以下体验一致性:

  • 滚动性能优化:鸿蒙的 List 组件默认性能优异,职位列表可通过 cachedCount: 2 配置预加载数量,保证数百条职位信息的滚动流畅;关闭滚动指示器(scrollBar: ScrollBar.None),保持招聘应用的简洁专业视觉。
  • 交互响应优化:React Native 的 TouchableOpacity 点击反馈在鸿蒙端可通过 Button 组件的 stateEffect: true 实现,保证投递按钮、导航项、公司卡片等核心操作的点击反馈一致性;底部导航的选中态(activeNavIcon/activeNavText)可通过 @State 驱动样式变化,逻辑完全复用。
  • 招聘数据体验优化:鸿蒙端可利用 @ohos.data.preferences 实现投递状态、未读消息状态的本地缓存,补充 React Native 版本的本地存储能力;利用鸿蒙的 Badge 组件优化未读消息的红点展示,提升鸿蒙端的原生体验。

从该 React Native 招聘页面的鸿蒙适配过程中,可提炼出招聘类应用跨端开发的通用方法论:

页面的投递逻辑(applyForJob)、数据格式化、状态样式映射等纯业务逻辑与 React Native UI 框架解耦,可 100% 跨端复用,这是招聘类应用跨端开发效率最大化的关键。

招聘数据的精准性要求极高,TypeScript/ArkTS 的强类型约束可提前发现数据类型错误,避免跨端适配过程中因数据格式问题导致的职位信息、投递状态展示错误。

招聘应用的UI排版强调信息层级、多维度数据展示,Flex 布局可 100% 复用于 React Native 和鸿蒙,保证跨端UI的专业性和一致性。


若将该招聘页面落地到鸿蒙端,建议分阶段实施:

  1. 数据模型迁移:首先迁移所有 TypeScript 类型定义,转换为 ArkTS 接口/枚举,验证数据模型的一致性,这一阶段几乎无适配成本。
  2. 基础组件迁移:迁移头部、搜索框、底部导航等基础组件,验证布局、样式、基础交互的适配可行性。
  3. 核心列表迁移:迁移职位列表、消息列表、投递记录列表,重点验证虚拟化渲染、状态样式、交互逻辑的适配一致性。
  4. 鸿蒙特性增强:针对鸿蒙端特性做增强,例如利用鸿蒙的 Swiper 组件优化banner展示(如需),利用 @ohos.security 模块增强简历数据的安全性,利用 FormExtensionAbility 实现招聘卡片的桌面小组件。

从性能优化角度,React Native 端可通过 FlatListgetItemLayout 预计算职位列表项高度,鸿蒙端可通过 ListestimateSize 预估列表项尺寸,进一步提升大量职位数据的滚动性能;两端均可通过图片懒加载优化公司logo的加载速度。

该 React Native 招聘页面的实现,充分体现了招聘类应用跨端开发的工程化思想:强类型数据建模保证数据精准性,虚拟化列表保证性能稳定性,状态驱动的交互保证体验一致性,Flex 布局保证UI专业性。向鸿蒙端迁移时,核心业务逻辑、数据模型、布局思路可完全复用,仅需适配组件语法、样式属性和原生 API 调用,适配成本可控制在 20%-30% 以内。


真实演示案例代码:




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

// 图标库
const ICONS = {
  home: '🏠',
  search: '🔍',
  job: '💼',
  company: '🏢',
  resume: '📄',
  chat: '💬',
  notification: '🔔',
  profile: '👤',
};

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

// 职位类型
type JobPosition = {
  id: string;
  title: string;
  company: string;
  salary: string;
  location: string;
  experience: string;
  education: string;
  tags: string[];
  postedDate: string;
  logo: string;
  applied: boolean;
};

// 公司类型
type Company = {
  id: string;
  name: string;
  industry: string;
  size: string;
  logo: string;
  hot: boolean;
};

// 消息类型
type Message = {
  id: string;
  sender: string;
  content: string;
  time: string;
  unread: boolean;
};

// 我的投递类型
type Application = {
  id: string;
  jobTitle: string;
  company: string;
  status: '已投递' | '已查看' | '面试中' | '已录用' | '未通过';
  applyDate: string;
};

// 主页面组件
const RecruitmentApp: React.FC = () => {
  const [jobs] = useState<JobPosition[]>([
    {
      id: '1',
      title: '前端开发工程师',
      company: '科技有限公司',
      salary: '15K-25K',
      location: '北京',
      experience: '3-5年',
      education: '本科',
      tags: ['React', 'TypeScript', 'Node.js'],
      postedDate: '2天前',
      logo: '',
      applied: false
    },
    {
      id: '2',
      title: '产品经理',
      company: '互联网公司',
      salary: '20K-35K',
      location: '上海',
      experience: '5-10年',
      education: '本科',
      tags: ['产品设计', '数据分析', '团队协作'],
      postedDate: '1天前',
      logo: '',
      applied: true
    },
    {
      id: '3',
      title: 'UI设计师',
      company: '设计工作室',
      salary: '12K-18K',
      location: '深圳',
      experience: '2-4年',
      education: '大专',
      tags: ['Figma', 'Sketch', '交互设计'],
      postedDate: '3天前',
      logo: '',
      applied: false
    },
    {
      id: '4',
      title: '数据分析师',
      company: '大数据公司',
      salary: '18K-30K',
      location: '杭州',
      experience: '3-5年',
      education: '硕士',
      tags: ['Python', 'SQL', 'Excel'],
      postedDate: '今天',
      logo: '',
      applied: false
    }
  ]);

  const [companies] = useState<Company[]>([
    { id: 'c1', name: '腾讯', industry: '互联网', size: '10000人以上', logo: '', hot: true },
    { id: 'c2', name: '阿里巴巴', industry: '电商', size: '10000人以上', logo: '', hot: true },
    { id: 'c3', name: '字节跳动', industry: '互联网', size: '10000人以上', logo: '', hot: true },
    { id: 'c4', name: '美团', industry: '本地生活', size: '5000-10000人', logo: '', hot: true },
  ]);

  const [messages] = useState<Message[]>([
    { id: 'm1', sender: '腾讯HR', content: '您的简历已收到,我们会尽快安排面试', time: '10:30', unread: true },
    { id: 'm2', sender: '阿里巴巴', content: '技术面试时间已确认,明天下午2点', time: '昨天', unread: false },
    { id: 'm3', sender: '字节跳动', content: '感谢您的投递,我们会尽快回复', time: '前天', unread: false },
  ]);

  const [applications] = useState<Application[]>([
    { id: 'a1', jobTitle: '前端开发工程师', company: '科技有限公司', status: '面试中', applyDate: '2023-05-10' },
    { id: 'a2', jobTitle: '产品经理', company: '互联网公司', status: '已查看', applyDate: '2023-05-12' },
    { id: 'a3', jobTitle: 'UI设计师', company: '设计工作室', status: '已投递', applyDate: '2023-05-14' },
  ]);

  const [activeTab, setActiveTab] = useState<'home' | 'search' | 'message' | 'profile'>('home');

  const applyForJob = (jobId: string) => {
    Alert.alert('投递成功', '职位申请已提交,等待企业回复');
  };

  const renderJobItem = ({ item }: { item: JobPosition }) => (
    <View style={styles.jobCard}>
      <View style={styles.jobHeader}>
        <View style={styles.companyLogo}>
          <Text style={styles.logoText}>🏢</Text>
        </div>
        <View style={styles.jobInfo}>
          <Text style={styles.jobTitle}>{item.title}</Text>
          <Text style={styles.companyName}>{item.company}</Text>
        </div>
        <TouchableOpacity 
          style={[styles.applyButton, item.applied && styles.appliedButton]} 
          onPress={() => applyForJob(item.id)}
        >
          <Text style={[styles.applyButtonText, item.applied && styles.appliedButtonText]}>
            {item.applied ? '已投递' : '投递'}
          </Text>
        </TouchableOpacity>
      </div>
      
      <View style={styles.jobDetails}>
        <Text style={styles.salary}>{item.salary}</Text>
        <Text style={styles.location}>{item.location} | {item.experience} | {item.education}</Text>
      </div>
      
      <View style={styles.jobTags}>
        {item.tags.map((tag, index) => (
          <View key={index} style={styles.tag}>
            <Text style={styles.tagText}>{tag}</Text>
          </View>
        ))}
      </div>
      
      <View style={styles.jobFooter}>
        <Text style={styles.postedDate}>{item.postedDate}发布</Text>
      </div>
    </View>
  );

  const renderMessageItem = ({ item }: { item: Message }) => (
    <TouchableOpacity style={styles.messageItem}>
      <View style={styles.messageAvatar}>
        <Text style={styles.avatarText}>👤</Text>
      </div>
      <View style={styles.messageContent}>
        <View style={styles.messageHeader}>
          <Text style={styles.senderName}>{item.sender}</Text>
          <Text style={styles.messageTime}>{item.time}</Text>
        </div>
        <Text style={styles.messageText}>{item.content}</Text>
        {item.unread && <View style={styles.unreadDot} />}
      </div>
    </TouchableOpacity>
  );

  const renderApplicationItem = ({ item }: { item: Application }) => (
    <View style={styles.applicationItem}>
      <View style={styles.applicationHeader}>
        <Text style={styles.applicationJobTitle}>{item.jobTitle}</Text>
        <Text style={[styles.applicationStatus, styles[`status_${item.status}`]]}>{item.status}</Text>
      </div>
      <Text style={styles.applicationCompany}>{item.company}</Text>
      <Text style={styles.applicationDate}>投递时间: {item.applyDate}</Text>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>求职招聘</Text>
        <View style={styles.headerActions}>
          <TouchableOpacity style={styles.notificationButton}>
            <Text style={styles.notificationIcon}>{ICONS.notification}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.profileButton}>
            <Text style={styles.profileIcon}>{ICONS.profile}</Text>
          </TouchableOpacity>
        </div>
      </View>

      <ScrollView style={styles.content}>
        {/* 搜索框 */}
        <View style={styles.searchContainer}>
          <Text style={styles.searchIcon}>{ICONS.search}</Text>
          <Text style={styles.searchPlaceholder}>搜索职位或公司</Text>
        </div>

        {/* 热门职位 */}
        <Text style={styles.sectionTitle}>热门职位</Text>
        <FlatList
          data={jobs}
          renderItem={renderJobItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 热门公司 */}
        <Text style={styles.sectionTitle}>热门公司</Text>
        <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.companyContainer}>
          <View style={styles.companyList}>
            {companies.map(company => (
              <TouchableOpacity key={company.id} style={styles.companyCard}>
                <View style={styles.companyLogoSmall}>
                  <Text style={styles.logoText}>🏢</Text>
                </div>
                <Text style={styles.companyNameSmall}>{company.name}</Text>
                <Text style={styles.companyIndustry}>{company.industry}</Text>
                {company.hot && <Text style={styles.hotTag}>🔥 热招</Text>}
              </TouchableOpacity>
            ))}
          </View>
        </ScrollView>

        {/* 最近消息 */}
        <Text style={styles.sectionTitle}>最近消息</Text>
        <FlatList
          data={messages.slice(0, 2)}
          renderItem={renderMessageItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 我的投递 */}
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>我的投递</Text>
          <TouchableOpacity>
            <Text style={styles.seeAllText}>查看全部</Text>
          </TouchableOpacity>
        </View>

        <FlatList
          data={applications}
          renderItem={renderApplicationItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 求职建议 */}
        <View style={styles.adviceCard}>
          <Text style={styles.adviceTitle}>求职建议</Text>
          <Text style={styles.adviceText}>• 简历要突出重点技能和经验</Text>
          <Text style={styles.adviceText}>• 针对不同岗位定制简历内容</Text>
          <Text style={styles.adviceText}>• 准备常见面试问题的标准答案</Text>
          <Text style={styles.adviceText}>• 提前了解公司背景和文化</Text>
        </div>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('home')}
        >
          <Text style={[styles.navIcon, activeTab === 'home' && styles.activeNavIcon]}>{ICONS.home}</Text>
          <Text style={[styles.navText, activeTab === 'home' && styles.activeNavText]}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('search')}
        >
          <Text style={[styles.navIcon, activeTab === 'search' && styles.activeNavIcon]}>{ICONS.search}</Text>
          <Text style={[styles.navText, activeTab === 'search' && styles.activeNavText]}>搜索</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('message')}
        >
          <Text style={[styles.navIcon, activeTab === 'message' && styles.activeNavIcon]}>{ICONS.chat}</Text>
          <Text style={[styles.navText, activeTab === 'message' && styles.activeNavText]}>消息</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => setActiveTab('profile')}
        >
          <Text style={[styles.navIcon, activeTab === 'profile' && styles.activeNavIcon]}>{ICONS.profile}</Text>
          <Text style={[styles.navText, activeTab === 'profile' && styles.activeNavText]}>我的</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',
  },
  notificationButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  notificationIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  profileButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
  },
  profileIcon: {
    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,
  },
  searchIcon: {
    fontSize: 18,
    color: '#64748b',
    marginRight: 12,
  },
  searchPlaceholder: {
    fontSize: 14,
    color: '#94a3b8',
    flex: 1,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  sectionHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  seeAllText: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: '500',
  },
  jobCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  jobHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
  },
  companyLogo: {
    width: 40,
    height: 40,
    borderRadius: 8,
    backgroundColor: '#dbeafe',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  logoText: {
    fontSize: 20,
  },
  jobInfo: {
    flex: 1,
  },
  jobTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1e293b',
    marginBottom: 4,
  },
  companyName: {
    fontSize: 14,
    color: '#64748b',
  },
  applyButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 20,
  },
  appliedButton: {
    backgroundColor: '#cbd5e1',
  },
  applyButtonText: {
    color: '#ffffff',
    fontWeight: '500',
  },
  appliedButtonText: {
    color: '#64748b',
  },
  jobDetails: {
    marginBottom: 12,
  },
  salary: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#ef4444',
    marginBottom: 4,
  },
  location: {
    fontSize: 12,
    color: '#64748b',
  },
  jobTags: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginBottom: 12,
  },
  tag: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
    marginRight: 8,
    marginBottom: 4,
  },
  tagText: {
    fontSize: 12,
    color: '#64748b',
  },
  jobFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  postedDate: {
    fontSize: 12,
    color: '#94a3b8',
  },
  companyContainer: {
    marginBottom: 16,
  },
  companyList: {
    flexDirection: 'row',
  },
  companyCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginRight: 12,
    width: 140,
    alignItems: 'center',
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  companyLogoSmall: {
    width: 40,
    height: 40,
    borderRadius: 8,
    backgroundColor: '#fef3c7',
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: 8,
  },
  companyNameSmall: {
    fontSize: 14,
    fontWeight: '500',
    color: '#1e293b',
    marginBottom: 4,
  },
  companyIndustry: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 4,
  },
  hotTag: {
    fontSize: 10,
    color: '#ffffff',
    backgroundColor: '#f59e0b',
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 10,
  },
  messageItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  messageAvatar: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: '#dbeafe',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  avatarText: {
    fontSize: 20,
  },
  messageContent: {
    flex: 1,
  },
  messageHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 4,
  },
  senderName: {
    fontSize: 14,
    fontWeight: '500',
    color: '#1e293b',
  },
  messageTime: {
    fontSize: 12,
    color: '#94a3b8',
  },
  messageText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 18,
  },
  unreadDot: {
    width: 8,
    height: 8,
    borderRadius: 4,
    backgroundColor: '#3b82f6',
    position: 'absolute',
    top: 0,
    right: 0,
  },
  applicationItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  applicationHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  applicationJobTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1e293b',
    flex: 1,
  },
  applicationStatus: {
    fontSize: 12,
    fontWeight: '500',
    paddingVertical: 4,
    paddingHorizontal: 8,
    borderRadius: 12,
  },
  status_已投递: {
    backgroundColor: '#dbeafe',
    color: '#3b82f6',
  },
  status_已查看: {
    backgroundColor: '#fef3c7',
    color: '#f59e0b',
  },
  status_面试中: {
    backgroundColor: '#d1fae5',
    color: '#10b981',
  },
  status_已录用: {
    backgroundColor: '#ddd6fe',
    color: '#8b5cf6',
  },
  status_未通过: {
    backgroundColor: '#fee2e2',
    color: '#ef4444',
  },
  applicationCompany: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 4,
  },
  applicationDate: {
    fontSize: 12,
    color: '#94a3b8',
  },
  adviceCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    marginBottom: 16,
  },
  adviceTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  adviceText: {
    fontSize: 12,
    color: '#64748b',
    lineHeight: 18,
    marginBottom: 6,
  },
  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 RecruitmentApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文剖析了一个基于React Native开发的招聘应用代码,重点分析了其数据模型设计、状态管理和跨平台实现策略。应用采用TypeScript定义职位、公司等核心数据模型,通过React Hooks管理状态,使用FlatList实现高效列表渲染,并展示了组件化开发与响应式布局的实现。特别探讨了该应用向鸿蒙系统迁移时的技术适配要点,包括性能优化、内存管理、网络请求等关键考量,为跨平台招聘类应用开发提供了实践参考。文章强调类型安全和模块化设计对跨端开发的重要性,并指出需针对不同平台特性调整交互体验。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐