最近重构项目的设计里大量用到须要多行输入的组件(支持复制粘贴)。例如输入须要排除的IP列表,须要排除的关键词列表等。在原先的实现里直接采用了textarea
,可是此次产品提出了要在每行前面自动带上序号的要求。react
在一番思考以后,想到了如下三个方案:数组
textarea
,使用多个input
框来模拟。在每次按下enter键以后生成下一个input
框和序号。textarea
,在按下enter键以后改变用户输入的文本,加上序号。textarea
,序号所在列使用绝对定位,监听文本变化同步序号列。简单对比一下三个方案的优劣:bash
方案 | 优点 | 劣势 |
---|---|---|
一 | 不用考虑滚动带来的定位问题,获取结果比较方便,多个input天然对应一个结果数组 | 按下enter以后的事件处理要考虑当前索引的位置,要处理删除行的状况, 复制黏贴要作额外处理 |
二 | 不用考虑滚动带来的定位问题 | 获取结果时要去除行号,要处理删除行的状况,复制黏贴要作额外处理 |
三 | 不须要额外处理复制粘贴,只须要监听textarea的change事件 | 要考虑绝对定位致使的样式和滚动带来的定位问题 |
进过对比和尝试,决定使用方案三,下面讨论一下具体的实现antd
方案三只须要经过监听textarea
的onChange
事件,经过换行符切割出结果数组,再根据数组生成出序号列就好了。难点在于如何让序号列的每一行看起来和textarea
的每一行处于同一行。ui
textarea
的每行文本高度仅能经过line-height
来设置,所以须要将绝对定位的序号列的行高和textarea
的行高设为等值。spa
经过textarea
的padding-left
空出位置来容纳序号所在列。设计
监听textarea
的onScroll
事件给序号所在列动态设置top
值来同步滚动。code
提一下换行符在js里的转义字符为\n
。orm
import React, { useState, FunctionComponent } from "react";
import { Input } from "antd"
import { throttle } from "lodash"
const { TextArea } = Input
export interface IAreaFormProps {
defaultList?: string[];
onChange?: (list: string[]) => void;
}
const AreaForm: FunctionComponent<IAreaFormProps> = (props) => {
const { defaultList = [], onChange } = props;
const [list, setList] = useState(defaultList);
const [areaValue, setAreaValue] = useState(defaultList.join("\n"))
const [top, setTop] = useState(0)
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = event.currentTarget.value
const list = value ? value.split("\n") : []
onChange && onChange(list)
setList(list)
setAreaValue(value)
}
const handleScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
setTop(-event.currentTarget.scrollTop)
}
return (
<div className="area-form">
<div className="area-form__content">
<ul className="area-form__index" style={{ top: top + 11 }}>
{
list.map((_, index) => (
<li key={index}>
<span>{index + 1}、</span>
</li>
))
}
</ul>
<TextArea autoFocus onScroll={throttle(handleScroll)} value={areaValue} onChange={e => handleChange(e)} autoComplete="off"></TextArea>
</div>
</div>
)
}
export default AreaForm;
复制代码
若是你们有更好的解决方案,欢迎评论区告知。cdn