React Native for OpenHarmony 实战:大小写转换实现
React Native 实现大小写转换工具,支持 8 种转换格式(如大写、小写、驼峰命名等)。核心功能包括: 状态管理:使用 useState 存储文本,useRef 创建独立动画值 流畅动画:按钮点击时触发缩放动画(先压缩后弹回) 转换函数:数组定义 8 种转换逻辑,包含正则表达式处理 数据驱动:每种转换方式包含名称、图标和转换函数 性能优化:使用原生驱动动画,避免卡顿 对比鸿蒙 ArkTS

今天我们用 React Native 实现一个大小写转换工具,支持 8 种常用的转换格式(大写、小写、驼峰命名、下划线命名等),还带有流畅的按钮动画。
状态设计
import React, { useState, useRef } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Animated } from 'react-native';
export const CaseConverter: React.FC = () => {
const [text, setText] = useState('');
const buttonAnims = useRef(Array(8).fill(0).map(() => new Animated.Value(1))).current;
const textAnim = useRef(new Animated.Value(1)).current;
状态设计非常简洁明了:
文本内容 text:存储用户输入的文本,也是转换操作的目标。初始为空字符串,等待用户输入。
按钮动画数组 buttonAnims:8 个转换按钮各有一个独立的动画值,初始都是 1(正常大小)。为什么每个按钮都需要独立的动画值?因为用户可能快速点击不同的按钮,如果共用一个动画值,动画会互相干扰,看起来很混乱。用独立的动画值,每个按钮的动画都是独立的,互不影响。
文本动画 textAnim:输入框的缩放动画,初始值 1 表示正常大小。当用户点击转换按钮时,输入框会轻微缩放,给用户"文本已更新"的视觉反馈。
Array(8).fill(0).map(() => new Animated.Value(1)) 这行代码创建了 8 个独立的动画值对象。fill(0) 先填充 8 个占位元素,map 遍历每个元素创建新的 Animated.Value。不能直接 fill(new Animated.Value(1)),因为那样会让所有元素共享同一个对象。
动画函数
const animateButton = (index: number) => {
Animated.sequence([
Animated.timing(buttonAnims[index], { toValue: 0.9, duration: 100, useNativeDriver: true }),
Animated.spring(buttonAnims[index], { toValue: 1, friction: 3, useNativeDriver: true }),
]).start();
// 文本动画
Animated.sequence([
Animated.timing(textAnim, { toValue: 0.95, duration: 100, useNativeDriver: true }),
Animated.spring(textAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
]).start();
};
这个函数在用户点击转换按钮时触发,同时播放两个动画:
按钮动画:
- 第一步:用
Animated.timing在 100 毫秒内把按钮缩小到 90%(toValue: 0.9) - 第二步:用
Animated.spring弹回到原大小(toValue: 1),friction: 3设置摩擦力,值越小弹性越大
这个动画模拟了"按下按钮"的物理效果。先压缩,再弹回,就像按下一个真实的按钮。100 毫秒的时长很短,用户感觉不到延迟,但足以产生明显的视觉反馈。
文本动画:
- 同样是先缩小(95%)再弹回(100%)
friction: 4比按钮的摩擦力大,弹性稍弱,动画更稳定
为什么文本缩小到 95% 而按钮缩小到 90%?因为输入框比按钮大,相同的缩放比例在大元素上更明显。95% 的缩放对输入框来说已经很明显了,如果也用 90%,会显得太夸张。
两个动画同时启动,但互不干扰。按钮动画只影响被点击的那个按钮,文本动画影响输入框。这种组合动画让用户清楚地知道:我点击了这个按钮,文本已经更新了。
useNativeDriver: true 让动画在原生层执行,不占用 JavaScript 线程,性能更好。即使用户快速连续点击多个按钮,动画也不会卡顿。
转换函数定义
const conversions = [
{ name: '大写', icon: '🔠', fn: (s: string) => s.toUpperCase() },
{ name: '小写', icon: '🔡', fn: (s: string) => s.toLowerCase() },
{ name: '首字母大写', icon: '🔤', fn: (s: string) => s.replace(/\b\w/g, c => c.toUpperCase()) },
{ name: '句首大写', icon: '📝', fn: (s: string) => s.toLowerCase().replace(/(^\w|[.!?]\s+\w)/g, c => c.toUpperCase()) },
{ name: '反转大小写', icon: '🔄', fn: (s: string) => s.split('').map(c => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).join('') },
{ name: '驼峰命名', icon: '🐫', fn: (s: string) => s.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase()) },
{ name: '下划线命名', icon: '➖', fn: (s: string) => s.toLowerCase().replace(/\s+/g, '_') },
{ name: '中划线命名', icon: '〰️', fn: (s: string) => s.toLowerCase().replace(/\s+/g, '-') },
];
这个数组定义了 8 种转换方式,每种包含名称、图标、转换函数。这种数据驱动的设计让代码非常简洁,添加新的转换方式只需要在数组里加一项。
大写转换:toUpperCase() 是 JavaScript 字符串的内置方法,把所有字母转成大写。比如 "Hello World" 变成 "HELLO WORLD"。这是最简单的转换,一行代码搞定。
小写转换:toLowerCase() 把所有字母转成小写。比如 "Hello World" 变成 "hello world"。和大写转换对应,也是内置方法。
首字母大写:/\b\w/g 匹配每个单词的首字母。\b 是单词边界,\w 是单词字符(字母、数字、下划线)。g 表示全局匹配,找到所有单词的首字母。replace 的第二个参数是函数,接收匹配到的字符,返回大写版本。比如 "hello world" 变成 "Hello World"。
这个转换常用于标题格式化。英文标题通常每个单词首字母大写(Title Case),这个函数就是实现这个效果。
句首大写:先把整个字符串转成小写,然后用正则匹配两种情况:
^\w:字符串开头的字母(第一个句子的首字母)[.!?]\s+\w:句号、感叹号、问号后面跟空白字符再跟字母(后续句子的首字母)
这个转换模拟了正常的英文书写规范:每个句子的首字母大写,其他字母小写。比如 "HELLO. HOW ARE YOU?" 变成 "Hello. How are you?"。
反转大小写:把字符串拆成字符数组,遍历每个字符,如果是大写就转小写,如果是小写就转大写。c === c.toUpperCase() 判断字符是否是大写,如果相等说明是大写(或者不是字母)。最后用 join('') 把字符数组拼回字符串。
这个转换比较少用,但在某些场景下很有趣。比如 "Hello World" 变成 "hELLO wORLD",所有大小写都反转了。
驼峰命名:先转成小写,然后用正则 /[^a-zA-Z0-9]+(.)/g 匹配"非字母数字字符 + 一个字符"的组合。[^a-zA-Z0-9]+ 匹配一个或多个非字母数字字符(空格、下划线、中划线等),(.) 捕获后面的一个字符。replace 的第二个参数 (_, c) => c.toUpperCase() 中,_ 是整个匹配(我们不需要),c 是捕获的字符,把它转成大写。
比如 "hello world" 变成 "helloWorld","user_name" 变成 "userName"。这是 JavaScript 中最常用的命名风格,变量名、函数名都用驼峰命名。
下划线命名:先转成小写,然后把所有空白字符(\s+)替换成下划线。比如 "Hello World" 变成 "hello_world"。这是 Python、Ruby 等语言常用的命名风格,也叫蛇形命名(snake_case)。
中划线命名:和下划线命名类似,只是把空白字符替换成中划线。比如 "Hello World" 变成 "hello-world"。这是 CSS 类名、URL slug 常用的格式,也叫烤串命名(kebab-case)。
每个转换函数都是纯函数,接收字符串返回新字符串,不修改原字符串。这符合函数式编程的思想,也让代码更容易测试和维护。
鸿蒙 ArkTS 对比:转换函数
conversions = [
{ name: '大写', icon: '🔠', fn: (s: string) => s.toUpperCase() },
{ name: '小写', icon: '🔡', fn: (s: string) => s.toLowerCase() },
{ name: '首字母大写', icon: '🔤', fn: (s: string) => s.replace(/\b\w/g, c => c.toUpperCase()) },
{ name: '句首大写', icon: '📝', fn: (s: string) => s.toLowerCase().replace(/(^\w|[.!?]\s+\w)/g, c => c.toUpperCase()) },
{ name: '反转大小写', icon: '🔄', fn: (s: string) => s.split('').map(c => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).join('') },
{ name: '驼峰命名', icon: '🐫', fn: (s: string) => s.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase()) },
{ name: '下划线命名', icon: '➖', fn: (s: string) => s.toLowerCase().replace(/\s+/g, '_') },
{ name: '中划线命名', icon: '〰️', fn: (s: string) => s.toLowerCase().replace(/\s+/g, '-') },
]
ArkTS 中的转换函数完全一样,因为字符串方法和正则表达式都是 JavaScript 标准 API,跨平台通用。这就是 React Native 的优势——核心业务逻辑可以直接复用,不需要为每个平台重写。
界面渲染:头部和输入框
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerIcon}>🔤</Text>
<Text style={styles.headerTitle}>大小写转换</Text>
<Text style={styles.headerSubtitle}>多种格式一键转换</Text>
</View>
<Animated.View style={[styles.inputCard, { transform: [{ scale: textAnim }] }]}>
<TextInput
style={styles.input}
value={text}
onChangeText={setText}
multiline
placeholder="输入要转换的文本..."
placeholderTextColor="#666"
/>
</Animated.View>
头部区域包含图标、标题、副标题,和其他工具保持一致的风格。🔤 图标表示"字母"的概念,和大小写转换的功能相符。
输入框动画:用 Animated.View 包裹输入框卡片,应用缩放动画。注意动画应用在卡片上,而不是 TextInput 本身,这样不会影响输入框的内部状态和光标位置。如果直接给 TextInput 加动画,可能会导致输入时光标跳动或者输入框失焦。
多行输入:multiline 属性让输入框支持换行,用户可以输入多行文本。minHeight: 150 确保输入框有足够的高度,即使只有一行文本,输入框也不会太小。
受控组件:value={text} 和 onChangeText={setText} 让输入框成为受控组件。输入框的内容完全由 text 状态控制,用户的输入会通过 setText 更新状态,状态更新又会触发输入框重新渲染。这种单向数据流让状态管理更清晰。
占位符:placeholder 提示用户"输入要转换的文本",明确告诉用户这个工具的用法。placeholderTextColor="#666" 设置占位符颜色为灰色,和实际输入的白色文本形成对比,让用户知道这只是提示文字。
转换按钮渲染
<View style={styles.buttons}>
{conversions.map(({ name, icon, fn }, index) => (
<Animated.View
key={name}
style={[styles.btnWrapper, { transform: [{ scale: buttonAnims[index] }] }]}
>
<TouchableOpacity
style={styles.btn}
onPress={() => { animateButton(index); setText(fn(text)); }}
activeOpacity={0.8}
>
<Text style={styles.btnIcon}>{icon}</Text>
<Text style={styles.btnText}>{name}</Text>
</TouchableOpacity>
</Animated.View>
))}
</View>
</ScrollView>
);
};
按钮区域用网格布局,每行两个按钮。map 遍历 conversions 数组,为每种转换方式生成一个按钮。
解构赋值:{ name, icon, fn } 从数组元素中提取需要的属性,让代码更简洁。index 是数组索引,用于访问对应的动画值。
动画包裹:每个按钮用 Animated.View 包裹,应用独立的缩放动画。key={name} 是 React 列表渲染的要求,用唯一的 key 标识每个元素,帮助 React 优化渲染性能。
点击处理:onPress 做了两件事:
animateButton(index):触发按钮和文本的动画setText(fn(text)):调用转换函数,把结果更新到状态
这两个操作的顺序很重要。先触发动画,再更新文本,这样动画和文本更新几乎同时发生,用户感觉很流畅。如果先更新文本再触发动画,可能会有轻微的延迟感。
按钮内容:图标和文字横向排列,flexDirection: 'row' 实现水平布局。图标在左,文字在右,marginRight: 8 在它们之间留出间距。
触摸反馈:activeOpacity={0.8} 让按钮按下时稍微变暗,配合缩放动画,给用户双重反馈。视觉上,按钮变暗了;动画上,按钮缩小了。这种多重反馈让用户确信"我的操作已经生效"。
鸿蒙 ArkTS 对比:按钮渲染
Grid() {
ForEach(this.conversions, (item: ConversionItem, index: number) => {
GridItem() {
Button() {
Row() {
Text(item.icon)
.fontSize(18)
.margin({ right: 8 })
Text(item.name)
.fontSize(14)
.fontWeight(FontWeight.Medium)
}
}
.width('100%')
.backgroundColor('#1a1a3e')
.borderRadius(12)
.border({ width: 1, color: '#3a3a6a' })
.onClick(() => {
this.animateButton(index)
this.text = item.fn(this.text)
})
.scale({ x: this.buttonScales[index], y: this.buttonScales[index] })
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(8)
.columnsGap(8)
鸿蒙用 Grid 组件实现网格布局,columnsTemplate('1fr 1fr') 定义两列等宽。ForEach 遍历数据,Button 组件自带点击效果。scale 属性绑定动画值,实现缩放动画。
布局结构和 React Native 类似,只是语法不同。核心逻辑(转换函数、动画触发)完全一样。
样式定义:容器和头部
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0f0f23', padding: 20 },
header: { alignItems: 'center', marginBottom: 24 },
headerIcon: { fontSize: 50, marginBottom: 8 },
headerTitle: { fontSize: 28, fontWeight: '700', color: '#fff' },
headerSubtitle: { fontSize: 14, color: '#888', marginTop: 4 },
容器用深蓝色背景 #0f0f23,这是整个应用的统一配色。padding: 20 在四周留出边距,让内容不会紧贴屏幕边缘。
头部元素全部居中对齐(alignItems: 'center'),图标、标题、副标题垂直排列,形成清晰的视觉层次。字号从大到小(50 → 28 → 14),颜色从亮到暗(emoji → 白色 → 灰色),引导用户的视线从上到下阅读。
样式定义:输入框
inputCard: {
backgroundColor: '#1a1a3e',
borderRadius: 20,
padding: 4,
marginBottom: 20,
borderWidth: 1,
borderColor: '#3a3a6a',
},
input: {
backgroundColor: '#252550',
padding: 16,
borderRadius: 16,
minHeight: 150,
fontSize: 16,
color: '#fff',
},
输入框用双层结构:外层是卡片容器(inputCard),内层是实际的输入框(input)。
卡片容器:深蓝色背景 #1a1a3e,圆角 20,内边距 4。这个 4 像素的内边距很关键,它在卡片和输入框之间留出一圈间隙,形成"内嵌"效果。边框颜色 #3a3a6a 比背景稍亮,让卡片有立体感。
输入框:更深的背景色 #252550,和卡片背景形成对比。圆角 16 比卡片的圆角 20 小,这样四个角不会超出卡片。最小高度 150 像素,确保输入框有足够的空间显示多行文本。
白色文字(color: '#fff')在深色背景上很醒目,16 号字体大小适中,既不会太小看不清,也不会太大占空间。
样式定义:按钮
buttons: { flexDirection: 'row', flexWrap: 'wrap' },
btnWrapper: { width: '48%', margin: '1%' },
btn: {
backgroundColor: '#1a1a3e',
padding: 16,
borderRadius: 12,
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#3a3a6a',
},
btnIcon: { fontSize: 18, marginRight: 8 },
btnText: { color: '#fff', fontSize: 14, fontWeight: '600' },
});
按钮区域用 Flexbox 布局,flexWrap: 'wrap' 让按钮自动换行。
按钮包裹器:宽度 48%,左右各留 1% 的外边距。两个按钮加起来:48% + 1% + 1% + 48% + 1% + 1% = 100%,正好占满一行。这种百分比布局可以自适应不同屏幕宽度。
按钮样式:深蓝色背景,圆角 12,内边距 16。flexDirection: 'row' 让图标和文字横向排列,justifyContent: 'center' 让它们水平居中,alignItems: 'center' 让它们垂直居中。
图标和文字:图标 18 号字体,文字 14 号字体,图标稍大一点更醒目。文字用中等粗细(fontWeight: '600'),比正常文字粗一点,但不像标题那么粗。
边框颜色和输入框一样,保持视觉一致性。所有卡片、按钮、输入框都用相同的配色方案,让界面看起来很统一。
小结
这个大小写转换工具展示了数据驱动设计的优势。8 种转换方式用一个数组定义,渲染逻辑只需要一个 map,代码简洁易维护。正则表达式实现了首字母大写、句首大写、驼峰命名等复杂转换。按钮和输入框的动画让工具用起来更流畅。在 OpenHarmony 平台上,字符串方法和正则表达式是 JavaScript 标准 API,核心代码不需要修改。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)