在这里插入图片描述

一、核心原理:计算器的设计与实现

1.1 计算器的设计理念

计算器是一个经典的交互式应用,主要用于:

  • 基础运算:加、减、乘、除四则运算
  • 连续运算:支持多个连续的运算操作
  • 特殊运算:百分比、正负号切换等
  • 结果显示:实时显示输入和计算结果
  • 用户交互:提供直观的按钮操作界面

1.2 计算器的核心要素

一个完整的计算器需要考虑:

  1. 显示区域:显示输入的数字和计算结果
  2. 键盘区域:数字按钮和操作按钮
  3. 状态管理:管理当前输入、前一个操作数、运算符、错误状态等
  4. 运算逻辑:实现各种数学运算
  5. 错误处理:处理除零错误、溢出等异常情况,显示错误提示
  6. 用户反馈:提供按钮点击反馈和结果反馈
  7. 错误恢复:在错误状态下,任何按钮操作都能清除错误状态

1.3 实现原理

计算器的核心实现原理:

  • 使用状态管理存储当前输入、前一个操作数、运算符
  • 使用 Flexbox 布局实现键盘网格
  • 使用 TouchableOpacity 实现按钮交互
  • 实现完整的四则运算逻辑
  • 支持连续运算和结果显示
  • 使用 StyleSheet 实现样式定制

二、基础计算器实现

2.1 组件结构

计算器组件包含以下部分:

  1. 显示容器:显示输入和结果的容器
  2. 显示文本:显示当前数字或结果
  3. 键盘容器:包含所有按钮的容器
  4. 数字按钮:0-9 数字按钮
  5. 操作按钮:加、减、乘、除按钮
  6. 功能按钮:清除、百分比、正负号、等号按钮

2.2 完整代码实现

import React, { useState, useCallback, memo } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  SafeAreaView,
} from 'react-native';

// 计算器按钮组件 Props 类型
interface CalculatorButtonProps {
  text: string;
  onPress: () => void;
  style?: any;
  textStyle?: any;
}

// 计算器按钮组件
const CalculatorButton = memo<CalculatorButtonProps>(({ 
  text,
  onPress,
  style,
  textStyle,
}) => (
  <TouchableOpacity
    style={[styles.button, style]}
    onPress={onPress}
    activeOpacity={0.7}
  >
    <Text style={[styles.buttonText, textStyle]}>{text}</Text>
  </TouchableOpacity>
));

CalculatorButton.displayName = 'CalculatorButton';

// 计算器组件
const Calculator = memo(() => {
  const [display, setDisplay] = useState('0');
  const [previousValue, setPreviousValue] = useState<number | null>(null);
  const [operation, setOperation] = useState<string | null>(null);
  const [waitingForOperand, setWaitingForOperand] = useState(false);
  const [isError, setIsError] = useState(false);

  // 处理数字按钮点击
  const handleNumberPress = useCallback((num: string) => {
    if (isError) {
      setIsError(false);
      setDisplay(num);
      return;
    }

    setDisplay(prev => {
      if (waitingForOperand) {
        setWaitingForOperand(false);
        return num;
      }
      return prev === '0' ? num : prev + num;
    });
  }, [waitingForOperand, isError]);

  // 处理操作符按钮点击
  const handleOperationPress = useCallback((nextOperation: string) => {
    if (isError) {
      setIsError(false);
      setDisplay('0');
      return;
    }

    const inputValue = parseFloat(display);

    if (previousValue === null) {
      setPreviousValue(inputValue);
    } else if (operation) {
      const currentValue = previousValue || 0;
      try {
        const newValue = performOperation(currentValue, inputValue, operation);
        setDisplay(String(newValue));
        setPreviousValue(newValue);
      } catch (error) {
        setIsError(true);
        setDisplay('错误');
        setPreviousValue(null);
        setOperation(null);
        return;
      }
    }

    setWaitingForOperand(true);
    setOperation(nextOperation);
  }, [display, previousValue, operation, isError]);

  // 处理等号按钮点击
  const handleEqualPress = useCallback(() => {
    if (isError) {
      setIsError(false);
      setDisplay('0');
      return;
    }

    const inputValue = parseFloat(display);
    if (previousValue === null || operation === null) return;

    try {
      const result = performOperation(previousValue || 0, inputValue, operation);
      setDisplay(String(result));
      setPreviousValue(null);
      setOperation(null);
      setWaitingForOperand(true);
    } catch (error) {
      setIsError(true);
      setDisplay('错误');
      setPreviousValue(null);
      setOperation(null);
    }
  }, [display, previousValue, operation, isError]);

  // 处理清除按钮点击
  const handleClearPress = useCallback(() => {
    setDisplay('0');
    setPreviousValue(null);
    setOperation(null);
    setWaitingForOperand(false);
  }, []);

  // 处理小数点按钮点击
  const handleDecimalPress = useCallback(() => {
    if (isError) {
      setIsError(false);
      setDisplay('0.');
      return;
    }

    setDisplay(prev => {
      if (waitingForOperand) {
        setWaitingForOperand(false);
        return '0.';
      }
      return prev.includes('.') ? prev : prev + '.';
    });
  }, [waitingForOperand, isError]);

  // 处理正负号切换
  const handleToggleSign = useCallback(() => {
    if (isError) {
      return;
    }
    setDisplay(prev => String(-parseFloat(prev)));
  }, [isError]);

  // 处理百分比
  const handlePercentage = useCallback(() => {
    if (isError) {
      return;
    }
    setDisplay(prev => String(parseFloat(prev) / 100));
  }, [isError]);

  return (
    <SafeAreaView style={styles.container}>
      {/* 标题区域 */}
      <View style={styles.header}>
        <Text style={styles.title}>React Native for Harmony</Text>
        <Text style={styles.subtitle}>模拟计算器</Text>
      </View>

      {/* 计算器主体 */}
      <View style={styles.calculatorContainer}>
        {/* 显示区域 */}
        <View style={styles.display}>
          <Text style={[styles.displayText, isError && styles.errorText]}>{display}</Text>
        </View>

        {/* 键盘区域 */}
        <View style={styles.keypad}>
          {/* 第一行 */}
          <CalculatorButton
            text="C"
            onPress={handleClearPress}
            style={styles.clearButton}
            textStyle={styles.clearButtonText}
          />
          <CalculatorButton
            text="±"
            onPress={handleToggleSign}
            style={styles.functionButton}
            textStyle={styles.functionButtonText}
          />
          <CalculatorButton
            text="%"
            onPress={handlePercentage}
            style={styles.functionButton}
            textStyle={styles.functionButtonText}
          />
          <CalculatorButton
            text="÷"
            onPress={() => handleOperationPress('/')}
            style={styles.operationButton}
            textStyle={styles.operationButtonText}
          />

          {/* 第二行 */}
          <CalculatorButton
            text="7"
            onPress={() => handleNumberPress('7')}
          />
          <CalculatorButton
            text="8"
            onPress={() => handleNumberPress('8')}
          />
          <CalculatorButton
            text="9"
            onPress={() => handleNumberPress('9')}
          />
          <CalculatorButton
            text="×"
            onPress={() => handleOperationPress('*')}
            style={styles.operationButton}
            textStyle={styles.operationButtonText}
          />

          {/* 第三行 */}
          <CalculatorButton
            text="4"
            onPress={() => handleNumberPress('4')}
          />
          <CalculatorButton
            text="5"
            onPress={() => handleNumberPress('5')}
          />
          <CalculatorButton
            text="6"
            onPress={() => handleNumberPress('6')}
          />
          <CalculatorButton
            text="-"
            onPress={() => handleOperationPress('-')}
            style={styles.operationButton}
            textStyle={styles.operationButtonText}
          />

          {/* 第四行 */}
          <CalculatorButton
            text="1"
            onPress={() => handleNumberPress('1')}
          />
          <CalculatorButton
            text="2"
            onPress={() => handleNumberPress('2')}
          />
          <CalculatorButton
            text="3"
            onPress={() => handleNumberPress('3')}
          />
          <CalculatorButton
            text="+"
            onPress={() => handleOperationPress('+')}
            style={styles.operationButton}
            textStyle={styles.operationButtonText}
          />

          {/* 第五行 */}
          <CalculatorButton
            text="0"
            onPress={() => handleNumberPress('0')}
            style={styles.zeroButton}
          />
          <CalculatorButton
            text="."
            onPress={handleDecimalPress}
          />
          <CalculatorButton
            text="="
            onPress={handleEqualPress}
            style={styles.equalButton}
            textStyle={styles.equalButtonText}
          />
        </View>
      </View>

      {/* 说明区域 */}
      <View style={styles.infoCard}>
        <Text style={styles.infoTitle}>💡 功能说明</Text>
        <Text style={styles.infoText}>• 基础运算:支持加、减、乘、除四则运算</Text>
        <Text style={styles.infoText}>• 连续运算:支持多个连续的运算操作</Text>
        <Text style={styles.infoText}>• 特殊运算:支持百分比、正负号切换</Text>
        <Text style={styles.infoText}>• 实时显示:实时显示输入和计算结果</Text>
        <Text style={styles.infoText}>• 错误处理:除零时显示红色错误提示</Text>
        <Text style={styles.infoText}>• 错误恢复:任何按钮操作都能清除错误状态</Text>
        <Text style={styles.infoText}>• 鸿蒙端完美兼容,交互流畅</Text>
      </View>
    </SafeAreaView>
  );
});

Calculator.displayName = 'Calculator';

// 执行计算操作
function performOperation(a: number, b: number, operation: string): number {
  switch (operation) {
    case '+':
      return a + b;
    case '-':
      return a - b;
    case '*':
      return a * b;
    case '/':
      if (b === 0) {
        throw new Error('除数不能为零');
      }
      return a / b;
    default:
      return b;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F7FA',
  },

  // ======== 标题区域 ========
  header: {
    padding: 20,
    backgroundColor: '#FFFFFF',
    borderBottomWidth: 1,
    borderBottomColor: '#EBEEF5',
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    color: '#303133',
    textAlign: 'center',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#909399',
    textAlign: 'center',
  },

  // ======== 计算器容器 ========
  calculatorContainer: {
    backgroundColor: '#FFFFFF',
    margin: 16,
    borderRadius: 16,
    padding: 16,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 4,
  },

  // ======== 显示区域 ========
  display: {
    backgroundColor: '#F5F7FA',
    borderRadius: 12,
    padding: 20,
    marginBottom: 16,
    alignItems: 'flex-end',
    minHeight: 80,
    justifyContent: 'flex-end',
  },
  displayText: {
    fontSize: 36,
    fontWeight: '600',
    color: '#303133',
  },
  errorText: {
    color: '#F56C6C',
  },

  // ======== 键盘区域 ========
  keypad: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
  },
  button: {
    width: '22%',
    aspectRatio: 1,
    backgroundColor: '#F5F7FA',
    borderRadius: 12,
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 12,
  },
  buttonText: {
    fontSize: 24,
    fontWeight: '500',
    color: '#303133',
  },

  // ======== 特殊按钮样式 ========
  clearButton: {
    backgroundColor: '#F56C6C',
  },
  clearButtonText: {
    color: '#FFFFFF',
  },
  functionButton: {
    backgroundColor: '#909399',
  },
  functionButtonText: {
    color: '#FFFFFF',
  },
  operationButton: {
    backgroundColor: '#409EFF',
  },
  operationButtonText: {
    color: '#FFFFFF',
  },
  equalButton: {
    backgroundColor: '#67C23A',
  },
  equalButtonText: {
    color: '#FFFFFF',
  },
  zeroButton: {
    width: '48%',
  },

  // ======== 信息卡片 ========
  infoCard: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 16,
    margin: 16,
    marginTop: 0,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 4,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#303133',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#606266',
    lineHeight: 22,
    marginBottom: 6,
  },
});

export default Calculator;

三、核心实现要点

3.1 状态管理

使用状态管理存储计算器的各种状态:

const [display, setDisplay] = useState('0');
const [previousValue, setPreviousValue] = useState<number | null>(null);
const [operation, setOperation] = useState<string | null>(null);
const [waitingForOperand, setWaitingForOperand] = useState(false);
const [isError, setIsError] = useState(false);

状态说明:

  • display:当前显示的数字或结果
  • previousValue:前一个操作数
  • operation:当前操作符
  • waitingForOperand:是否等待输入下一个操作数
  • isError:是否处于错误状态(如除零错误)

3.2 数字输入处理

处理数字按钮的点击事件:

const handleNumberPress = useCallback((num: string) => {
  setDisplay(prev => {
    if (waitingForOperand) {
      setWaitingForOperand(false);
      return num;
    }
    return prev === '0' ? num : prev + num;
  });
}, [waitingForOperand]);

处理逻辑:

  • 如果等待输入下一个操作数,则替换当前显示
  • 如果当前显示为 ‘0’,则替换为新数字
  • 否则追加新数字到当前显示

3.3 运算符处理

处理运算符按钮的点击事件:

const handleOperationPress = useCallback((nextOperation: string) => {
  const inputValue = parseFloat(display);

  if (previousValue === null) {
    setPreviousValue(inputValue);
  } else if (operation) {
    const currentValue = previousValue || 0;
    const newValue = performOperation(currentValue, inputValue, operation);
    setDisplay(String(newValue));
    setPreviousValue(newValue);
  }

  setWaitingForOperand(true);
  setOperation(nextOperation);
}, [display, previousValue, operation]);

处理逻辑:

  • 如果没有前一个操作数,则保存当前值
  • 如果有前一个操作数和运算符,则执行运算
  • 设置等待输入下一个操作数的状态
  • 保存当前运算符

3.4 等号处理

处理等号按钮的点击事件:

const handleEqualPress = useCallback(() => {
  const inputValue = parseFloat(display);
  if (previousValue === null || operation === null) return;

  const result = performOperation(previousValue || 0, inputValue, operation);
  setDisplay(String(result));
  setPreviousValue(null);
  setOperation(null);
  setWaitingForOperand(true);
}, [display, previousValue, operation]);

处理逻辑:

  • 如果没有前一个操作数或运算符,则不执行
  • 执行运算并显示结果
  • 清空前一个操作数和运算符
  • 设置等待输入下一个操作数的状态

3.5 错误处理

处理除零错误等异常情况:

const [isError, setIsError] = useState(false);

// 在 performOperation 函数中抛出错误
function performOperation(a: number, b: number, operation: string): number {
  switch (operation) {
    case '/':
      if (b === 0) {
        throw new Error('除数不能为零');
      }
      return a / b;
    default:
      return b;
  }
}

// 在运算符处理中捕获错误
try {
  const newValue = performOperation(currentValue, inputValue, operation);
  setDisplay(String(newValue));
  setPreviousValue(newValue);
} catch (error) {
  setIsError(true);
  setDisplay('错误');
  setPreviousValue(null);
  setOperation(null);
}

错误处理要点:

  • 使用 isError 状态管理错误状态
  • 在 performOperation 函数中抛出错误
  • 在运算符处理和等号处理中捕获错误
  • 在显示区域显示红色错误文字
  • 在错误状态下,任何按钮操作都会清除错误状态

3.6 键盘布局

使用 Flexbox 实现键盘网格布局:

<View style={styles.keypad}>
  <CalculatorButton text="C" onPress={handleClearPress} />
  <CalculatorButton text="7" onPress={() => handleNumberPress('7')} />
  {/* 更多按钮 */}
</View>

布局设计要点:

  • 使用 flexWrap: 'wrap' 实现自动换行
  • 每个按钮宽度为 22%,每行 4 个按钮
  • 0 按钮宽度为 48%,占据两个按钮的位置
  • 使用 justifyContent: 'space-between' 控制按钮间距

四、性能优化

4.1 使用 memo 优化

所有组件都使用 memo 包装:

const CalculatorButton = memo<CalculatorButtonProps>(({ text, onPress, style, textStyle }) => {
  // ...
});

const Calculator = memo(() => {
  // ...
});

为什么使用 memo?

  • 避免不必要的重新渲染
  • 提升整体性能
  • 在复杂应用中,避免父组件更新时重新渲染所有按钮

4.2 使用 useCallback 优化

使用 useCallback 缓存回调函数:

const handleNumberPress = useCallback((num: string) => {
  setDisplay(prev => {
    if (waitingForOperand) {
      setWaitingForOperand(false);
      return num;
    }
    return prev === '0' ? num : prev + num;
  });
}, [waitingForOperand]);

为什么使用 useCallback?

  • 避免每次渲染都创建新函数
  • 减少子组件的重新渲染
  • 提升整体性能

4.3 按钮组件优化

使用独立的按钮组件:

const CalculatorButton = memo<CalculatorButtonProps>(({ text, onPress, style, textStyle }) => (
  <TouchableOpacity
    style={[styles.button, style]}
    onPress={onPress}
    activeOpacity={0.7}
  >
    <Text style={[styles.buttonText, textStyle]}>{text}</Text>
  </TouchableOpacity>
));

优化要点:

  • 使用 memo 避免不必要的重新渲染
  • 使用 activeOpacity 提供点击反馈
  • 支持自定义样式和文本样式

五、常见问题与解决方案

5.1 连续运算不正确

问题现象: 连续运算时结果不正确

可能原因:

  1. 状态管理不正确
  2. 运算符处理逻辑错误
  3. 等待操作数状态设置错误

解决方案:

// 1. 确保状态管理正确
const [previousValue, setPreviousValue] = useState<number | null>(null);
const [operation, setOperation] = useState<string | null>(null);
const [waitingForOperand, setWaitingForOperand] = useState(false);

// 2. 确保运算符处理逻辑正确
if (previousValue === null) {
  setPreviousValue(inputValue);
} else if (operation) {
  const currentValue = previousValue || 0;
  const newValue = performOperation(currentValue, inputValue, operation);
  setDisplay(String(newValue));
  setPreviousValue(newValue);
}

// 3. 确保等待操作数状态设置正确
setWaitingForOperand(true);

5.2 小数点输入错误

问题现象: 小数点输入不正确

可能原因:

  1. 没有检查是否已经包含小数点
  2. 等待操作数状态处理错误

解决方案:

const handleDecimalPress = useCallback(() => {
  setDisplay(prev => {
    if (waitingForOperand) {
      setWaitingForOperand(false);
      return '0.';
    }
    return prev.includes('.') ? prev : prev + '.';
  });
}, [waitingForOperand]);

5.3 除零错误

问题现象: 除以零时显示错误

可能原因:

  1. 没有处理除零情况
  2. 显示结果为 Infinity 或 NaN

解决方案:

使用错误状态管理除零错误:

// 1. 添加错误状态
const [isError, setIsError] = useState(false);

// 2. 在 performOperation 函数中抛出错误
function performOperation(a: number, b: number, operation: string): number {
  switch (operation) {
    case '/':
      if (b === 0) {
        throw new Error('除数不能为零');
      }
      return a / b;
    default:
      return b;
  }
}

// 3. 在运算符处理中捕获错误
const handleOperationPress = useCallback((nextOperation: string) => {
  if (isError) {
    setIsError(false);
    setDisplay('0');
    return;
  }

  const inputValue = parseFloat(display);

  if (previousValue === null) {
    setPreviousValue(inputValue);
  } else if (operation) {
    const currentValue = previousValue || 0;
    try {
      const newValue = performOperation(currentValue, inputValue, operation);
      setDisplay(String(newValue));
      setPreviousValue(newValue);
    } catch (error) {
      setIsError(true);
      setDisplay('错误');
      setPreviousValue(null);
      setOperation(null);
      return;
    }
  }

  setWaitingForOperand(true);
  setOperation(nextOperation);
}, [display, previousValue, operation, isError]);

// 4. 在显示区域显示错误
<Text style={[styles.displayText, isError && styles.errorText]}>{display}</Text>

// 5. 在样式中添加错误文本样式
errorText: {
  color: '#F56C6C',
},

5.4 按钮点击无响应

问题现象: 点击按钮没有响应

可能原因:

  1. onPress 事件未正确绑定
  2. 组件未正确渲染
  3. 状态更新未触发重新渲染

解决方案:

// 1. 确保 onPress 事件正确绑定
<CalculatorButton
  text="7"
  onPress={() => handleNumberPress('7')}
/>

// 2. 确保组件正确渲染
const Calculator = memo(() => {
  // ...
});

// 3. 确保状态更新正确
setDisplay(prev => {
  // ...
});

六、扩展用法

6.1 添加科学计算功能

添加三角函数、对数等科学计算功能:

const handleScientificOperation = useCallback((op: string) => {
  const inputValue = parseFloat(display);
  let result: number;

  switch (op) {
    case 'sin':
      result = Math.sin(inputValue);
      break;
    case 'cos':
      result = Math.cos(inputValue);
      break;
    case 'tan':
      result = Math.tan(inputValue);
      break;
    case 'log':
      result = Math.log10(inputValue);
      break;
    case 'ln':
      result = Math.log(inputValue);
      break;
    default:
      return;
  }

  setDisplay(String(result));
  setWaitingForOperand(true);
}, [display]);

6.2 添加历史记录功能

添加计算历史记录功能:

const [history, setHistory] = useState<string[]>([]);

const handleEqualPress = useCallback(() => {
  const inputValue = parseFloat(display);
  if (previousValue === null || operation === null) return;

  const result = performOperation(previousValue || 0, inputValue, operation);
  const expression = `${previousValue} ${operation} ${inputValue} = ${result}`;
  
  setHistory(prev => [...prev, expression]);
  setDisplay(String(result));
  setPreviousValue(null);
  setOperation(null);
  setWaitingForOperand(true);
}, [display, previousValue, operation]);

6.3 添加语音输入功能

添加语音输入数字和运算符的功能:

const handleVoiceInput = useCallback((text: string) => {
  // 解析语音输入
  const numbers = text.match(/\d+/g);
  const operators = text.match(/[+\-*/]/g);

  if (numbers && numbers.length > 0) {
    handleNumberPress(numbers[0]);
  }

  if (operators && operators.length > 0) {
    handleOperationPress(operators[0]);
  }
}, []);

6.4 添加主题切换功能

添加亮色和暗色主题切换功能:

const [isDarkMode, setIsDarkMode] = useState(false);

const theme = isDarkMode ? darkTheme : lightTheme;

<View style={[styles.container, theme.container]}>
  <View style={[styles.display, theme.display]}>
    <Text style={[styles.displayText, theme.displayText]}>{display}</Text>
  </View>
</View>

七、总结

计算器是一个经典的交互式应用,通过本篇文章,我们学习了:

  1. 状态管理:使用 useState 管理计算器的各种状态
  2. 事件处理:处理数字、运算符、等号等按钮的点击事件
  3. 布局设计:使用 Flexbox 实现键盘网格布局
  4. 性能优化:使用 memo 和 useCallback 优化性能
  5. 错误处理:处理除零错误等异常情况
  6. 扩展功能:添加科学计算、历史记录等扩展功能

计算器组件在 React Native for Harmony 中表现良好,交互流畅,是一个很好的学习案例。

@欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐