实现一个内容自带行号的textarea

需求背景

最近重构项目的设计里大量用到须要多行输入的组件(支持复制粘贴)。例如输入须要排除的IP列表,须要排除的关键词列表等。在原先的实现里直接采用了textarea,可是此次产品提出了要在每行前面自动带上序号的要求。react

效果图

实现方案

在一番思考以后,想到了如下三个方案:数组

    1. 不用textarea,使用多个input框来模拟。在每次按下enter键以后生成下一个input框和序号。
    1. 使用textarea,在按下enter键以后改变用户输入的文本,加上序号。
    1. 使用textarea,序号所在列使用绝对定位,监听文本变化同步序号列。

简单对比一下三个方案的优劣:bash

方案 优点 劣势
不用考虑滚动带来的定位问题,获取结果比较方便,多个input天然对应一个结果数组 按下enter以后的事件处理要考虑当前索引的位置,要处理删除行的状况, 复制黏贴要作额外处理
不用考虑滚动带来的定位问题 获取结果时要去除行号,要处理删除行的状况,复制黏贴要作额外处理
不须要额外处理复制粘贴,只须要监听textarea的change事件 要考虑绝对定位致使的样式和滚动带来的定位问题

进过对比和尝试,决定使用方案三,下面讨论一下具体的实现antd

具体实现

方案三只须要经过监听textareaonChange事件,经过换行符切割出结果数组,再根据数组生成出序号列就好了。难点在于如何让序号列的每一行看起来和textarea的每一行处于同一行ui

textarea的每行文本高度仅能经过line-height来设置,所以须要将绝对定位的序号列的行高和textarea的行高设为等值。spa

经过textareapadding-left空出位置来容纳序号所在列。设计

监听textareaonScroll事件给序号所在列动态设置top值来同步滚动。code

提一下换行符在js里的转义字符为\norm

代码

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

相关文章
相关标签/搜索