React Native 问题在文本输入中突出显示主题标签/提及作为用户类型

Sto*_*now 2 javascript react-native

我正在尝试创建一个 TextInput 来检测并突出显示#hashtags@mentions作为用户键入。我使用这个要点中的代码作为起点,从要点上的 GIF 来看,它似乎完全符合我的要求。但是,当我实施此解决方案时,我发现出现了一些意外的延迟。

我将“提及”颜色设置为红色,所有其他文本设置为绿色。您会在 GIF 中注意到,当我将光标移回代码的突出显示部分,然后按空格键并再次开始输入时,从那时起每个字母都会短暂地变成红色,然后翻转为绿色。这是非常微妙的,只有当我将光标移回突出显示的单词并从那里开始输入时,才会出现这种情况。

我在 iOS 模拟器(14.5 和 15.2)、运行 Expo Go 的物理设备(15.2)以及直接在我的物理设备(Expo Go 之外)上从 Xcode 运行应用程序时观察到了相同的行为。

如何避免格式化延迟?

这是一个通过创建的新的 React Native 项目expo init。整个项目粘贴如下:

包.json

{
  "name": "reactnativeplayground",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "~44.0.2",
    "expo-splash-screen": "~0.14.1",
    "expo-status-bar": "~1.2.0",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-native": "0.64.3",
    "react-native-web": "0.17.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9"
  },
  "private": true
}
Run Code Online (Sandbox Code Playgroud)

应用程序.js

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, TextInput } from 'react-native';

export default function App() {
  const [testFormattedContent, setTestFormattedContent] = React.useState('');

  const handleChangeText = (inputText) => {
    const retLines = inputText.split("\n");
    const formattedText = [];
    retLines.forEach((retLine) => {
      const words = retLine.split(" ");
      const contentLength = words.length;
      var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
      words.forEach((word,index) => {
        if (
          (word.startsWith("@") && !format.test(word.substr(1))) ||
          (word.startsWith("#") && !format.test(word.substr(1)))
        ) {
          const mention = (
            <Text key={index} style={{ color: 'red' }}>
              {word}
            </Text>
          );
          if (index !== contentLength - 1) formattedText.push(mention, " ");
          else formattedText.push(mention);
        } else {
          if (index !== contentLength - 1) return formattedText.push(word, " ");
          else return formattedText.push(word);
        }
      });
    });

    setTestFormattedContent(formattedText);
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={{ flex: 1, margin: 50, borderColor: 'black', borderWidth: 1}}
        onChangeText={handleChangeText}
      >
        <Text>{testFormattedContent}</Text>
      </TextInput>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    // alignItems: 'center',
    // justifyContent: 'center',
  },
});


Run Code Online (Sandbox Code Playgroud)

Sam*_*Elk 7

编辑: 我改进了我的解决方案,以便它可以处理多行文本和占位符文本。我将其打包在带有辅助函数的自定义组件中。我还改进了解释。我把解决方案放在上面,解释放在下面。

零食博览会:

https://snack.expo.dev/@misterorrange/user-mention-highlight

辅助函数:

const validateMention = (word) => {
    var format = /[ !#@$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n]/;
    if (
        (word.startsWith('@') && !format.test(word.substr(1))) ||
        (word.startsWith('#') && !format.test(word.substr(1)))
    ) {
        return true;
    }
    return false;
};
export default validateMention;
Run Code Online (Sandbox Code Playgroud)

自定义组件:

import React, { useRef, useState } from 'react';
import { StyleSheet, Text, View, TextInput } from 'react-native';
import validateMention from './helpers/validateMention';

/**
 * @props
 * style: React.StyleSheet
 *
 * placeholder: string | default: 'Type something ...'
 *
 * placeholderTextColor: hexColor | default: #8e8e8e
 */

const FormattedTextInput = (props) => {
    const {
        style,
        placeholder = 'Type something ...',
        placeholderTextColor = '#8e8e8e',
    } = props;
    const [testFormattedContent, setTestFormattedContent] = useState('');
    const refTextInput = useRef(null);

    const handleChangeText = (inputText) => {
        const retLines = inputText.split('\n');
        const formattedText = [];
        retLines.forEach((retLine, index) => {
            if (index !== 0) formattedText.push('\n');
            const words = retLine.split(' ');
            const contentLength = words.length;
            words.forEach((word, index) => {
                if (validateMention(word)) {
                    const mention = (
                        <Text key={index} style={{ color: 'red' }}>
                            {word}
                        </Text>
                    );
                    if (index !== contentLength - 1)
                        formattedText.push(mention, ' ');
                    else formattedText.push(mention);
                } else {
                    if (index !== contentLength - 1)
                        return formattedText.push(word, ' ');
                    else return formattedText.push(word);
                }
            });
        });
        setTestFormattedContent(formattedText);
    };

    return (
        <View
            style={[styles.container, style]}
            onTouchStart={() => {
                refTextInput.current.focus();
            }}
        >
            <Text style={styles.text}>{testFormattedContent}</Text>
            <TextInput
                ref={refTextInput}
                style={styles.text_input}
                onChangeText={handleChangeText}
                placeholder={placeholder}
                placeholderTextColor={placeholderTextColor}
                multiline={true}
            />
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        margin: 50,
        borderColor: 'black',
        borderWidth: 1,
        justifyContent: 'center',
        position: 'relative',
    },
    text_input: {
        color: 'transparent',
        position: 'absolute',
        width: '100%',
    },
    text: {
        position: 'absolute',
    },
});
export default FormattedTextInput;
Run Code Online (Sandbox Code Playgroud)

说明:

问题:

有时,在 TextInput 中键入的字母的颜色会非常短暂地出现错误。

推断问题原因:

从您的代码中,我们可以看到您只为每个用户输入设置一次文本颜色。因此,我们可以推断出某些东西添加虚假彩色字母的速度比您的代码有时间完成。

性能和 Big O 表示法:

为了衡量计算机科学中函数的性能,我们使用 Big O 表示法。

大 O 表示法是一种数学表示法,描述函数在参数趋于特定值或无穷大时的极限行为。在计算机科学中,大 O 表示法用于根据算法的运行时间或空间需求如何随着输入大小的增长而增长来对算法进行分类。

换句话说:

大 O 表示法描述了代码的复杂性。例如,假设您有一个简单的 for 循环遍历数组:

let integer = 0
for (let i = 0; i < array.length; i++) {
    const element = array[i];
    integer+=i;
}
Run Code Online (Sandbox Code Playgroud)

随着数组的增长,for 循环运行的次数也会增长。你的 for 循环有一个很大的 O 表示法 O(n),因为它会对数组中的每个值运行一次。

现在假设您在第一个 for 循环内添加另一个 for 循环。它会运行 n 次,但每次也会运行 n 次。所以它会运行n^2次。换句话说,时间复杂度为 O(n^2)。

https://en.wikipedia.org/wiki/Big_O_notation

https://www.bigocheatsheet.com

问题说明:

当用户键入一个字母时,它TextInput会立即将其添加到其文本字段中。

向字符串添加一个字母的时间复杂度为 O(1)。

但是,您的handleChangeText函数有 2 个循环,因此它的运行时间复杂度为 O(n^2)。

但它还有 2 个正则表达式测试,每个测试都有 O(n) 时间复杂度。所以我们可以说它的时间复杂度为 O(n^3)。

实际上,我们说运行时间复杂度为 O(a*b*c),因为 n 值不同。

所以我们可以看出,问题的根源在于性能。O(1) 总是比 O(n^3) 更快。

解决方案:

我解决这个问题的第一次尝试是提高性能:

事实上,我们可以将其减少到 O(n),因为没有必要每次都遍历所有单词。我们只需要验证用户的最后输入。

我的第一次尝试是使用onKeyPresson 而不是onChangeText,但是当用户longPress在 Backspace 上触发 a 时出现错误。该onKeyPress事件最初会被多次触发,但在一定时间后它会停止触发并从 textInput 中删除所有文本,这导致状态和 TextInput 不同。所以我放弃了这个想法。

我的第二次尝试是使用onChangeText但只关注最后一个词。由于数组访问的时间复杂度为 O(1),因此该解决方案是可以接受的。然而,它并没有如预期地解决问题,并且增加了很多代码复杂性。

然后我承认我的努力是徒劳的,因为我们永远不会比TextInput. 当 textInput 的时间复杂度为 O(1) 时,我们的时间复杂度最多为 O(n)。

因此,我改变了焦点,决定从 中删除显示的文本TextInput,将其绝对放置在文本之上TextInput并使TextInput文本不可见。

我决定为您提供最可预测地解决问题的代码,并且对当前代码进行最少的修改。

PS:React 文档早些时候指出,不应将 React 组件存储在状态中。我在更新的文档中找不到它,但我个人仍然避免这样做。