本文正在参加「金石计划」
一:引言
本篇文章仿写antd组件库的autoComplete组件,文章重点是模拟autoComplete组件的内部功能实现,样式比较丑希望大家包涵。
二:组件分析
观察下图,我们设计一个可配置的autoComplete组件,它主要有包含以下配置。
- className: 类名
- placeholder:初始输入提示
- onSearch:自定义搜索查询
三:问题拆解
在设计组件前,我们可以先考子问题,最后组装起来就实现了目标组件。
**问题1:输入框输入时,数据防抖处理? **
考虑搜索过程中频繁的向后端发送请求,因此我们对搜索的处理添加防抖
-
导入防抖lodash
import {debounce} from "lodash";
-
定义防抖函数
const debunleInput = useCallback(debounce(onInputChange,500),[])
-
添加输入框内
<input type="text" onInput={(e:any)=>{ setInputVal(e.target.value); debunleInput(e) }} />
问题2:如何将用户自定义搜索结果展示?(搜索过程有可能是请求后端,也有可能是前端处理)
由于我们不确定用户处理根据输入框内容如何处理搜索结果,可能是同步代码处理,也有可能是后端异步请求的处理方式,因此我们必须使用Promise.resolve(...)将所有的可能统一变为prmosise操作,然后就可以便捷的获取数据
//输入搜索返回结果处理,onSearch是用户自定义搜索返回的结果
const onInputChange = (e:any) => {
Promise.resolve(onSearch(e.target.value)).then((msg:any)=>{
setList(msg);
setActivedIndex(0);
setIsDownShow(true);
})
}
问题3:如何通过键盘指令控制下拉列表选择?
-
首先我们给输入框添加键盘监听事件
-
根据键盘输入的字符可以获取其唯一的code值,判断是否是上,下,enter,esc指令
const handleKeyDown = (e:any) => { console.log(e.keyCode); switch (e.keyCode) { case 13://enter //选择 setInputVal(list[activedIndex]); setIsDownShow(false); break case 38://↑ if(activedIndex!==0) { setActivedIndex(activedIndex-1); } break case 40://↓ if(activedIndex!==list.length-1) { setActivedIndex(activedIndex+1); } break case 27://esc //关闭并退出 setActivedIndex(0); setIsDownShow(false); break default: break } }
五:autoComplete代码实现
import React,{useState,useCallback, useEffect, useRef} from "react";
import {debounce} from "lodash";
import classNames from "classnames";
interface AutoComplete {
className?:string
options?:any,
placeholder?:string,
onSearch:(e:any)=>void //用户自定义搜索结果
}
/**
* 1.输入框防抖,避免多次请求
* 2.非输入框点击关闭下拉框
*/
const AutoComplate = (props:AutoComplete) => {
const {
options,
className,
placeholder,
onSearch
} = props;
const [list,setList] = useState([]);
const [inputVal,setInputVal] = useState('');
const [isDownShow,setIsDownShow] = useState(false);
const [activedIndex,setActivedIndex] = useState(0);//当前选中的列表
const divRef = useRef<HTMLDivElement>(null);
useEffect(()=>{
const handle = (e:any) => {
const isOutside = !divRef.current?.contains(e.target as Node);
if(isOutside) setIsDownShow(false);
}
document.addEventListener('click',handle);
return ()=>{
document.removeEventListener('click',handle);
}
},[])
//input change
const onInputChange = (e:any) => {
Promise.resolve(onSearch(e.target.value)).then((msg:any)=>{
setList(msg);
setActivedIndex(0);
setIsDownShow(true);
})
}
//防抖处理
const debunleInput = useCallback(debounce(onInputChange,500),[])
//classname
const classes = classNames('auto-complete',className);
//选择下拉列表某一项
const choseItem = (e:any) => {
setInputVal(e);
}
//键盘事件监听
const handleKeyDown = (e:any) => {
console.log(e.keyCode);
switch (e.keyCode) {
case 13://enter
//选择
setInputVal(list[activedIndex]);
setIsDownShow(false);
break
case 38://↑
if(activedIndex!==0) {
setActivedIndex(activedIndex-1);
}
break
case 40://↓
if(activedIndex!==list.length-1) {
setActivedIndex(activedIndex+1);
}
break
case 27://esc
//关闭并退出
setActivedIndex(0);
setIsDownShow(false);
break
default:
break
}
}
return (
<div className={classes} ref={divRef}>
<input
type="text"
placeholder={placeholder}
onKeyDown={handleKeyDown}
onInput={(e:any)=>{
setInputVal(e.target.value);
debunleInput(e)
}}
value={inputVal}
/>
<div className="auto-complete-lists">
{isDownShow && list.length>0 && list.map((item,index)=>(
<div
key={item+''+Math.random()}
className={activedIndex===index?'auto-complete-lists-item auto-complete-lists-item-actived':'auto-complete-lists-item'}
onClick={()=>{
setIsDownShow(false);
choseItem(item);
}}
>
{item}
</div>
))}
</div>
</div>
)
}
export default AutoComplate;
六:功能演示
演示1:基础功能演示
<AutoComplate onSearch={search}/>
const search = (value:any) => {
//模拟后后端请求
return new Promise((resolve,reject)=>{
setTimeout(() => {
let arr = Array.from(new Array(7),item=>Math.floor(Math.random()*10));
resolve(arr);
}, 500);
})
}
演示2:键盘事件演示(纯键盘操作,无鼠标介入)
总结
今天autoComplete组件到此结束,希望大家多多支持,我们下一个组件见。