【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. 添加动画效果:提升用户体验

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

相关推荐
bin91532 小时前
(文后附完整代码)html+css+javascript 弹球射击游戏项目分析
前端·javascript·css·游戏·html·前端开发
qq_459558692 小时前
使用DrissionPage打开Edge
前端·edge
二哈喇子!10 小时前
BOM模型
开发语言·前端·javascript·bom
二哈喇子!10 小时前
Vue2 监听器 watcher
前端·javascript·vue.js
摘星编程10 小时前
OpenHarmony + RN:Bluetooth连接蓝牙外设
react native·react.js·harmonyos
yanyu-yaya10 小时前
前端面试题
前端·面试·前端框架
二哈喇子!11 小时前
使用NVM下载Node.js管理多版本
前端·npm·node.js
GGGG寄了11 小时前
HTML——文本标签
开发语言·前端·html