在移动应用开发中,知识问答游戏是一种受欢迎的应用类型,它不仅能娱乐用户,还能帮助用户知识。本文将深入分析一个功能完备的 React Native 知识问答游戏实现,探讨其架构设计、状态管理、游戏逻辑以及跨端兼容性策略。

组件化架构设计

该实现采用了清晰的组件化架构,主要包含以下部分:

  • 主应用组件 (KnowledgeQuizApp) - 负责整体布局和状态管理
  • 游戏状态管理 - 管理游戏的不同状态(菜单、游戏中、结果、排行榜)
  • 题目渲染 - 渲染当前题目和选项
  • 结果展示 - 展示游戏结果和得分
  • 排行榜 - 展示玩家排名

这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。

状态管理

KnowledgeQuizApp 组件使用 useState 钩子管理多个关键状态:

const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
const [selectedOption, setSelectedOption] = useState<number | null>(null);
const [score, setScore] = useState<number>(0);
const [timeLeft, setTimeLeft] = useState<number>(15);
const [gameState, setGameState] = useState<'menu' | 'playing' | 'result' | 'leaderboard'>('menu');
const [answerRecords, setAnswerRecords] = useState<AnswerRecord[]>([]);

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了游戏的各种功能。使用 TypeScript 联合类型确保了 gameStatedifficulty 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。


游戏流程控制

应用实现了完整的游戏流程控制:

  1. 开始游戏 - 重置所有状态,进入游戏状态
  2. 选择答案 - 记录选择,判断正误,计算得分
  3. 下一题 - 切换到下一题或结束游戏
  4. 游戏结束 - 展示游戏结果

这种流程控制使得游戏逻辑清晰,用户体验流畅。

倒计时功能

应用实现了一个倒计时功能,为每个题目限制 15 秒的答题时间:

React.useEffect(() => {
  let timer: NodeJS.Timeout;
  if (gameState === 'playing' && timeLeft > 0 && selectedOption === null) {
    timer = setTimeout(() => {
      setTimeLeft(timeLeft - 1);
    }, 1000);
  } else if (timeLeft === 0 && selectedOption === null) {
    // 时间到了但没选,算错误
    selectOption(-1); // -1 表示未作答
  }
  return () => clearTimeout(timer);
}, [timeLeft, gameState, selectedOption]);

这种实现方式使用了 useEffect 钩子和 setTimeout 函数,确保了倒计时的准确性和可靠性。在组件卸载时,通过返回清理函数清除定时器,避免了内存泄漏。

答题记录

应用实现了详细的答题记录功能,记录每个题目的答题情况:

  • 选中的选项
  • 是否正确
  • 答题所用时间
  • 获得的分数

这种记录功能使得游戏结果更加详细,用户可以了解自己的答题情况,提高学习效果。


类型定义

该实现使用 TypeScript 定义了三个核心数据类型:

  1. Question - 题目类型,包含题目文本、选项、正确答案、分类、难度和分值
  2. AnswerRecord - 答题记录类型,包含题目 ID、选中选项、是否正确、答题时间和得分
  3. LeaderboardEntry - 排行榜条目类型,包含玩家名称、总得分、游戏次数和胜率

这些类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。

数据组织

应用数据按照功能模块进行组织:

  • questions - 题目列表
  • answerRecords - 答题记录列表
  • leaderboard - 排行榜列表

这种数据组织方式使得数据管理更加清晰,易于扩展和维护。


游戏状态管理

应用实现了四种游戏状态:

  • menu - 游戏菜单,显示游戏标题和开始按钮
  • playing - 游戏中,显示当前题目、选项和倒计时
  • result - 游戏结果,显示得分和答题情况
  • leaderboard - 排行榜,显示玩家排名

这种状态管理使得用户界面根据游戏进度动态变化,提供了良好的用户体验。


React Native 与鸿蒙跨端考虑

在设计跨端知识问答游戏时,需要特别关注以下几个方面:

  1. 组件 API 兼容性 - 确保使用的 React Native 组件在鸿蒙系统上有对应实现
  2. 样式系统差异 - 不同平台对样式的支持程度不同,需要确保样式在两端都能正常显示
  3. 定时器实现 - 不同平台的定时器实现可能存在差异
  4. 图标系统 - 确保图标在不同平台上都能正常显示
  5. 触摸事件处理 - 不同平台的触摸事件机制可能存在差异

类型

使用 TypeScript 类型定义确保了代码的类型安全:

type Question = {
  id: string;
  text: string;
  options: string[];
  correctAnswer: number;
  category: string;
  difficulty: '简单' | '中等' | '困难';
  points: number;
};

type AnswerRecord = {
  id: string;
  questionId: string;
  selectedOption: number | null;
  isCorrect: boolean;
  timeTaken: number; // 秒
  pointsScored: number;
};

type LeaderboardEntry = {
  id: string;
  playerName: string;
  totalScore: number;
  gamesPlayed: number;
  winRate: number;
};

类型定义不仅提高了代码的可读性,也减少了运行时错误的可能性,对于团队协作尤为重要。


教育应用

该实现特别适合作为教育应用,包含以下功能:

  • 知识测验 - 通过问答形式测试用户的知识水平
  • 学习反馈 - 提供详细的答题反馈,帮助用户学习
  • 进度跟踪 - 记录用户的答题情况,跟踪学习进度
  • 排行榜 - 激发用户的学习积极性

娱乐应用

对于娱乐应用,该实现可以用于:

  • ** trivia游戏** - 提供各种有趣的 trivia 题目
  • 智力竞赛 - 组织智力竞赛活动
  • 社交游戏 - 支持多人对战和分享成绩
  • 主题游戏 - 根据不同主题提供相应的题目

企业培训

对于企业培训,该实现可以用于:

  • 员工培训 - 通过问答形式测试员工的培训效果
  • 知识考核 - 定期考核员工的专业知识
  • 合规测试 - 测试员工对公司政策和法规的了解
  • 技能评估 - 评估员工的技能水平

当前实现使用 useEffectsetTimeout 实现倒计时,可以考虑使用 useRef 优化定时器:

// 优化前
React.useEffect(() => {
  let timer: NodeJS.Timeout;
  if (gameState === 'playing' && timeLeft > 0 && selectedOption === null) {
    timer = setTimeout(() => {
      setTimeLeft(timeLeft - 1);
    }, 1000);
  } else if (timeLeft === 0 && selectedOption === null) {
    selectOption(-1);
  }
  return () => clearTimeout(timer);
}, [timeLeft, gameState, selectedOption]);

// 优化后
const timerRef = useRef<NodeJS.Timeout | null>(null);

React.useEffect(() => {
  if (gameState === 'playing' && selectedOption === null) {
    if (timeLeft > 0) {
      timerRef.current = setTimeout(() => {
        setTimeLeft(prev => prev - 1);
      }, 1000);
    } else {
      selectOption(-1);
    }
  }
  return () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  };
}, [timeLeft, gameState, selectedOption]);

2. 状态管理

当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:

// 优化前
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
const [selectedOption, setSelectedOption] = useState<number | null>(null);
const [score, setScore] = useState<number>(0);
const [timeLeft, setTimeLeft] = useState<number>(15);
const [gameState, setGameState] = useState<'menu' | 'playing' | 'result' | 'leaderboard'>('menu');
const [answerRecords, setAnswerRecords] = useState<AnswerRecord[]>([]);

// 优化后
type GameState = {
  currentQuestionIndex: number;
  selectedOption: number | null;
  score: number;
  timeLeft: number;
  gameState: 'menu' | 'playing' | 'result' | 'leaderboard';
  answerRecords: AnswerRecord[];
};

type GameAction =
  | { type: 'START_GAME' }
  | { type: 'SELECT_OPTION'; payload: number }
  | { type: 'NEXT_QUESTION' }
  | { type: 'DECREMENT_TIME' }
  | { type: 'SHOW_RESULT' }
  | { type: 'SHOW_LEADERBOARD' };

const initialState: GameState = {
  currentQuestionIndex: 0,
  selectedOption: null,
  score: 0,
  timeLeft: 15,
  gameState: 'menu',
  answerRecords: []
};

const gameReducer = (state: GameState, action: GameAction): GameState => {
  switch (action.type) {
    case 'START_GAME':
      return {
        ...initialState,
        gameState: 'playing'
      };
    case 'SELECT_OPTION':
      // 处理选择答案的逻辑
      return state;
    case 'NEXT_QUESTION':
      // 处理下一题的逻辑
      return state;
    case 'DECREMENT_TIME':
      return {
        ...state,
        timeLeft: state.timeLeft - 1
      };
    case 'SHOW_RESULT':
      return {
        ...state,
        gameState: 'result'
      };
    case 'SHOW_LEADERBOARD':
      return {
        ...state,
        gameState: 'leaderboard'
      };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(gameReducer, initialState);

3. 动画

可以为游戏添加动画效果,提升用户体验:

import { Animated } from 'react-native';

const KnowledgeQuizApp = () => {
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const scaleAnim = useRef(new Animated.Value(0.8)).current;
  
  const startGame = () => {
    // 动画效果
    Animated.parallel([
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true
      }),
      Animated.spring(scaleAnim, {
        toValue: 1,
        friction: 3,
        tension: 40,
        useNativeDriver: true
      })
    ]).start();
    
    // 其他游戏开始逻辑
  };
  
  return (
    <SafeAreaView style={styles.container}>
      {/* 游戏菜单 */}
      {gameState === 'menu' && (
        <Animated.View style={[styles.menuContainer, { opacity: fadeAnim, transform: [{ scale: scaleAnim }] }]}>
          {/* 菜单内容 */}
        </Animated.View>
      )}
      
      {/* 其他游戏状态 */}
    </SafeAreaView>
  );
};

4. 音效集成

可以为游戏添加音效,提升用户体验:

import { Audio } from 'expo-av';

const KnowledgeQuizApp = () => {
  const [correctSound, setCorrectSound] = useState<Audio.Sound | null>(null);
  const [wrongSound, setWrongSound] = useState<Audio.Sound | null>(null);
  
  useEffect(() => {
    const loadSounds = async () => {
      try {
        const { sound: correct } = await Audio.Sound.createAsync(
          require('./assets/sounds/correct.mp3')
        );
        setCorrectSound(correct);
        
        const { sound: wrong } = await Audio.Sound.createAsync(
          require('./assets/sounds/wrong.mp3')
        );
        setWrongSound(wrong);
      } catch (error) {
        console.error('Error loading sounds:', error);
      }
    };
    
    loadSounds();
    
    return () => {
      if (correctSound) correctSound.unloadAsync();
      if (wrongSound) wrongSound.unloadAsync();
    };
  }, []);
  
  const selectOption = (optionIndex: number) => {
    // 其他逻辑
    
    // 播放音效
    if (isCorrect && correctSound) {
      correctSound.replayAsync();
    } else if (!isCorrect && wrongSound) {
      wrongSound.replayAsync();
    }
  };
  
  // 其他游戏逻辑
};

本文深入分析了一个功能完备的 React Native 知识问答游戏实现,从架构设计、状态管理、游戏逻辑到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。


基于 React Native 开发的知识问答游戏应用的核心技术实现逻辑,同时掌握将其适配到鸿蒙(HarmonyOS)平台的关键要点。该应用是典型的交互式游戏类应用,融合了状态驱动的多场景切换、倒计时逻辑、答题交互、结果统计、排行榜展示等游戏类应用核心特性,是从 React Native 向鸿蒙跨端迁移的典型复杂场景案例。

该知识问答游戏应用的核心价值在于多状态场景管理实时交互逻辑数据驱动的游戏流程控制,完全符合移动端交互式游戏应用的开发最佳实践,其技术架构涵盖了游戏类应用的全核心场景。

1. 类型系统

应用采用 TypeScript 强类型设计,构建了完整的游戏数据模型体系,为跨端适配提供了标准化的数据基础:

  • 核心类型定义
    // 题目类型 - 覆盖答题场景全维度属性
    type Question = {
      id: string;
      text: string;
      options: string[];
      correctAnswer: number;
      category: string;
      difficulty: '简单' | '中等' | '困难';
      points: number;
    };
    
    // 答题记录类型 - 完整记录答题行为和结果
    type AnswerRecord = {
      id: string;
      questionId: string;
      selectedOption: number | null;
      isCorrect: boolean;
      timeTaken: number; // 秒
      pointsScored: number;
    };
    
    // 排行榜类型 - 玩家成绩统计
    type LeaderboardEntry = {
      id: string;
      playerName: string;
      totalScore: number;
      gamesPlayed: number;
      winRate: number;
    };
    
  • 数据模型设计亮点
    • 场景化属性设计:Question 类型包含分类、难度、分值等游戏核心属性,满足答题场景的全维度需求;
    • 行为记录完整性:AnswerRecord 完整记录答题行为(选中选项、耗时、得分),为结果统计提供数据支撑;
    • 强类型约束:difficulty 使用联合类型限制取值范围,避免运行时数据错误;
    • 标准化标识:所有类型均包含 id 字段,保证数据唯一性和可追踪性;
  • 类型设计价值
    • 强类型约束使跨端迁移时数据层可 100% 复用,仅需适配视图层渲染逻辑;
    • 标准化的数据结构为游戏逻辑(答题判断、得分计算、结果统计)提供了精准的数据支撑;
    • 类型定义清晰区分了不同场景的数据边界,降低了跨端适配的认知成本。

2. 状态管理

应用采用 React Hooks 实现复杂的游戏状态管理,通过多状态协同控制游戏全流程,是交互式应用的典型状态管理范式:

  • 核心状态设计
    // 当前题目索引 - 控制答题进度
    const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
    // 选中的选项 - 答题交互核心状态
    const [selectedOption, setSelectedOption] = useState<number | null>(null);
    // 当前得分 - 游戏核心数据
    const [score, setScore] = useState<number>(0);
    // 剩余时间 - 倒计时控制
    const [timeLeft, setTimeLeft] = useState<number>(15);
    // 游戏状态 - 多场景切换核心
    const [gameState, setGameState] = useState<'menu' | 'playing' | 'result' | 'leaderboard'>('menu');
    // 答题记录 - 完整记录答题过程
    const [answerRecords, setAnswerRecords] = useState<AnswerRecord[]>([]);
    
  • 核心游戏逻辑
    (1)答题交互逻辑
    const selectOption = (optionIndex: number) => {
      if (selectedOption !== null) return; // 防止重复选择
      
      setSelectedOption(optionIndex);
      
      const currentQuestion = questions[currentQuestionIndex];
      const isCorrect = optionIndex === currentQuestion.correctAnswer;
      const pointsScored = isCorrect ? currentQuestion.points : 0;
      
      // 记录答题情况
      const newRecord: AnswerRecord = {
        id: `${currentQuestionIndex}-${Date.now()}`,
        questionId: currentQuestion.id,
        selectedOption: optionIndex,
        isCorrect,
        timeTaken: 15 - timeLeft,
        pointsScored
      };
      
      setAnswerRecords([...answerRecords, newRecord]);
      setScore(score + pointsScored);
      
      // 显示正确答案后自动下一题
      setTimeout(() => {
        nextQuestion();
      }, 1500);
    };
    
    (2)倒计时逻辑
    React.useEffect(() => {
      let timer: NodeJS.Timeout;
      if (gameState === 'playing' && timeLeft > 0 && selectedOption === null) {
        timer = setTimeout(() => {
          setTimeLeft(timeLeft - 1);
        }, 1000);
      } else if (timeLeft === 0 && selectedOption === null) {
        // 时间到了但没选,算错误
        selectOption(-1); // -1 表示未作答
      }
      return () => clearTimeout(timer);
    }, [timeLeft, gameState, selectedOption]);
    
  • 状态管理亮点
    • 单一数据源:gameState 作为核心状态,统一控制菜单、答题、结果、排行榜四个场景的切换;
    • 状态协同:timeLeftselectedOptioncurrentQuestionIndex 等状态协同工作,精准控制答题流程;
    • 副作用管理:使用 useEffect 管理倒计时定时器,通过清理函数避免内存泄漏;
    • 不可变更新:答题记录采用数组展开方式更新,保证状态不可变性;
    • 防重复操作:selectOption 中增加防重复选择逻辑,提升交互稳定性;
    • 自动流程控制:答题后自动跳转到下一题,无需用户额外操作,提升游戏流畅度。

3. 多场景渲染

应用实现了游戏类应用的多场景渲染体系,通过条件渲染实现不同游戏状态的界面切换,同时提供丰富的交互反馈:

  • 多场景渲染逻辑
    {gameState === 'menu' && renderMenu()}
    {gameState === 'playing' && renderGame()}
    {gameState === 'result' && renderResult()}
    {gameState === 'leaderboard' && renderLeaderboard()}
    
    • 场景封装:将不同场景的渲染逻辑封装为独立函数,代码结构清晰,便于跨端适配;
    • 条件渲染:基于 gameState 状态精准控制场景切换,无冗余渲染;
  • 交互反馈设计
    <TouchableOpacity
      style={[
        styles.optionButton,
        selectedOption === index && styles.selectedOption,
        selectedOption !== null && index === currentQuestion.correctAnswer && styles.correctOption,
        selectedOption !== null && selectedOption === index && selectedOption !== currentQuestion.correctAnswer && styles.wrongOption
      ]}
      onPress={() => selectOption(index)}
      disabled={selectedOption !== null}
    >
      <Text style={styles.optionText}>{String.fromCharCode(65 + index)}. {option}</Text>
    </TouchableOpacity>
    
    • 多维度视觉反馈:选中状态、正确答案、错误答案分别对应不同样式,反馈清晰;
    • 交互禁用:答题后禁用选项按钮,防止重复操作;
    • 动态文本生成:使用 String.fromCharCode(65 + index) 自动生成选项字母(A/B/C/D);
  • 结果统计逻辑
    const correctAnswers = answerRecords.filter(record => record.isCorrect).length;
    const accuracy = Math.round((correctAnswers / questions.length) * 100);
    
    • 基于答题记录的动态统计,为结果页面提供精准的成绩分析;
    • 分级反馈:根据正确率提供不同的评价文本(优秀/良好/还需努力)。

4. 样式系统

应用的样式系统遵循游戏类应用的设计规范,注重视觉层次感和交互反馈的视觉表达:

  • 场景化样式体系
    • 菜单场景:大标题、按钮化设计、统计卡片,突出游戏入口和核心信息;
    • 答题场景:倒计时醒目、题目卡片化、选项按钮化,聚焦答题核心;
    • 结果场景:大数字展示得分、正确率,分级色彩反馈,突出游戏成果;
    • 排行榜场景:列表化展示、排名视觉差异化,突出竞争感;
  • 交互反馈样式
    • 选中选项:#dbeafe 背景色 + #3b82f6 边框;
    • 正确答案:#d1fae5 背景色 + #10b981 边框;
    • 错误答案:#fee2e2 背景色 + #ef4444 边框;
    • 色彩体系符合用户认知习惯,反馈直观;
  • 响应式设计
    • 使用 Dimensions.get('window') 获取屏幕尺寸,为响应式布局预留扩展空间;
    • 百分比和固定尺寸结合,保证在不同设备上的布局一致性;
    • 流式布局:分类标签使用 flexWrap: 'wrap' 实现自动换行,适配不同屏幕宽度。

将该 React Native 知识问答游戏应用适配到鸿蒙平台,核心是将 React Native 的状态管理、多场景渲染、定时器逻辑、交互反馈映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案和关键技术点。

1. 技术栈与核心API映射

鸿蒙端采用 ArkTS 语言 + ArkUI 组件库(Stage模型),与 React Native 的核心 API 映射关系如下:

React Native 核心API 鸿蒙 ArkTS 对应实现 适配关键说明
useState @State/@Link 状态声明语法替换,逻辑一致
useEffect (定时器) setInterval + clearInterval 定时器逻辑重构
SafeAreaView SafeArea 组件 + safeArea(true) 安全区域适配
View Column/Row/Stack 基础布局组件替换
Text Text 组件 属性基本兼容,样式语法调整
TouchableOpacity Button + stateEffect(false) 可点击组件替换,去除默认效果
ScrollView Scroll 组件 滚动容器替换
FlatList List + ForEach 列表渲染适配
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 弹窗交互替换
setTimeout/clearTimeout setTimeout + clearTimeout 语法兼容,逻辑复用
String.fromCharCode String.fromCharCode API 兼容,直接复用
条件渲染(&&) if 语句 条件渲染语法适配
数组 filter/map filter + ForEach 数组操作适配

2. 鸿蒙端

// index.ets - 鸿蒙端入口文件
@Entry
@Component
struct KnowledgeQuizApp {
  // 核心状态 - 对应 React Native 的 useState
  @State currentQuestionIndex: number = 0;
  @State selectedOption: number | null = null;
  @State score: number = 0;
  @State timeLeft: number = 15;
  @State gameState: 'menu' | 'playing' | 'result' | 'leaderboard' = 'menu';
  @State answerRecords: AnswerRecord[] = [];
  
  // 定时器引用 - 用于清理
  private timerId: number | null = null;
  private timeoutId: number | null = null;

  // 类型定义(完全复用 React Native 的类型)
  type Question = {
    id: string;
    text: string;
    options: string[];
    correctAnswer: number;
    category: string;
    difficulty: '简单' | '中等' | '困难';
    points: number;
  };

  type AnswerRecord = {
    id: string;
    questionId: string;
    selectedOption: number | null;
    isCorrect: boolean;
    timeTaken: number;
    pointsScored: number;
  };

  type LeaderboardEntry = {
    id: string;
    playerName: string;
    totalScore: number;
    gamesPlayed: number;
    winRate: number;
  };

  // 示例题目数据(完全复用)
  private questions: Question[] = [/* 题目数据 */];
  
  // 排行榜数据(完全复用)
  private leaderboard: LeaderboardEntry[] = [/* 排行榜数据 */];

  // 通用样式封装 - 替代 React Native 的 StyleSheet
  @Styles
  cardShadow() {
    .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
  }

  // 生命周期 - 组件销毁时清理定时器
  aboutToDisappear() {
    if (this.timerId !== null) {
      clearInterval(this.timerId);
    }
    if (this.timeoutId !== null) {
      clearTimeout(this.timeoutId);
    }
  }

  build() {
    Column({ space: 0 }) {
      // 头部组件
      this.Header();
      
      // 不同游戏状态渲染
      if (this.gameState === 'menu') {
        this.renderMenu();
      } else if (this.gameState === 'playing') {
        this.renderGame();
      } else if (this.gameState === 'result') {
        this.renderResult();
      } else if (this.gameState === 'leaderboard') {
        this.renderLeaderboard();
      }
      
      // 底部导航
      this.BottomNav();
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8fafc')
    .safeArea(true); // 对应 React Native 的 SafeAreaView
  }
}

3. 核心游戏逻辑适配实现

(1)核心游戏方法适配
// 开始游戏
private startGame() {
  this.currentQuestionIndex = 0;
  this.selectedOption = null;
  this.score = 0;
  this.timeLeft = 15;
  this.answerRecords = [];
  this.gameState = 'playing';
  
  // 启动倒计时
  this.startTimer();
}

// 启动倒计时 - 替代 React Native 的 useEffect 定时器
private startTimer() {
  // 先清理之前的定时器
  if (this.timerId !== null) {
    clearInterval(this.timerId);
  }
  
  this.timerId = setInterval(() => {
    if (this.gameState === 'playing' && this.timeLeft > 0 && this.selectedOption === null) {
      this.timeLeft -= 1;
      
      // 时间到了但没选,算错误
      if (this.timeLeft === 0 && this.selectedOption === null) {
        this.selectOption(-1);
      }
    }
  }, 1000);
}

// 选择答案
private selectOption(optionIndex: number) {
  if (this.selectedOption !== null) return; // 防止重复选择
  
  this.selectedOption = optionIndex;
  
  const currentQuestion = this.questions[this.currentQuestionIndex];
  const isCorrect = optionIndex === currentQuestion.correctAnswer && optionIndex !== -1;
  const pointsScored = isCorrect ? currentQuestion.points : 0;
  
  // 记录答题情况
  const newRecord: AnswerRecord = {
    id: `${this.currentQuestionIndex}-${Date.now()}`,
    questionId: currentQuestion.id,
    selectedOption: optionIndex === -1 ? null : optionIndex,
    isCorrect,
    timeTaken: 15 - this.timeLeft,
    pointsScored
  };
  
  this.answerRecords = [...this.answerRecords, newRecord];
  this.score += pointsScored;
  
  // 显示正确答案后自动下一题
  this.timeoutId = setTimeout(() => {
    this.nextQuestion();
  }, 1500);
}

// 下一题
private nextQuestion() {
  // 清理当前定时器
  if (this.timerId !== null) {
    clearInterval(this.timerId);
  }
  
  if (this.currentQuestionIndex < this.questions.length - 1) {
    this.currentQuestionIndex += 1;
    this.selectedOption = null;
    this.timeLeft = 15;
    
    // 重启定时器
    this.startTimer();
  } else {
    // 游戏结束
    this.gameState = 'result';
  }
}
(2)多场景渲染适配
// 游戏菜单界面
@Builder
renderMenu() {
  Scroll() {
    Column({ space: 0 }) {
      Column({ space: 8 }) {
        Text('知识问答游戏')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b')
          .textAlign(TextAlign.Center);
        
        Text('挑战你的知识储备,赢取积分排名')
          .fontSize(16)
          .fontColor('#64748b')
          .textAlign(TextAlign.Center)
          .marginBottom(40);
        
        // 开始游戏按钮
        Button('开始游戏')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#ffffff')
          .backgroundColor('#3b82f6')
          .paddingVertical(16)
          .borderRadius(12)
          .width('100%')
          .stateEffect(false)
          .marginBottom(16)
          .onClick(() => this.startGame());
        
        // 排行榜按钮
        Button('排行榜')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#3b82f6')
          .backgroundColor('#f1f5f9')
          .paddingVertical(16)
          .borderRadius(12)
          .width('100%')
          .stateEffect(false)
          .marginBottom(30)
          .onClick(() => this.gameState = 'leaderboard');
        
        // 统计数据
        Row({ space: 0 }) {
          // 题目数量
          this.renderStatItem(`${this.questions.length}`, '题目数量');
          // 每题限时
          this.renderStatItem('15s', '每题限时');
          // 知识点
          this.renderStatItem('120+', '知识点');
        }
        .justifyContent(FlexAlign.SpaceAround)
        .marginBottom(40)
        .width('100%');
        
        // 知识分类
        Column({ space: 16 }) {
          Text('知识分类')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .width('100%');
          
          // 分类标签流式布局
          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween, gap: 8 }) {
            this.renderCategoryItem('🌍 地理');
            this.renderCategoryItem('📚 文学');
            this.renderCategoryItem('🔬 科学');
            this.renderCategoryItem('🌌 天文');
            this.renderCategoryItem('🎨 艺术');
            this.renderCategoryItem('🏛️ 历史');
          }
          .width('100%');
        }
        .marginTop(20)
        .width('100%');
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .flex(1);
    }
    .padding(16)
    .width('100%')
    .flex(1);
  }
  .flex(1)
  .width('100%');
}

// 游戏进行中界面
@Builder
renderGame() {
  // 当前题目
  const currentQuestion = this.questions[this.currentQuestionIndex];
  
  Scroll() {
    Column({ space: 20 }) {
      // 游戏头部(题目计数和得分)
      Row() {
        Text(`第 ${this.currentQuestionIndex + 1}/${this.questions.length} 题`)
          .fontSize(16)
          .fontColor('#64748b');
        
        Text(`得分: ${this.score}`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b');
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .width('100%');
      
      // 倒计时
      Column() {
        Text(`${this.timeLeft} 秒`)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#ef4444');
      }
      .alignItems(Alignment.Center)
      .width('100%');
      
      // 题目卡片
      Column({ space: 8 }) {
        // 分类标签
        Text(currentQuestion.category)
          .fontSize(12)
          .fontColor('#ffffff')
          .backgroundColor('#3b82f6')
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .borderRadius(12)
          .alignSelf(ItemAlign.Start);
        
        // 难度标签
        Text(currentQuestion.difficulty)
          .fontSize(12)
          .fontColor('#ffffff')
          .backgroundColor('#10b981')
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .borderRadius(12)
          .alignSelf(ItemAlign.Start)
          .marginBottom(12);
        
        // 题目文本
        Text(currentQuestion.text)
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1e293b')
          .lineHeight(26)
          .width('100%');
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(20)
      .cardShadow()
      .width('100%');
      
      // 选项容器
      Column({ space: 12 }) {
        ForEach(currentQuestion.options, (option, index) => {
          // 计算选项样式
          let bgColor = '#f1f5f9';
          let borderColor = 'transparent';
          let borderWidth = 0;
          
          if (this.selectedOption === index) {
            bgColor = '#dbeafe';
            borderColor = '#3b82f6';
            borderWidth = 2;
          }
          
          if (this.selectedOption !== null && index === currentQuestion.correctAnswer) {
            bgColor = '#d1fae5';
            borderColor = '#10b981';
            borderWidth = 2;
          }
          
          if (this.selectedOption !== null && this.selectedOption === index && this.selectedOption !== currentQuestion.correctAnswer) {
            bgColor = '#fee2e2';
            borderColor = '#ef4444';
            borderWidth = 2;
          }
          
          // 选项按钮
          Button(`${String.fromCharCode(65 + index)}. ${option}`)
            .fontSize(16)
            .fontColor('#1e293b')
            .backgroundColor(bgColor)
            .border({ width: borderWidth, color: borderColor })
            .borderRadius(12)
            .padding(16)
            .width('100%')
            .stateEffect(false)
            .enabled(this.selectedOption === null)
            .onClick(() => this.selectOption(index));
        });
      }
      .width('100%');
      
      // 反馈信息
      if (this.selectedOption !== null) {
        Column({ space: 8 }) {
          Text(this.selectedOption === currentQuestion.correctAnswer ? '回答正确!' : '回答错误!')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .width('100%')
            .textAlign(TextAlign.Center);
          
          if (this.selectedOption !== currentQuestion.correctAnswer) {
            Text(`正确答案是: ${String.fromCharCode(65 + currentQuestion.correctAnswer)}. ${currentQuestion.options[currentQuestion.correctAnswer]}`)
              .fontSize(16)
              .fontColor('#1e293b')
              .width('100%')
              .textAlign(TextAlign.Center);
          }
        }
        .backgroundColor('#ffffff')
        .borderRadius(12)
        .padding(16)
        .cardShadow()
        .width('100%');
      }
    }
    .padding(16)
    .width('100%')
    .flex(1);
  }
  .flex(1)
  .width('100%');
}

// 游戏结果界面
@Builder
renderResult() {
  const correctAnswers = this.answerRecords.filter(record => record.isCorrect).length;
  const accuracy = Math.round((correctAnswers / this.questions.length) * 100);
  
  Scroll() {
    Column({ space: 30 }) {
      Text('游戏结束')
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .textAlign(TextAlign.Center)
        .width('100%');
      
      // 结果统计
      Row({ space: 0 }) {
        // 总得分
        this.renderResultStatItem(`${this.score}`, '总得分');
        // 正确率
        this.renderResultStatItem(`${accuracy}%`, '正确率');
        // 总题数
        this.renderResultStatItem(`${this.questions.length}`, '总题数');
      }
      .justifyContent(FlexAlign.SpaceAround)
      .width('100%');
      
      // 表现评价
      Column() {
        let performanceText = '';
        let performanceColor = '';
        
        if (accuracy >= 80) {
          performanceText = '优秀表现!';
          performanceColor = '#10b981';
        } else if (accuracy >= 60) {
          performanceText = '良好表现';
          performanceColor = '#f59e0b';
        } else {
          performanceText = '还需努力';
          performanceColor = '#ef4444';
        }
        
        Text(performanceText)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(performanceColor);
      }
      .alignItems(Alignment.Center)
      .width('100%');
      
      // 再玩一次按钮
      Button('再玩一次')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#ffffff')
        .backgroundColor('#3b82f6')
        .paddingVertical(16)
        .borderRadius(12)
        .width('100%')
        .stateEffect(false)
        .onClick(() => this.startGame());
      
      // 返回主菜单按钮
      Button('返回主菜单')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#3b82f6')
        .backgroundColor('#f1f5f9')
        .paddingVertical(16)
        .borderRadius(12)
        .width('100%')
        .stateEffect(false)
        .onClick(() => this.gameState = 'menu');
    }
    .padding(16)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .flex(1);
  }
  .flex(1)
  .width('100%');
}

// 排行榜界面
@Builder
renderLeaderboard() {
  Scroll() {
    Column({ space: 20 }) {
      Text('排行榜')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .textAlign(TextAlign.Center)
        .width('100%');
      
      // 排行榜列表
      List() {
        ForEach(this.leaderboard, (item, index) => {
          ListItem() {
            Row({ space: 0 }) {
              // 排名
              Row({ space: 8 }) {
                Text(`${index + 1}`)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#64748b');
                
                if (index < 3) {
                  Text(index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉')
                    .fontSize(20);
                }
              }
              .width(60)
              .alignItems(Alignment.Center);
              
              // 玩家名称
              Text(item.playerName)
                .fontSize(16)
                .fontWeight(FontWeight.Medium)
                .fontColor('#1e293b')
                .flex(1);
              
              // 得分
              Column({ space: 0 }) {
                Text(`${item.totalScore}`)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#3b82f6');
                
                Text(`(${item.gamesPlayed}局)`)
                  .fontSize(12)
                  .fontColor('#64748b');
              }
              .alignItems(Alignment.End);
            }
            .backgroundColor(index === 0 ? '#fffbeb' : '#ffffff')
            .border({ 
              width: index === 0 ? 1 : 0,
              color: '#fbbf24'
            })
            .borderRadius(12)
            .padding(16)
            .width('100%');
          }
        });
      }
      .width('100%');
      
      // 返回主菜单按钮
      Button('返回主菜单')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#3b82f6')
        .backgroundColor('#f1f5f9')
        .paddingVertical(16)
        .borderRadius(12)
        .width('100%')
        .stateEffect(false)
        .onClick(() => this.gameState = 'menu');
    }
    .padding(16)
    .width('100%')
    .flex(1);
  }
  .flex(1)
  .width('100%');
}

// 辅助构建方法
@Builder
renderStatItem(value: string, label: string) {
  Column({ space: 4 }) {
    Text(value)
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .fontColor('#3b82f6');
    
    Text(label)
      .fontSize(14)
      .fontColor('#64748b');
  }
  .alignItems(Alignment.Center);
}

@Builder
renderResultStatItem(value: string, label: string) {
  Column({ space: 4 }) {
    Text(value)
      .fontSize(32)
      .fontWeight(FontWeight.Bold)
      .fontColor('#3b82f6');
    
    Text(label)
      .fontSize(16)
      .fontColor('#64748b');
  }
  .alignItems(Alignment.Center);
}

@Builder
renderCategoryItem(text: string) {
  Text(text)
    .fontSize(14)
    .fontColor('#0369a1')
    .backgroundColor('#e0f2fe')
    .padding({ left: 12, right: 12, top: 8, bottom: 8 })
    .borderRadius(20);
}
(3)头部与底部导航适配
// 头部组件
@Builder
Header() {
  Row() {
    Text('知识问答游戏')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1e293b');
    
    // 设置按钮
    Button('⚙️')
      .fontSize(20)
      .fontColor('#64748b')
      .backgroundColor(Color.Transparent)
      .stateEffect(false)
      .onClick(() => {
        AlertDialog.show({
          title: '设置',
          message: '游戏设置选项',
          confirm: { value: '确定' }
        });
      });
  }
  .justifyContent(FlexAlign.SpaceBetween)
  .alignItems(Alignment.Center)
  .padding(20)
  .backgroundColor('#ffffff')
  .borderBottom({ width: 1, color: '#e2e8f0' })
  .width('100%');
}

// 底部导航
@Builder
BottomNav() {
  Row({ space: 0 }) {
    // 首页
    Button() {
      Column({ space: 4 }) {
        Text('🏠')
          .fontSize(20)
          .fontColor('#94a3b8');
        Text('首页')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
    }
    .backgroundColor(Color.Transparent)
    .flex(1)
    .stateEffect(false)
    .onClick(() => this.gameState !== 'menu' && (this.gameState = 'menu'));
    
    // 排行
    Button() {
      Column({ space: 4 }) {
        Text('🏆')
          .fontSize(20)
          .fontColor('#94a3b8');
        Text('排行')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
    }
    .backgroundColor(Color.Transparent)
    .flex(1)
    .stateEffect(false)
    .onClick(() => this.gameState = 'leaderboard');
    
    // 分享
    Button() {
      Column({ space: 4 }) {
        Text('📤')
          .fontSize(20)
          .fontColor('#94a3b8');
        Text('分享')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
    }
    .backgroundColor(Color.Transparent)
    .flex(1)
    .stateEffect(false)
    .onClick(() => {
      AlertDialog.show({
        title: '分享',
        message: '分享游戏成绩',
        confirm: { value: '确定' }
      });
    });
    
    // 我的
    Button() {
      Column({ space: 4 }) {
        Text('👤')
          .fontSize(20)
          .fontColor('#94a3b8');
        Text('我的')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
    }
    .backgroundColor(Color.Transparent)
    .flex(1)
    .stateEffect(false)
    .onClick(() => {
      AlertDialog.show({
        title: '我的',
        message: '个人中心',
        confirm: { value: '确定' }
      });
    });
  }
  .backgroundColor('#ffffff')
  .borderTop({ width: 1, color: '#e2e8f0' })
  .paddingVertical(12)
  .justifyContent(FlexAlign.SpaceAround)
  .width('100%');
}

(1)定时器逻辑

React Native 的 useEffect 定时器转换为鸿蒙的 setInterval + 生命周期管理:

// React Native
React.useEffect(() => {
  let timer: NodeJS.Timeout;
  if (gameState === 'playing' && timeLeft > 0 && selectedOption === null) {
    timer = setTimeout(() => {
      setTimeLeft(timeLeft - 1);
    }, 1000);
  }
  return () => clearTimeout(timer);
}, [timeLeft, gameState, selectedOption]);

// 鸿蒙
private startTimer() {
  // 先清理之前的定时器
  if (this.timerId !== null) {
    clearInterval(this.timerId);
  }
  
  this.timerId = setInterval(() => {
    if (this.gameState === 'playing' && this.timeLeft > 0 && this.selectedOption === null) {
      this.timeLeft -= 1;
      
      // 时间到了但没选,算错误
      if (this.timeLeft === 0 && this.selectedOption === null) {
        this.selectOption(-1);
      }
    }
  }, 1000);
}

// 组件销毁时清理
aboutToDisappear() {
  if (this.timerId !== null) {
    clearInterval(this.timerId);
  }
}
(2)多状态

React Native 的条件样式数组转换为鸿蒙的条件变量计算:

// React Native
style={[
  styles.optionButton,
  selectedOption === index && styles.selectedOption,
  selectedOption !== null && index === currentQuestion.correctAnswer && styles.correctOption,
  selectedOption !== null && selectedOption === index && selectedOption !== currentQuestion.correctAnswer && styles.wrongOption
]}

// 鸿蒙
let bgColor = '#f1f5f9';
let borderColor = 'transparent';
let borderWidth = 0;

if (this.selectedOption === index) {
  bgColor = '#dbeafe';
  borderColor = '#3b82f6';
  borderWidth = 2;
}

if (this.selectedOption !== null && index === currentQuestion.correctAnswer) {
  bgColor = '#d1fae5';
  borderColor = '#10b981';
  borderWidth = 2;
}

if (this.selectedOption !== null && this.selectedOption === index && this.selectedOption !== currentQuestion.correctAnswer) {
  bgColor = '#fee2e2';
  borderColor = '#ef4444';
  borderWidth = 2;
}

Button()
  .backgroundColor(bgColor)
  .border({ width: borderWidth, color: borderColor });
(3)多场景

React Native 的条件渲染(&&)转换为鸿蒙的 if 语句:

// React Native
{gameState === 'menu' && renderMenu()}
{gameState === 'playing' && renderGame()}
{gameState === 'result' && renderResult()}
{gameState === 'leaderboard' && renderLeaderboard()}

// 鸿蒙
if (this.gameState === 'menu') {
  this.renderMenu();
} else if (this.gameState === 'playing') {
  this.renderGame();
} else if (this.gameState === 'result') {
  this.renderResult();
} else if (this.gameState === 'leaderboard') {
  this.renderLeaderboard();
}
(4)按钮禁用

React Native 的 disabled 属性转换为鸿蒙的 enabled 方法:

// React Native
<TouchableOpacity
  disabled={selectedOption !== null}
>

// 鸿蒙
Button()
  .enabled(this.selectedOption === null);

真实演示案例代码:




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

// Base64 图标库
const ICONS_BASE64 = {
  quiz: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  question: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  answer: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  score: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  timer: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  leaderboard: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  trophy: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 问题类型
type Question = {
  id: string;
  text: string;
  options: string[];
  correctAnswer: number;
  category: string;
  difficulty: '简单' | '中等' | '困难';
  points: number;
};

// 答题记录类型
type AnswerRecord = {
  id: string;
  questionId: string;
  selectedOption: number | null;
  isCorrect: boolean;
  timeTaken: number; // 秒
  pointsScored: number;
};

// 排行榜类型
type LeaderboardEntry = {
  id: string;
  playerName: string;
  totalScore: number;
  gamesPlayed: number;
  winRate: number;
};

const KnowledgeQuizApp = () => {
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
  const [selectedOption, setSelectedOption] = useState<number | null>(null);
  const [score, setScore] = useState<number>(0);
  const [timeLeft, setTimeLeft] = useState<number>(15); // 每题15秒
  const [gameState, setGameState] = useState<'menu' | 'playing' | 'result' | 'leaderboard'>('menu'); // 游戏状态
  const [answerRecords, setAnswerRecords] = useState<AnswerRecord[]>([]);
  
  // 示例题目数据
  const questions: Question[] = [
    {
      id: '1',
      text: '世界上最大的海洋是哪个?',
      options: ['大西洋', '印度洋', '太平洋', '北冰洋'],
      correctAnswer: 2,
      category: '地理',
      difficulty: '简单',
      points: 10
    },
    {
      id: '2',
      text: '中国的首都是哪里?',
      options: ['上海', '广州', '北京', '深圳'],
      correctAnswer: 2,
      category: '地理',
      difficulty: '简单',
      points: 10
    },
    {
      id: '3',
      text: '下列哪位是《哈利·波特》系列小说的作者?',
      options: ['J.R.R. 托尔金', 'J.K. 罗琳', '乔治·R.R. 马丁', '丹·布朗'],
      correctAnswer: 1,
      category: '文学',
      difficulty: '中等',
      points: 20
    },
    {
      id: '4',
      text: '人体最大的器官是什么?',
      options: ['肝脏', '大脑', '皮肤', '心脏'],
      correctAnswer: 2,
      category: '科学',
      difficulty: '中等',
      points: 20
    },
    {
      id: '5',
      text: '太阳系中最大的行星是哪个?',
      options: ['地球', '火星', '木星', '土星'],
      correctAnswer: 2,
      category: '天文',
      difficulty: '中等',
      points: 20
    }
  ];
  
  const [leaderboard] = useState<LeaderboardEntry[]>([
    { id: '1', playerName: '玩家A', totalScore: 120, gamesPlayed: 5, winRate: 80 },
    { id: '2', playerName: '玩家B', totalScore: 100, gamesPlayed: 4, winRate: 75 },
    { id: '3', playerName: '玩家C', totalScore: 90, gamesPlayed: 6, winRate: 70 },
    { id: '4', playerName: '玩家D', totalScore: 80, gamesPlayed: 3, winRate: 90 },
    { id: '5', playerName: '玩家E', totalScore: 70, gamesPlayed: 7, winRate: 65 },
  ]);

  // 开始游戏
  const startGame = () => {
    setCurrentQuestionIndex(0);
    setSelectedOption(null);
    setScore(0);
    setTimeLeft(15);
    setAnswerRecords([]);
    setGameState('playing');
  };

  // 选择答案
  const selectOption = (optionIndex: number) => {
    if (selectedOption !== null) return; // 防止重复选择
    
    setSelectedOption(optionIndex);
    
    const currentQuestion = questions[currentQuestionIndex];
    const isCorrect = optionIndex === currentQuestion.correctAnswer;
    const pointsScored = isCorrect ? currentQuestion.points : 0;
    
    // 记录答题情况
    const newRecord: AnswerRecord = {
      id: `${currentQuestionIndex}-${Date.now()}`,
      questionId: currentQuestion.id,
      selectedOption: optionIndex,
      isCorrect,
      timeTaken: 15 - timeLeft,
      pointsScored
    };
    
    setAnswerRecords([...answerRecords, newRecord]);
    setScore(score + pointsScored);
    
    // 显示正确答案后自动下一题
    setTimeout(() => {
      nextQuestion();
    }, 1500);
  };

  // 下一题
  const nextQuestion = () => {
    if (currentQuestionIndex < questions.length - 1) {
      setCurrentQuestionIndex(currentQuestionIndex + 1);
      setSelectedOption(null);
      setTimeLeft(15);
    } else {
      // 游戏结束
      setGameState('result');
    }
  };

  // 倒计时
  React.useEffect(() => {
    let timer: NodeJS.Timeout;
    if (gameState === 'playing' && timeLeft > 0 && selectedOption === null) {
      timer = setTimeout(() => {
        setTimeLeft(timeLeft - 1);
      }, 1000);
    } else if (timeLeft === 0 && selectedOption === null) {
      // 时间到了但没选,算错误
      selectOption(-1); // -1 表示未作答
    }
    return () => clearTimeout(timer);
  }, [timeLeft, gameState, selectedOption]);

  // 当前题目
  const currentQuestion = questions[currentQuestionIndex];

  // 游戏菜单界面
  const renderMenu = () => (
    <ScrollView style={styles.content}>
      <View style={styles.menuContainer}>
        <Text style={styles.menuTitle}>知识问答游戏</Text>
        <Text style={styles.menuSubtitle}>挑战你的知识储备,赢取积分排名</Text>
        
        <TouchableOpacity style={styles.menuButton} onPress={startGame}>
          <Text style={styles.menuButtonText}>开始游戏</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.menuButtonSecondary} onPress={() => setGameState('leaderboard')}>
          <Text style={styles.menuButtonSecondaryText}>排行榜</Text>
        </TouchableOpacity>
        
        <View style={styles.statsContainer}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{questions.length}</Text>
            <Text style={styles.statLabel}>题目数量</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>15s</Text>
            <Text style={styles.statLabel}>每题限时</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>120+</Text>
            <Text style={styles.statLabel}>知识点</Text>
          </View>
        </View>
        
        <View style={styles.categoryContainer}>
          <Text style={styles.categoryTitle}>知识分类</Text>
          <View style={styles.categoryList}>
            <Text style={styles.categoryItem}>🌍 地理</Text>
            <Text style={styles.categoryItem}>📚 文学</Text>
            <Text style={styles.categoryItem}>🔬 科学</Text>
            <Text style={styles.categoryItem}>🌌 天文</Text>
            <Text style={styles.categoryItem}>🎨 艺术</Text>
            <Text style={styles.categoryItem}>🏛️ 历史</Text>
          </View>
        </View>
      </View>
    </ScrollView>
  );

  // 游戏进行中界面
  const renderGame = () => (
    <ScrollView style={styles.content}>
      <View style={styles.gameHeader}>
        <Text style={styles.questionCounter}>{currentQuestionIndex + 1}/{questions.length}</Text>
        <Text style={styles.scoreDisplay}>得分: {score}</Text>
      </View>
      
      <View style={styles.timerContainer}>
        <Text style={styles.timerText}>{timeLeft}</Text>
      </View>
      
      <View style={styles.questionCard}>
        <Text style={styles.categoryTag}>{currentQuestion.category}</Text>
        <Text style={styles.difficultyTag}>{currentQuestion.difficulty}</Text>
        <Text style={styles.questionText}>{currentQuestion.text}</Text>
      </View>
      
      <View style={styles.optionsContainer}>
        {currentQuestion.options.map((option, index) => (
          <TouchableOpacity
            key={index}
            style={[
              styles.optionButton,
              selectedOption === index && styles.selectedOption,
              selectedOption !== null && index === currentQuestion.correctAnswer && styles.correctOption,
              selectedOption !== null && selectedOption === index && selectedOption !== currentQuestion.correctAnswer && styles.wrongOption
            ]}
            onPress={() => selectOption(index)}
            disabled={selectedOption !== null}
          >
            <Text style={styles.optionText}>{String.fromCharCode(65 + index)}. {option}</Text>
          </TouchableOpacity>
        ))}
      </View>
      
      {selectedOption !== null && (
        <View style={styles.feedbackContainer}>
          <Text style={styles.feedbackText}>
            {selectedOption === currentQuestion.correctAnswer ? '回答正确!' : '回答错误!'}
          </Text>
          {selectedOption !== currentQuestion.correctAnswer && (
            <Text style={styles.correctAnswerText}>
              正确答案是: {String.fromCharCode(65 + currentQuestion.correctAnswer)}. {currentQuestion.options[currentQuestion.correctAnswer]}
            </Text>
          )}
        </View>
      )}
    </ScrollView>
  );

  // 游戏结果界面
  const renderResult = () => {
    const correctAnswers = answerRecords.filter(record => record.isCorrect).length;
    const accuracy = Math.round((correctAnswers / questions.length) * 100);
    
    return (
      <ScrollView style={styles.content}>
        <View style={styles.resultContainer}>
          <Text style={styles.resultTitle}>游戏结束</Text>
          
          <View style={styles.resultStats}>
            <View style={styles.resultStatItem}>
              <Text style={styles.resultStatNumber}>{score}</Text>
              <Text style={styles.resultStatLabel}>总得分</Text>
            </View>
            <View style={styles.resultStatItem}>
              <Text style={styles.resultStatNumber}>{accuracy}%</Text>
              <Text style={styles.resultStatLabel}>正确率</Text>
            </View>
            <View style={styles.resultStatItem}>
              <Text style={styles.resultStatNumber}>{questions.length}</Text>
              <Text style={styles.resultStatLabel}>总题数</Text>
            </View>
          </View>
          
          <View style={styles.performanceIndicator}>
            {accuracy >= 80 ? (
              <Text style={styles.excellentPerformance}>优秀表现!</Text>
            ) : accuracy >= 60 ? (
              <Text style={styles.goodPerformance}>良好表现</Text>
            ) : (
              <Text style={styles.averagePerformance}>还需努力</Text>
            )}
          </View>
          
          <TouchableOpacity style={styles.menuButton} onPress={startGame}>
            <Text style={styles.menuButtonText}>再玩一次</Text>
          </TouchableOpacity>
          
          <TouchableOpacity style={styles.menuButtonSecondary} onPress={() => setGameState('menu')}>
            <Text style={styles.menuButtonSecondaryText}>返回主菜单</Text>
          </TouchableOpacity>
        </View>
      </ScrollView>
    );
  };

  // 排行榜界面
  const renderLeaderboard = () => (
    <ScrollView style={styles.content}>
      <View style={styles.leaderboardContainer}>
        <Text style={styles.leaderboardTitle}>排行榜</Text>
        
        <FlatList
          data={leaderboard}
          keyExtractor={item => item.id}
          renderItem={({ item, index }) => (
            <View style={[
              styles.leaderboardItem,
              index === 0 && styles.topRankItem
            ]}>
              <View style={styles.rankContainer}>
                <Text style={styles.rankText}>{index + 1}</Text>
                {index < 3 && (
                  <Text style={styles.rankIcon}>
                    {index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'}
                  </Text>
                )}
              </View>
              
              <Text style={styles.playerName}>{item.playerName}</Text>
              
              <View style={styles.scoreContainer}>
                <Text style={styles.scoreText}>{item.totalScore}</Text>
                <Text style={styles.gamesPlayed}>({item.gamesPlayed})</Text>
              </View>
            </View>
          )}
        />
        
        <TouchableOpacity style={styles.menuButtonSecondary} onPress={() => setGameState('menu')}>
          <Text style={styles.menuButtonSecondaryText}>返回主菜单</Text>
        </TouchableOpacity>
      </View>
    </ScrollView>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>知识问答游戏</Text>
        <TouchableOpacity onPress={() => Alert.alert('设置', '游戏设置选项')}>
          <Text style={styles.headerIcon}>⚙️</Text>
        </TouchableOpacity>
      </View>

      {/* 不同游戏状态渲染 */}
      {gameState === 'menu' && renderMenu()}
      {gameState === 'playing' && renderGame()}
      {gameState === 'result' && renderResult()}
      {gameState === 'leaderboard' && renderLeaderboard()}

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem}
          onPress={() => gameState !== 'menu' && setGameState('menu')}
        >
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem}
          onPress={() => setGameState('leaderboard')}
        >
          <Text style={styles.navIcon}>🏆</Text>
          <Text style={styles.navText}>排行</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem}
          onPress={() => Alert.alert('分享', '分享游戏成绩')}
        >
          <Text style={styles.navIcon}>📤</Text>
          <Text style={styles.navText}>分享</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem}
          onPress={() => Alert.alert('我的', '个人中心')}
        >
          <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: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  headerIcon: {
    fontSize: 20,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  menuContainer: {
    flex: 1,
    justifyContent: 'center',
  },
  menuTitle: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#1e293b',
    textAlign: 'center',
    marginBottom: 8,
  },
  menuSubtitle: {
    fontSize: 16,
    color: '#64748b',
    textAlign: 'center',
    marginBottom: 40,
  },
  menuButton: {
    backgroundColor: '#3b82f6',
    paddingVertical: 16,
    borderRadius: 12,
    alignItems: 'center',
    marginBottom: 16,
  },
  menuButtonText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#ffffff',
  },
  menuButtonSecondary: {
    backgroundColor: '#f1f5f9',
    paddingVertical: 16,
    borderRadius: 12,
    alignItems: 'center',
    marginBottom: 30,
  },
  menuButtonSecondaryText: {
    fontSize: 16,
    fontWeight: '500',
    color: '#3b82f6',
  },
  statsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 40,
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  statLabel: {
    fontSize: 14,
    color: '#64748b',
    marginTop: 4,
  },
  categoryContainer: {
    marginTop: 20,
  },
  categoryTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 16,
  },
  categoryList: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
  },
  categoryItem: {
    backgroundColor: '#e0f2fe',
    paddingHorizontal: 12,
    paddingVertical: 8,
    borderRadius: 20,
    marginBottom: 8,
    fontSize: 14,
    color: '#0369a1',
  },
  gameHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 20,
  },
  questionCounter: {
    fontSize: 16,
    color: '#64748b',
  },
  scoreDisplay: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  timerContainer: {
    alignItems: 'center',
    marginBottom: 20,
  },
  timerText: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#ef4444',
  },
  questionCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    marginBottom: 20,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  categoryTag: {
    fontSize: 12,
    color: '#ffffff',
    backgroundColor: '#3b82f6',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
    alignSelf: 'flex-start',
    marginBottom: 8,
  },
  difficultyTag: {
    fontSize: 12,
    color: '#ffffff',
    backgroundColor: '#10b981',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
    alignSelf: 'flex-start',
    marginBottom: 12,
  },
  questionText: {
    fontSize: 18,
    fontWeight: '500',
    color: '#1e293b',
    lineHeight: 26,
  },
  optionsContainer: {
    marginBottom: 20,
  },
  optionButton: {
    backgroundColor: '#f1f5f9',
    padding: 16,
    borderRadius: 12,
    marginBottom: 12,
  },
  selectedOption: {
    backgroundColor: '#dbeafe',
    borderWidth: 2,
    borderColor: '#3b82f6',
  },
  correctOption: {
    backgroundColor: '#d1fae5',
    borderWidth: 2,
    borderColor: '#10b981',
  },
  wrongOption: {
    backgroundColor: '#fee2e2',
    borderWidth: 2,
    borderColor: '#ef4444',
  },
  optionText: {
    fontSize: 16,
    color: '#1e293b',
  },
  feedbackContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    alignItems: 'center',
    marginBottom: 20,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  feedbackText: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  correctAnswerText: {
    fontSize: 16,
    color: '#1e293b',
    textAlign: 'center',
  },
  resultContainer: {
    flex: 1,
    justifyContent: 'center',
  },
  resultTitle: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1e293b',
    textAlign: 'center',
    marginBottom: 30,
  },
  resultStats: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 30,
  },
  resultStatItem: {
    alignItems: 'center',
  },
  resultStatNumber: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  resultStatLabel: {
    fontSize: 16,
    color: '#64748b',
    marginTop: 4,
  },
  performanceIndicator: {
    alignItems: 'center',
    marginBottom: 30,
  },
  excellentPerformance: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#10b981',
  },
  goodPerformance: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#f59e0b',
  },
  averagePerformance: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#ef4444',
  },
  leaderboardContainer: {},
  leaderboardTitle: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 20,
    textAlign: 'center',
  },
  leaderboardItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  topRankItem: {
    backgroundColor: '#fffbeb',
    borderWidth: 1,
    borderColor: '#fbbf24',
  },
  rankContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    width: 60,
  },
  rankText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#64748b',
    marginRight: 8,
  },
  rankIcon: {
    fontSize: 20,
  },
  playerName: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1e293b',
    flex: 1,
  },
  scoreContainer: {
    alignItems: 'flex-end',
  },
  scoreText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  gamesPlayed: {
    fontSize: 12,
    color: '#64748b',
  },
  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,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
});

export default KnowledgeQuizApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文探讨了一个基于React Native的知识问答游戏实现方案,重点分析了其架构设计和技术实现。该应用采用组件化架构,分为主应用组件、游戏状态管理、题目渲染、结果展示和排行榜等模块。通过useState管理游戏状态、答题记录等关键数据,并实现了完整的游戏流程控制,包括倒计时功能和答题记录系统。文章还讨论了TypeScript类型定义的优势,以及该应用在教育、娱乐和企业培训等场景的应用价值。最后提出了使用useRef优化定时器和使用useReducer管理复杂状态的技术优化建议。

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

Logo

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

更多推荐