You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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组件,刚好可以和ChipTextField组合实现需求,这也是最省心的方式。

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作为输入框,监听输入事件
  • PopperList来渲染下拉选项列表
  • 点击列表项时,把该项添加到芯片数组
  • 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

火山引擎 最新活动