返回

RN Paper Modal TextInput 光标闪烁解决

javascript

React Native Paper Modal 中 TextInput 光标闪烁问题的处理

在 React Native 应用开发中使用 React Native Paper 组件库时,在 Modal 中放置 TextInput 组件可能会遇到一个常见的问题:用户在输入文本时,光标会短暂地闪烁。虽然输入的字符能够正常显示,这种闪烁会影响用户体验。 此文分析这个问题出现的原因,并给出几种可能的解决方案。

问题分析:为什么会出现光标闪烁?

光标闪烁的问题通常与 TextInput 组件的状态更新、Modal 组件的渲染机制以及React Native 内部的事件循环有关。 一种可能的解释是,TextInput 的 value 属性更新过于频繁,导致组件重新渲染。 在 Modal 中,这种重新渲染的频率可能会更高,从而加剧了闪烁现象。 尤其是在 TextInput 组件中设置了诸如 onChangeText 这样的事件处理函数时,每次输入都会触发状态更新,因此问题尤为明显。

解决方案一:延迟状态更新

一种策略是通过使用 useRef 存储输入框的临时值,然后在输入停止一段时间后,再更新组件的状态。 这种方法可以有效减少不必要的渲染,从而减轻光标闪烁。

操作步骤:

  1. 引入 useRef hook。
  2. 创建一个 useRef 对象,用于存储临时文本值。
  3. 使用 setTimeout 函数设置一个延迟,在输入停止一段时间后,才更新状态。
  4. 清理掉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。

操作步骤:

  1. 使用 React.memo 包裹 TextInput 组件。
  2. 提供自定义比较函数(可选)。

代码示例:

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.memoTextInput 组件包裹起来,避免不必要的重新渲染。

总结

以上提供的两种方案都旨在减少不必要的重新渲染,从而缓解在 React Native Paper Modal 组件中使用 TextInput 时出现的光标闪烁问题。 在实际应用中,可以根据具体情况选择合适的方案,或者结合使用。