【React + TypeScript 实现高性能多列多选组件】

引言

在现代Web应用中,多选组件是常见的UI元素,尤其是在需要用户从多个选项中进行选择的场景。本文将介绍如何使用React和TypeScript实现一个功能完整、性能优化的多列多选组件,支持"Select All"功能和垂直填充的多列布局。

组件功能介绍

本文实现的MultiCheck组件具有以下功能:

  1. 基础多选功能:支持用户选择多个选项
  2. "Select All"功能:一键全选/取消全选所有选项
  3. 多列布局:支持垂直填充的多列排列
  4. 受控组件设计:支持外部控制选中状态
  5. 性能优化:使用React Hooks优化渲染性能
  6. 类型安全:完整的TypeScript类型定义

技术栈

  • React 18:使用函数组件和Hooks
  • TypeScript:提供类型安全
  • CSS Grid:实现灵活的多列布局
  • React HooksuseState, useEffect, useCallback

核心实现

1. 类型定义

首先,我们定义组件的类型,确保类型安全:

typescript 复制代码
export type Option = {
  label: string,
  value: string
}

type Props = {
  label?: string,
  options: Option[],
  columns?: number,
  values?: string[]
  onChange?: (options: Option[]) => void,
}

2. 状态管理

使用useState钩子管理选中状态,并通过useEffect实现与外部状态的同步:

typescript 复制代码
const [selectedValues, setSelectedValues] = useState<string[]>(values);

// 监听外部values变化,实现双向更新
useEffect(() => {
  setSelectedValues(values);
}, [values]);

3. 全选功能实现

实现"Select All"功能需要以下逻辑:

typescript 复制代码
// 判断是否全选
const isAllSelected = options.length > 0 && selectedValues.length === options.length;

// 处理全选事件
const handleSelectAllChange = useCallback(() => {
  const newSelected = isAllSelected ? [] : options.map(opt => opt.value);
  setSelectedValues(newSelected);
  
  if (onChange) {
    const selectedOptions = isAllSelected ? [] : [...options];
    onChange(selectedOptions);
  }
}, [options, isAllSelected, onChange]);

4. 多列垂直填充布局

使用CSS Grid实现垂直填充的多列布局:

typescript 复制代码
<div className='div-option-container' style={{
  display: 'grid',
  gridTemplateColumns: `repeat(${Math.min(columns, options.length + 1)}, auto)`,
  gridTemplateRows: `repeat(${Math.ceil((options.length+1) / columns)}, auto)`,
  gridAutoFlow: "column", // 关键:实现垂直填充
  width: '100%'
}}>
  {/* 选项内容 */}
</div>

5. 性能优化

使用useCallback缓存事件处理函数,避免不必要的重新渲染:

typescript 复制代码
const handleOptionChange = useCallback((value: string) => {
  setSelectedValues(prev => {
    const newSelected = prev.includes(value)
      ? prev.filter(v => v !== value)
      : [...prev, value];
    
    if (onChange) {
      const selectedOptions = options.filter(opt => newSelected.includes(opt.value));
      onChange(selectedOptions);
    }
    
    return newSelected;
  });
}, [options, onChange]);

完整组件代码

typescript 复制代码
import './MultiCheck.css';
import React, { useState, useEffect, useCallback } from 'react';

export type Option = {
  label: string,
  value: string
}

type Props = {
  label?: string,
  options: Option[],
  columns?: number,
  values?: string[]
  onChange?: (options: Option[]) => void,
}

const MultiCheck: React.FunctionComponent<Props> = ({
  label,
  options,
  columns = 1,
  values = [],
  onChange
}): JSX.Element => {
  const [selectedValues, setSelectedValues] = useState<string[]>(values);

  useEffect(() => {
    setSelectedValues(values);
  }, [values]);

  const isAllSelected = options.length > 0 && selectedValues.length === options.length;

  const handleSelectAllChange = useCallback(() => {
    const newSelected = isAllSelected ? [] : options.map(opt => opt.value);
    setSelectedValues(newSelected);
    
    if (onChange) {
      const selectedOptions = isAllSelected ? [] : [...options];
      onChange(selectedOptions);
    }
  }, [options, isAllSelected, onChange]);

  const handleOptionChange = useCallback((value: string) => {
    setSelectedValues(prev => {
      const newSelected = prev.includes(value)
        ? prev.filter(v => v !== value)
        : [...prev, value];
      
      if (onChange) {
        const selectedOptions = options.filter(opt => newSelected.includes(opt.value));
        onChange(selectedOptions);
      }
      
      return newSelected;
    });
  }, [options, onChange]);

  return(
      <div className='div-container' style={{ width: `${200*columns}px` }}>
        {label && <div className='div-label-container'>{label}</div>}
        <div className='div-option-container' style={{
          display: 'grid',
          gridTemplateColumns: `repeat(${Math.min(columns, options.length + 1)}, auto)`,
          gridTemplateRows: `repeat(${Math.ceil((options.length+1) / columns)}, auto)`,
          gridAutoFlow: "column",
          width: '100%'
        }}>
          <div className='div-option'>
            <input
              type="checkbox"
              id="select-all"
              checked={isAllSelected}
              onChange={handleSelectAllChange}
            />
            <label htmlFor="select-all">Select All</label>
          </div>
          
          {options.map(option => (
            <div key={option.value} className='div-option'>
              <input
                type="checkbox"
                id={`option-${option.value}`}
                checked={selectedValues.includes(option.value)}
                onChange={() => handleOptionChange(option.value)}
                className='div-option-checkbox'
              />
              <label htmlFor={`option-${option.value}`}>{option.label}</label>
            </div>
          ))}
        </div>
      </div>
  )
}

export default MultiCheck;

使用示例

typescript 复制代码
import React from 'react';
import MultiCheck, { Option } from './MultiCheck';

const App: React.FC = () => {
  const options: Option[] = [
    { label: '选项1', value: '1' },
    { label: '选项2', value: '2' },
    { label: '选项3', value: '3' },
    { label: '选项4', value: '4' },
    { label: '选项5', value: '5' },
    { label: '选项6', value: '6' },
  ];

  const handleChange = (selectedOptions: Option[]) => {
    console.log('选中的选项:', selectedOptions);
  };

  return (
    <div className="App">
      <MultiCheck
        label="多选组件示例"
        options={options}
        columns={2}
        values={['2', '4']}
        onChange={handleChange}
      />
    </div>
  );
};

export default App;

性能优化关键点

  1. 使用useCallback缓存事件处理函数:避免每次渲染都创建新函数
  2. 使用CSS Grid实现高效布局:减少JavaScript计算负担
  3. 合理设计依赖数组:确保Hooks只在必要时重新执行
  4. 函数式状态更新:避免闭包陷阱,确保基于最新状态更新

总结

本文实现的MultiCheck组件是一个功能完整、性能优化的多选组件,支持"Select All"功能和垂直填充的多列布局。通过使用React Hooks和TypeScript,我们实现了一个类型安全、性能优异的组件。

该组件的设计思路和实现方式可以作为开发其他复杂UI组件的参考,尤其是在需要处理大量选项和复杂布局的场景。

扩展建议

  1. 添加搜索功能:支持在大量选项中快速搜索
  2. 支持分组:实现选项的分组显示
  3. 添加键盘导航:提高可访问性
  4. 支持自定义样式:允许外部自定义组件样式
  5. 添加动画效果:提升用户体验

通过不断扩展和优化,这个组件可以适应更多复杂的业务场景,成为一个功能强大的多选组件库。

相关推荐
鹿心肺语2 分钟前
前端HTML转PDF的两种主流方案深度解析
前端·javascript
海石21 分钟前
去到比北方更北的地方—2025年终总结
前端·ai编程·年终总结
一个懒人懒人28 分钟前
Promise async/await与fetch的概念
前端·javascript·html
Mintopia34 分钟前
Web 安全与反编译源码下的权限设计:构筑前后端一致的防护体系
前端·安全
输出输入36 分钟前
前端核心技术
开发语言·前端
Mintopia40 分钟前
Web 安全与反编译源码下的权限设计:构建前后端一体的信任防线
前端·安全·编译原理
林深现海1 小时前
Jetson Orin nano/nx刷机后无法打开chrome/firefox浏览器
前端·chrome·firefox
黄诂多1 小时前
APP原生与H5互调Bridge技术原理及基础使用
前端
前端市界1 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
早點睡3901 小时前
高级进阶 ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-drag-sort
react native·react.js·harmonyos