Material-UI v1.0(即material-ui-next)如何创建带自动补全的芯片输入框?
在Material-UI v1.0中实现带自动补全的芯片输入框
刚好我之前在Material-UI v1.0(也就是material-ui-next)项目里做过类似需求,那个基于v0.20的第三方库确实没法直接适配新版本,下面给你分享两种靠谱的实现方案:
方案一:用官方实验室组件快速实现
Material-UI v1.0核心组件里没有现成的芯片自动补全,但@material-ui/lab实验室包提供的Autocomplete组件,刚好可以和Chip、TextField组合实现需求,这也是最省心的方式。
1. 先安装依赖
(如果还没装的话)
npm install @material-ui/lab @material-ui/core
2. 完整代码示例
import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import Chip from '@material-ui/core/Chip'; const TagInput = () => { // 存储选中的标签(芯片) const [selectedTags, setSelectedTags] = useState([]); // 可选的标签列表,可以是静态数据或从API获取 const availableTags = ['React', 'Material-UI', 'JavaScript', 'TypeScript', 'Node.js']; return ( <Autocomplete // 开启多选模式,支持添加多个芯片 multiple // 传入可选选项 options={availableTags} // 绑定选中的标签数组 value={selectedTags} // 选中/删除标签时更新状态 onChange={(event, newValue) => { setSelectedTags(newValue); }} // 自定义选中项的渲染:转为Chip组件 renderTags={(value, getTagProps) => value.map((tag, index) => ( <Chip key={index} label={tag} // 利用getTagProps自动绑定删除逻辑,不用自己写删除事件 {...getTagProps({ index })} variant="outlined" /> )) } // 渲染输入框载体 renderInput={(params) => ( <TextField {...params} variant="outlined" label="添加标签" placeholder="开始输入选择标签" fullWidth /> )} /> ); }; export default TagInput;
3. 关键细节解读
multiple属性:必须开启,这样才能支持多选并生成多个芯片renderTags:这是核心,它让我们把选中的选项转换成Chip组件,getTagProps会自动处理芯片的删除逻辑,省了很多代码- 自定义选项渲染:如果需要给下拉选项加高亮或者图标,可以用
renderOption属性:
renderOption={(option, { inputValue }) => { // 高亮匹配输入的文本 const match = option.toLowerCase().indexOf(inputValue.toLowerCase()); const before = option.slice(0, match); const matched = option.slice(match, match + inputValue.length); const after = option.slice(match + inputValue.length); return ( <span> {before} <strong>{matched}</strong> {after} </span> ); }}
- 动态数据源:如果选项是从API获取的,只需要把
availableTags改成状态变量,监听输入值变化去请求数据即可,比如:
const [availableTags, setAvailableTags] = useState([]); const [inputValue, setInputValue] = useState(''); // 模拟API请求 useEffect(() => { if (inputValue.length > 0) { const timer = setTimeout(() => { // 这里替换成你的API请求逻辑 const filteredTags = ['React', 'Material-UI', 'JavaScript'].filter(tag => tag.toLowerCase().includes(inputValue.toLowerCase()) ); setAvailableTags(filteredTags); }, 300); return () => clearTimeout(timer); } else { setAvailableTags([]); } }, [inputValue]); // 在Autocomplete中绑定inputValue和onInputChange <Autocomplete // ...其他属性 inputValue={inputValue} onInputChange={(event, newInputValue) => { setInputValue(newInputValue); }} />
方案二:手动组合核心组件(无实验室依赖)
如果不想引入@material-ui/lab,也可以用Material-UI核心组件手动搭建,不过需要自己处理更多交互逻辑:
核心思路
- 用
TextField作为输入框,监听输入事件 - 用
Popper和List来渲染下拉选项列表 - 点击列表项时,把该项添加到芯片数组
- 给
Chip添加删除按钮,点击时移除对应项
简化代码示例
import React, { useState, useRef } from 'react'; import TextField from '@material-ui/core/TextField'; import Chip from '@material-ui/core/Chip'; import Popper from '@material-ui/core/Popper'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import Paper from '@material-ui/core/Paper'; const ManualTagInput = () => { const [selectedTags, setSelectedTags] = useState([]); const [inputValue, setInputValue] = useState(''); const [showOptions, setShowOptions] = useState(false); const inputRef = useRef(null); const availableTags = ['React', 'Material-UI', 'JavaScript', 'TypeScript']; // 过滤匹配的选项 const filteredOptions = availableTags.filter(tag => tag.toLowerCase().includes(inputValue.toLowerCase()) && !selectedTags.includes(tag) ); const handleAddTag = (tag) => { setSelectedTags([...selectedTags, tag]); setInputValue(''); setShowOptions(false); }; const handleRemoveTag = (tagToRemove) => { setSelectedTags(selectedTags.filter(tag => tag !== tagToRemove)); }; return ( <div> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 8 }}> {selectedTags.map(tag => ( <Chip key={tag} label={tag} onDelete={() => handleRemoveTag(tag)} variant="outlined" /> ))} <TextField ref={inputRef} value={inputValue} onChange={(e) => { setInputValue(e.target.value); setShowOptions(e.target.value.length > 0); }} placeholder="添加标签" variant="outlined" size="small" style={{ flexGrow: 1, minWidth: 150 }} /> </div> <Popper open={showOptions && filteredOptions.length > 0} anchorEl={inputRef.current} placement="bottom-start" > <Paper> <List> {filteredOptions.map(tag => ( <ListItem key={tag} button onClick={() => handleAddTag(tag)} > {tag} </ListItem> ))} </List> </Paper> </Popper> </div> ); }; export default ManualTagInput;
这种方式需要自己处理下拉框的定位、输入匹配、键盘导航(比如上下箭头选择)等逻辑,适合需要高度自定义的场景,但代码量会多一些。
内容的提问来源于stack exchange,提问作者Jules




