阅读 249

用Rax开发一个联想搜索输入框,内附封装后的npm组件

来淘系实习已经一个半月了,在这里接触了一个之前未了解过的开发框架:Rax。从最开始拿到需求看着Rax一脸蒙蔽,到现在渐渐熟悉用Rax做开发,这个过程中也让我遇到了Rax的大大小小的问题。下面就来分享一下如何用Rax开发一个联想搜索输入框。

需求——开篇

我们拿到手的需求是这样的:有一个输入框,它支持联想搜索,当输入框里有输入时,右侧会出现一个清空的icon,点击这个icon可以可以去清空这个输入框,可能会配“热搜词”,点击“热搜词”将该“热搜词”补全到输入框内。下面是手淘主搜的例子:

尝试——碰壁

这里我们考虑用Rax提供的TextInput组件去实现这个需求,该组件主要有下面这些属性(这里只放了部分属性,并非该组件的完整属性)

当我们看到点击icon清空输入框这个功能时,首先想到用受控组件去做,当点击icon时去改变value属性的值。(虽然TextInput有自带的clear方法,可以用来清空输入框,但是考虑到我们可能还要有赋值的场景,不考虑用clear方法)

import { createElement, useState, Fragment } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import TextInput from 'rax-textinput';
import { EventObject } from 'rax-textinput/lib/types';

import './index.css';

function SearchInput() {
  const [value, setValue] = useState<string>('');

  const onInput = (e: EventObject) => {
    setValue(e.value);

    // 发起请求
    // ...
  }

  return (
    <Fragment>
      <View className="container">
        <TextInput className="input"
                   value={value}
                   onInput={onInput}/>
        {
          value
          && (
            <Text onClick={() => setValue('')}>
              清空
            </Text>
          )
        }
      </View>
      <Text>
        value: {value}
      </Text>
    </Fragment>
  );
}

export default SearchInput;
复制代码

因为我们的项目需要用到web端和weex端,看一下在两个容器中的效果。

web端看上去是没问题的,符合我们预期的效果。

weex端,发现当输入框里已经有中文的时候,我们无法在后面继续输入中文。这就不符合我们的预期,如果把这样一个输入框放在我们的项目里会非常影响用户体验的。

思考——解决

大胆猜测在weex端不能连续输入中文的原因:我们使用的受控组件,用户使用ios系统在输入中文时,会自动在两个字的拼音中间加入空格,而我们在onInput事件中给value赋值导致了拼音输入中断。 考虑换用非受控组件来完成我们这个需求。分析一下,输入框的值可以被两种方式改变:

(1)通过输入框输入

(2)点击“清空”,清空输入框。或者点击“热搜词”,将其赋值到输入框

对这两种赋值方式做处理:

import { createElement, useState, Fragment } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import TextInput from 'rax-textinput';
import { EventObject } from 'rax-textinput/lib/types';

import './index.css';

function SearchInput() {
  const [value, setValue] = useState<string>('');
  const [defaultValue, setDefaultValue] = useState<string>('');
  const [inputKey, setInputKey] = useState<number>(0);

  // (1)输入框输入事件
  const onInput = (value: string) => {
    setValue(value);

    // 发起请求
    // ...
  }

  // (2)设置输入框的值(非输入事件)
  const setInputValue = (text: string) => {
    // 重新渲染输入框,并将要设的值赋给defaultValue
    setInputKey(inputKey + 1);
    setDefaultValue(text);
    /*
        可以考虑延迟重新渲染,优先触发输入事件
        setTimeout(() => {
          setInputKey(inputKey + 1);
          setDefaultValue(text);
        }, 20);
    */

    // 触发输入事件
    onInput(text);
  }

  return (
    <Fragment>
      <View className="container">
        <TextInput className="input"
                   key={inputKey}
                   defaultValue={defaultValue}
                   onInput={(e: EventObject) => onInput(e.value)}/>
        {
          value
          && (
            <Text onClick={() => setInputValue('')}>
              清空
            </Text>
          )
        }
      </View>
      <Text>
        value: {value}
      </Text>
    </Fragment>
  );
}

export default SearchInput;
复制代码

我们看下效果

web端:

weex端:

可以看到,在两种容器里都达到了我们预期的效果。

封装——复用

当我们实现这个功能后考虑到以后复用,决定在Rax的TextInput组件上封装一下,重写它的onInput事件,并加上常用的函数防抖逻辑。

npm包:debounce-text-input

源码:debounce-text-input

使用参考:

import { createElement, useState, Fragment } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import DebounceTextInput from 'debounce-text-input';
import { EventObject } from 'rax-textinput/lib/types';

import './index.css';
function SearchInput() {
  const [value, setValue] = useState<string>('');

  const onInput = (text: string) => {
    setValue(text);
    
    // 网络请求
    // ...
  }

  return (
    <Fragment>
      <View className="container">
        <DebounceTextInput className="input"
                           value={value}
                           debounceInterval={600}
                           onInput={(e: EventObject) => onInput(e.value)}/>
        {
          value
          && (
            <Text onClick={() => onInput('')}>
              清空
            </Text>
          )
        }
      </View>
      <Text>
        debounceInterval: 600
      </Text>
      <Text>
        value: {value}
      </Text>
      <Text onClick={() => onInput('felix')}>
        set: felix
      </Text>
    </Fragment>
  );
}

export default SearchInput;
复制代码

web端:

weex端:

效果——结尾

最终我们这个需求顺利完成,效果如下: