React Native 鸿蒙跨平台开发:实现一个多功能单位转换器
length: [{ label: '米', value: 'm', factor: 1 },{ label: '千米', value: 'km', factor: 1000 },{ label: '厘米', value: 'cm', factor: 0.01 },{ label: '毫米', value: 'mm', factor: 0.001 },{ label: '英寸', value: '
·

一、核心原理:单位转换器的设计与实现
1.1 单位转换器的设计理念
单位转换器是一个实用的工具,主要用于:
- 长度转换:米、千米、厘米、毫米、英寸、英尺等
- 重量转换:千克、克、毫克、磅、盎司等
- 温度转换:摄氏度、华氏度、开尔文等
- 体积转换:升、毫升、加仑等
1.2 单位转换器的核心要素
一个完整的单位转换器需要考虑:
- 输入区域:用户输入数值
- 单位选择:选择源单位和目标单位
- 转换逻辑:实现各种单位之间的转换
- 结果显示:显示转换后的结果
- 历史记录:保存最近的转换记录
- 常用单位:提供常用单位的快捷选择
- 实时转换:输入时实时显示转换结果
1.3 实现原理
单位转换器的核心实现原理:
- 使用状态管理存储输入值、源单位、目标单位
- 使用转换公式实现单位之间的转换
- 使用 ScrollView 确保页面可滚动
- 使用 TouchableOpacity 实现按钮交互
- 使用 TextInput 实现数值输入
- 使用 StyleSheet 实现样式定制
二、基础单位转换器实现
2.1 组件结构
单位转换器组件包含以下部分:
- 类型选择:选择转换类型(长度、重量、温度)
- 输入区域:输入要转换的数值
- 单位选择:选择源单位和目标单位
- 结果显示:显示转换后的结果
- 历史记录:显示最近的转换记录
2.2 完整代码实现
import React, { useState, useCallback, memo } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
ScrollView,
TextInput,
} from 'react-native';
// 转换类型
type ConversionType = 'length' | 'weight' | 'temperature';
// 单位定义
const units = {
length: [
{ label: '米', value: 'm', factor: 1 },
{ label: '千米', value: 'km', factor: 1000 },
{ label: '厘米', value: 'cm', factor: 0.01 },
{ label: '毫米', value: 'mm', factor: 0.001 },
{ label: '英寸', value: 'in', factor: 0.0254 },
{ label: '英尺', value: 'ft', factor: 0.3048 },
],
weight: [
{ label: '千克', value: 'kg', factor: 1 },
{ label: '克', value: 'g', factor: 0.001 },
{ label: '毫克', value: 'mg', factor: 0.000001 },
{ label: '磅', value: 'lb', factor: 0.453592 },
{ label: '盎司', value: 'oz', factor: 0.0283495 },
],
temperature: [
{ label: '摄氏度', value: 'c' },
{ label: '华氏度', value: 'f' },
{ label: '开尔文', value: 'k' },
],
};
// 单位转换器组件
const UnitConverter = memo(() => {
const [conversionType, setConversionType] = useState<ConversionType>('length');
const [inputValue, setInputValue] = useState('');
const [fromUnit, setFromUnit] = useState(units.length[0].value);
const [toUnit, setToUnit] = useState(units.length[1].value);
const [result, setResult] = useState('');
const [history, setHistory] = useState<Array<{ type: string; from: string; to: string; value: string; result: string }>>([]);
// 执行转换
const convert = useCallback(() => {
const value = parseFloat(inputValue);
if (isNaN(value)) {
setResult('请输入有效的数值');
return;
}
let convertedValue: number;
if (conversionType === 'temperature') {
// 温度转换需要特殊处理
convertedValue = convertTemperature(value, fromUnit, toUnit);
} else {
// 长度和重量转换使用因子
const fromUnits = units[conversionType];
const fromFactor = fromUnits.find(u => u.value === fromUnit)?.factor || 1;
const toFactor = fromUnits.find(u => u.value === toUnit)?.factor || 1;
convertedValue = (value * fromFactor) / toFactor;
}
const resultStr = convertedValue.toFixed(4);
setResult(resultStr);
// 添加到历史记录
const newHistory = {
type: conversionType === 'length' ? '长度' : conversionType === 'weight' ? '重量' : '温度',
from: units[conversionType].find(u => u.value === fromUnit)?.label || fromUnit,
to: units[conversionType].find(u => u.value === toUnit)?.label || toUnit,
value: inputValue,
result: resultStr,
};
setHistory(prev => [newHistory, ...prev].slice(0, 10));
}, [inputValue, fromUnit, toUnit, conversionType]);
// 温度转换
const convertTemperature = useCallback((value: number, from: string, to: string): number => {
// 先转换为摄氏度
let celsius: number;
if (from === 'f') {
celsius = (value - 32) * 5 / 9;
} else if (from === 'k') {
celsius = value - 273.15;
} else {
celsius = value;
}
// 从摄氏度转换为目标单位
if (to === 'f') {
return celsius * 9 / 5 + 32;
} else if (to === 'k') {
return celsius + 273.15;
} else {
return celsius;
}
}, []);
// 实时转换
React.useEffect(() => {
if (inputValue) {
const value = parseFloat(inputValue);
if (!isNaN(value)) {
let convertedValue: number;
if (conversionType === 'temperature') {
convertedValue = convertTemperature(value, fromUnit, toUnit);
} else {
const fromUnits = units[conversionType];
const fromFactor = fromUnits.find(u => u.value === fromUnit)?.factor || 1;
const toFactor = fromUnits.find(u => u.value === toUnit)?.factor || 1;
convertedValue = (value * fromFactor) / toFactor;
}
setResult(convertedValue.toFixed(4));
}
} else {
setResult('');
}
}, [inputValue, fromUnit, toUnit, conversionType, convertTemperature]);
// 切换转换类型
const handleTypeChange = useCallback((type: ConversionType) => {
setConversionType(type);
setInputValue('');
setResult('');
setFromUnit(units[type][0].value);
setToUnit(units[type][1].value);
}, []);
// 清除输入
const handleClear = useCallback(() => {
setInputValue('');
setResult('');
}, []);
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.scrollView}>
{/* 标题区域 */}
<View style={styles.header}>
<Text style={styles.title}>React Native for Harmony</Text>
<Text style={styles.subtitle}>单位转换器</Text>
</View>
{/* 转换器主体 */}
<View style={styles.converterContainer}>
{/* 转换类型选择 */}
<View style={styles.typeSelector}>
<TouchableOpacity
style={[styles.typeButton, conversionType === 'length' && styles.activeTypeButton]}
onPress={() => handleTypeChange('length')}
>
<Text style={[styles.typeButtonText, conversionType === 'length' && styles.activeTypeButtonText]}>长度</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.typeButton, conversionType === 'weight' && styles.activeTypeButton]}
onPress={() => handleTypeChange('weight')}
>
<Text style={[styles.typeButtonText, conversionType === 'weight' && styles.activeTypeButtonText]}>重量</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.typeButton, conversionType === 'temperature' && styles.activeTypeButton]}
onPress={() => handleTypeChange('temperature')}
>
<Text style={[styles.typeButtonText, conversionType === 'temperature' && styles.activeTypeButtonText]}>温度</Text>
</TouchableOpacity>
</View>
{/* 输入区域 */}
<View style={styles.inputSection}>
<Text style={styles.label}>输入数值</Text>
<TextInput
style={styles.input}
value={inputValue}
onChangeText={setInputValue}
placeholder="请输入数值"
keyboardType="decimal-pad"
placeholderTextColor="#909399"
/>
</View>
{/* 源单位选择 */}
<View style={styles.unitSection}>
<Text style={styles.label}>从</Text>
<View style={styles.unitButtons}>
{units[conversionType].map(unit => (
<TouchableOpacity
key={unit.value}
style={[styles.unitButton, fromUnit === unit.value && styles.activeUnitButton]}
onPress={() => setFromUnit(unit.value)}
>
<Text style={[styles.unitButtonText, fromUnit === unit.value && styles.activeUnitButtonText]}>
{unit.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
{/* 目标单位选择 */}
<View style={styles.unitSection}>
<Text style={styles.label}>到</Text>
<View style={styles.unitButtons}>
{units[conversionType].map(unit => (
<TouchableOpacity
key={unit.value}
style={[styles.unitButton, toUnit === unit.value && styles.activeUnitButton]}
onPress={() => setToUnit(unit.value)}
>
<Text style={[styles.unitButtonText, toUnit === unit.value && styles.activeUnitButtonText]}>
{unit.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
{/* 结果显示 */}
<View style={styles.resultSection}>
<Text style={styles.resultLabel}>转换结果</Text>
<View style={styles.resultBox}>
<Text style={styles.resultText}>{result || '--'}</Text>
</View>
</View>
{/* 操作按钮 */}
<View style={styles.actionButtons}>
<TouchableOpacity
style={styles.actionButton}
onPress={handleClear}
>
<Text style={styles.actionButtonText}>清除</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, styles.convertButton]}
onPress={convert}
>
<Text style={styles.actionButtonText}>转换</Text>
</TouchableOpacity>
</View>
</View>
{/* 历史记录 */}
{history.length > 0 && (
<View style={styles.historyContainer}>
<Text style={styles.historyTitle}>历史记录</Text>
{history.map((item, index) => (
<View key={index} style={styles.historyItem}>
<Text style={styles.historyType}>{item.type}</Text>
<Text style={styles.historyText}>
{item.value} {item.from} = {item.result} {item.to}
</Text>
</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}>• 历史记录:保存最近的10条转换记录</Text>
<Text style={styles.infoText}>• 精确计算:保留4位小数</Text>
<Text style={styles.infoText}>• 温度转换:支持摄氏度、华氏度、开尔文</Text>
<Text style={styles.infoText}>• 鸿蒙端完美兼容,转换准确</Text>
</View>
</ScrollView>
</SafeAreaView>
);
});
UnitConverter.displayName = 'UnitConverter';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F7FA',
},
scrollView: {
flex: 1,
},
// ======== 标题区域 ========
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',
},
// ======== 转换器容器 ========
converterContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 16,
padding: 20,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 4,
},
// ======== 转换类型选择 ========
typeSelector: {
flexDirection: 'row',
marginBottom: 24,
},
typeButton: {
flex: 1,
paddingVertical: 12,
marginHorizontal: 4,
borderRadius: 8,
backgroundColor: '#F5F7FA',
alignItems: 'center',
},
activeTypeButton: {
backgroundColor: '#409EFF',
},
typeButtonText: {
fontSize: 14,
fontWeight: '600',
color: '#606266',
},
activeTypeButtonText: {
color: '#FFFFFF',
},
// ======== 输入区域 ========
inputSection: {
marginBottom: 20,
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#303133',
marginBottom: 8,
},
input: {
backgroundColor: '#F5F7FA',
borderRadius: 8,
padding: 12,
fontSize: 16,
color: '#303133',
borderWidth: 1,
borderColor: '#EBEEF5',
},
// ======== 单位选择 ========
unitSection: {
marginBottom: 20,
},
unitButtons: {
flexDirection: 'row',
flexWrap: 'wrap',
},
unitButton: {
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 6,
backgroundColor: '#F5F7FA',
marginRight: 8,
marginBottom: 8,
},
activeUnitButton: {
backgroundColor: '#409EFF',
},
unitButtonText: {
fontSize: 14,
color: '#606266',
},
activeUnitButtonText: {
color: '#FFFFFF',
},
// ======== 结果显示 ========
resultSection: {
marginBottom: 20,
},
resultLabel: {
fontSize: 14,
fontWeight: '600',
color: '#303133',
marginBottom: 8,
},
resultBox: {
backgroundColor: '#F5F7FA',
borderRadius: 8,
padding: 16,
alignItems: 'center',
},
resultText: {
fontSize: 24,
fontWeight: '700',
color: '#409EFF',
},
// ======== 操作按钮 ========
actionButtons: {
flexDirection: 'row',
},
actionButton: {
flex: 1,
paddingVertical: 12,
borderRadius: 8,
backgroundColor: '#F5F7FA',
alignItems: 'center',
marginHorizontal: 4,
},
convertButton: {
backgroundColor: '#409EFF',
},
actionButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#303133',
},
// ======== 历史记录 ========
historyContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 12,
padding: 16,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 4,
},
historyTitle: {
fontSize: 16,
fontWeight: '600',
color: '#303133',
marginBottom: 12,
},
historyItem: {
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: '#EBEEF5',
},
historyType: {
fontSize: 12,
color: '#909399',
marginBottom: 4,
},
historyText: {
fontSize: 14,
color: '#606266',
},
// ======== 信息卡片 ========
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 UnitConverter;
三、核心实现要点
3.1 单位定义
定义各种单位及其转换因子:
const units = {
length: [
{ label: '米', value: 'm', factor: 1 },
{ label: '千米', value: 'km', factor: 1000 },
{ label: '厘米', value: 'cm', factor: 0.01 },
{ label: '毫米', value: 'mm', factor: 0.001 },
{ label: '英寸', value: 'in', factor: 0.0254 },
{ label: '英尺', value: 'ft', factor: 0.3048 },
],
weight: [
{ label: '千克', value: 'kg', factor: 1 },
{ label: '克', value: 'g', factor: 0.001 },
{ label: '毫克', value: 'mg', factor: 0.000001 },
{ label: '磅', value: 'lb', factor: 0.453592 },
{ label: '盎司', value: 'oz', factor: 0.0283495 },
],
temperature: [
{ label: '摄氏度', value: 'c' },
{ label: '华氏度', value: 'f' },
{ label: '开尔文', value: 'k' },
],
};
单位定义要点:
- 使用对象数组存储单位信息
- factor 表示相对于基准单位的转换因子
- 温度单位不需要 factor,使用特殊转换逻辑
3.2 长度和重量转换
使用转换因子实现长度和重量转换:
const fromUnits = units[conversionType];
const fromFactor = fromUnits.find(u => u.value === fromUnit)?.factor || 1;
const toFactor = fromUnits.find(u => u.value === toUnit)?.factor || 1;
convertedValue = (value * fromFactor) / toFactor;
转换逻辑:
- 将输入值转换为基准单位(乘以 fromFactor)
- 将基准单位转换为目标单位(除以 toFactor)
- 例如:1 千米转换为米:1 * 1000 / 1 = 1000 米
3.3 温度转换
温度转换需要特殊处理:
const convertTemperature = useCallback((value: number, from: string, to: string): number => {
// 先转换为摄氏度
let celsius: number;
if (from === 'f') {
celsius = (value - 32) * 5 / 9;
} else if (from === 'k') {
celsius = value - 273.15;
} else {
celsius = value;
}
// 从摄氏度转换为目标单位
if (to === 'f') {
return celsius * 9 / 5 + 32;
} else if (to === 'k') {
return celsius + 273.15;
} else {
return celsius;
}
}, []);
温度转换要点:
- 先将源单位转换为摄氏度
- 再从摄氏度转换为目标单位
- 使用不同的转换公式
3.4 实时转换
输入时实时显示转换结果:
React.useEffect(() => {
if (inputValue) {
const value = parseFloat(inputValue);
if (!isNaN(value)) {
// 执行转换
setResult(convertedValue.toFixed(4));
}
} else {
setResult('');
}
}, [inputValue, fromUnit, toUnit, conversionType, convertTemperature]);
实时转换要点:
- 使用 useEffect 监听输入值和单位变化
- 输入时自动执行转换
- 保留 4 位小数
四、性能优化
4.1 使用 useCallback 优化
使用 useCallback 缓存回调函数:
const convert = useCallback(() => {
const value = parseFloat(inputValue);
if (isNaN(value)) {
setResult('请输入有效的数值');
return;
}
// 转换逻辑
}, [inputValue, fromUnit, toUnit, conversionType]);
const convertTemperature = useCallback((value: number, from: string, to: string): number => {
// 温度转换逻辑
}, []);
为什么使用 useCallback?
- 避免每次渲染都创建新函数
- 减少子组件的重新渲染
- 提升整体性能
4.2 使用 memo 优化
使用 memo 包装组件:
const UnitConverter = memo(() => {
// ...
});
为什么使用 memo?
- 避免不必要的重新渲染
- 提升整体性能
- 在复杂应用中效果更明显
4.3 限制历史记录数量
限制历史记录的数量:
setHistory(prev => [newHistory, ...prev].slice(0, 10));
为什么限制数量?
- 避免历史记录过多占用内存
- 提升性能
- 只显示最近的 10 条记录
五、常见问题与解决方案
5.1 转换结果不正确
问题现象: 转换结果与预期不符
可能原因:
- 转换因子设置错误
- 温度转换公式错误
- 单位选择错误
解决方案:
// 检查转换因子
{ label: '千米', value: 'km', factor: 1000 },
// 检查温度转换公式
if (from === 'f') {
celsius = (value - 32) * 5 / 9;
}
5.2 输入无效数值
问题现象: 输入非数值时显示错误
可能原因:
- 没有验证输入值
- 没有处理 NaN 情况
解决方案:
const value = parseFloat(inputValue);
if (isNaN(value)) {
setResult('请输入有效的数值');
return;
}
5.3 历史记录不更新
问题现象: 转换后历史记录没有更新
可能原因:
- 没有正确更新历史记录状态
- 历史记录对象结构错误
解决方案:
const newHistory = {
type: conversionType === 'length' ? '长度' : conversionType === 'weight' ? '重量' : '温度',
from: units[conversionType].find(u => u.value === fromUnit)?.label || fromUnit,
to: units[conversionType].find(u => u.value === toUnit)?.label || toUnit,
value: inputValue,
result: resultStr,
};
setHistory(prev => [newHistory, ...prev].slice(0, 10));
六、扩展用法
6.1 添加更多单位类型
添加面积、体积、速度等单位类型:
const units = {
length: [...],
weight: [...],
temperature: [...],
area: [
{ label: '平方米', value: 'm2', factor: 1 },
{ label: '平方千米', value: 'km2', factor: 1000000 },
{ label: '平方英尺', value: 'ft2', factor: 0.092903 },
],
volume: [
{ label: '升', value: 'l', factor: 1 },
{ label: '毫升', value: 'ml', factor: 0.001 },
{ label: '加仑', value: 'gal', factor: 3.78541 },
],
};
6.2 添加复制功能
添加复制转换结果的功能:
import { Clipboard } from 'react-native';
const handleCopy = useCallback(() => {
if (result) {
Clipboard.setString(result);
// 显示复制成功提示
}
}, [result]);
6.3 添加反向转换
添加快速反向转换的功能:
const handleReverse = useCallback(() => {
if (result) {
setInputValue(result);
const temp = fromUnit;
setFromUnit(toUnit);
setToUnit(temp);
}
}, [result, fromUnit, toUnit]);
七、总结
单位转换器是一个实用的工具,通过本篇文章,我们学习了:
- 单位定义:定义各种单位及其转换因子
- 转换逻辑:实现长度、重量、温度之间的转换
- 实时转换:输入时实时显示转换结果
- 历史记录:保存最近的转换记录
- 性能优化:使用 useCallback、memo 优化性能
- 错误处理:处理无效输入和转换错误
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐




所有评论(0)