RN Paper Modal TextInput 光标闪烁解决
2025-02-08 15:35:32
React Native Paper Modal 中 TextInput 光标闪烁问题的处理
在 React Native 应用开发中使用 React Native Paper 组件库时,在 Modal 中放置 TextInput 组件可能会遇到一个常见的问题:用户在输入文本时,光标会短暂地闪烁。虽然输入的字符能够正常显示,这种闪烁会影响用户体验。 此文分析这个问题出现的原因,并给出几种可能的解决方案。
问题分析:为什么会出现光标闪烁?
光标闪烁的问题通常与 TextInput 组件的状态更新、Modal 组件的渲染机制以及React Native 内部的事件循环有关。 一种可能的解释是,TextInput 的 value
属性更新过于频繁,导致组件重新渲染。 在 Modal 中,这种重新渲染的频率可能会更高,从而加剧了闪烁现象。 尤其是在 TextInput 组件中设置了诸如 onChangeText
这样的事件处理函数时,每次输入都会触发状态更新,因此问题尤为明显。
解决方案一:延迟状态更新
一种策略是通过使用 useRef
存储输入框的临时值,然后在输入停止一段时间后,再更新组件的状态。 这种方法可以有效减少不必要的渲染,从而减轻光标闪烁。
操作步骤:
- 引入
useRef
hook。 - 创建一个
useRef
对象,用于存储临时文本值。 - 使用
setTimeout
函数设置一个延迟,在输入停止一段时间后,才更新状态。 - 清理掉setTimeout 的延时
代码示例:
import React, { useState, useRef } from 'react';
import { Portal, Modal, Button, Title, Text, TextInput } from 'react-native-paper';
import { View } from 'react-native';
const MyModal = () => {
const [nameNew, setNameNew] = useState('');
const [emailNew, setEmailNew] = useState('');
const [visibleModalAddPerson, setVisibleModalAddPerson] = useState(false);
const nameNewRef = useRef('');
const emailNewRef = useRef('');
const nameTimeout = useRef(null);
const emailTimeout = useRef(null);
const closeModalAddPerson = () => {
setVisibleModalAddPerson(false);
}
const input1 = useRef(null);
const input2 = useRef(null);
const addPerson = () => {
console.log('Add person pressed!')
}
const handleNameChange = (text) => {
nameNewRef.current = text;
if (nameTimeout.current) {
clearTimeout(nameTimeout.current);
}
nameTimeout.current = setTimeout(() => {
setNameNew(nameNewRef.current);
}, 300); // 300ms 延迟
};
const handleEmailChange = (text) => {
emailNewRef.current = text;
if (emailTimeout.current) {
clearTimeout(emailTimeout.current);
}
emailTimeout.current = setTimeout(() => {
setEmailNew(emailNewRef.current);
}, 300); // 300ms 延迟
};
return (
<Portal>
<Modal visible={visibleModalAddPerson} onDismiss={closeModalAddPerson} contentContainerStyle={{ backgroundColor: 'white', padding: 20, width: '80%', marginHorizontal: '10%'}}>
<View>
<Title style={{alignSelf:'center'}}>Title here</Title>
<Text> </Text>
<TextInput
mode="outlined"
label="Name"
style={{alignSelf:'center', width:'95%'}}
value={nameNew}
onChangeText={handleNameChange}
ref={input1}
returnKeyType='next'
blurOnSubmit={false}
onSubmitEditing={() => input2.current.focus()}
/>
<TextInput
mode="outlined"
label="Email"
style={{alignSelf:'center', width:'95%'}}
value={emailNew}
onChangeText={handleEmailChange}
ref={input2}
returnKeyType='done'
blurOnSubmit={false}
onSubmitEditing={() => addPerson()}
/>
<Button
uppercase={false}
style={{backgroundColor:'#2c3e50', width: '95%', alignSelf:'center', margin: 10}}
labelStyle={{color:'white'}}
onPress={()=>addPerson()}
>Add person</Button>
</View>
</Modal>
</Portal>
);
};
export default MyModal;
在这个示例中,handleNameChange
函数使用了 useRef
来存储 TextInput 的临时值。 在每次输入时,nameNewRef.current
都会更新。 setTimeout
函数设置了一个 300ms 的延迟,只有当用户停止输入 300ms 后,才会将 nameNewRef.current
的值更新到 nameNew
状态。 email
输入框同样适用此策略。
解决方案二:控制组件的渲染
使用 React.memo
可以有选择性地渲染 Modal 中的 TextInput 组件。 通过比较 props 的变化,可以避免不必要的重新渲染,从而减少光标闪烁的可能性。
React.memo 类似于PureComponent, 但它适用于函数组件。 默认情况下,它对 props 进行浅比较,并且只有当 props 发生变化时才重新渲染组件。 你还可以提供自定义比较函数作为第二个参数传递给 React.memo。
操作步骤:
- 使用
React.memo
包裹 TextInput 组件。 - 提供自定义比较函数(可选)。
代码示例:
import React, { useState, useRef } from 'react';
import { Portal, Modal, Button, Title, Text, TextInput } from 'react-native-paper';
import { View } from 'react-native';
const MyModal = () => {
const [nameNew, setNameNew] = useState('');
const [emailNew, setEmailNew] = useState('');
const [visibleModalAddPerson, setVisibleModalAddPerson] = useState(false);
const input1 = useRef(null);
const input2 = useRef(null);
const closeModalAddPerson = () => {
setVisibleModalAddPerson(false);
}
const addPerson = () => {
console.log('Add person pressed!')
}
const MemoizedTextInput = React.memo(TextInput);
return (
<Portal>
<Modal visible={visibleModalAddPerson} onDismiss={closeModalAddPerson} contentContainerStyle={{ backgroundColor: 'white', padding: 20, width: '80%', marginHorizontal: '10%'}}>
<View>
<Title style={{alignSelf:'center'}}>Title here</Title>
<Text> </Text>
<MemoizedTextInput
mode="outlined"
label="Name"
style={{alignSelf:'center', width:'95%'}}
value={nameNew}
onChangeText={nameNew => setNameNew(nameNew)}
ref={input1}
returnKeyType='next'
blurOnSubmit={false}
onSubmitEditing={() => input2.current.focus()}
/>
<MemoizedTextInput
mode="outlined"
label="Email"
style={{alignSelf:'center', width:'95%'}}
value={emailNew}
onChangeText={emailNew => setEmailNew(emailNew)}
ref={input2}
returnKeyType='done'
blurOnSubmit={false}
onSubmitEditing={() => addPerson()}
/>
<Button
uppercase={false}
style={{backgroundColor:'#2c3e50', width: '95%', alignSelf:'center', margin: 10}}
labelStyle={{color:'white'}}
onPress={()=>addPerson()}
>Add person</Button>
</View>
</Modal>
</Portal>
);
};
export default MyModal;
在这个示例中,通过 React.memo
将 TextInput
组件包裹起来,避免不必要的重新渲染。
总结
以上提供的两种方案都旨在减少不必要的重新渲染,从而缓解在 React Native Paper Modal 组件中使用 TextInput 时出现的光标闪烁问题。 在实际应用中,可以根据具体情况选择合适的方案,或者结合使用。