网站搜索

reactjs - 使用 Typescript 使 React 的 "选择文件" Mui 按钮组件通用化


以下代码实现了可点击的 Mui 按钮,点击后会打开系统的“选择文件”窗口。它的用法非常简单,可以通过 <SelectFileButton onFileSelected={(selectedFiles) => { ... }} 触发。

我现在面临的问题是,我试图让它更通用,使任何“可点击”类型都可以替代 Mui Button(行:<Button onClick={handleClick}>{children}</Button>),当然,我在这个艰巨的任务上惨败了。有没有一种简单的解决方法,可以将上述代码行更改为更通用的等效形式?

import React from "react";
import Button from "@mui/material/Button";
import { ChangeEvent, ReactNode } from "react";

type Props = {
  onFileSelected?: (selectedFiles: File | File[]) => void;
  multiple?: boolean;
  children: ReactNode;
};

export const SelectFileButton: React.FC<Props> = ({
  children,
  onFileSelected = null,
  multiple = false,
}) => {
  const hiddenFileInput = React.useRef<HTMLInputElement>(null);

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.files)
      onFileSelected?.(
        multiple ? Array.from(e.target.files) : e.target.files[0]
      );
  };

  const handleClick = () => {
    hiddenFileInput.current!.click();
  };

  return (
    <React.Fragment>
      <Button onClick={handleClick}>{children}</Button> 
      <input
        type="file"
        ref={hiddenFileInput}
        style={{ display: "none" }}
        onChange={handleFileChange}
        multiple={multiple}
      />
    </React.Fragment>
  );
};

答案#1

你可以将任何元素作为属性传递

export const FileUploadButton: React.FC<Props> = ({
  children,
  onFileSelected = null,
  multiple = false,
  ClickableElement,
  onClick,
}) => {
  const hiddenFileInput = React.useRef<HTMLInputElement>(null)

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.files)
      onFileSelected?.(
        multiple ? Array.from(e.target.files) : e.target.files[0],
      )
  }

  const handleUploadClick = () => {
    hiddenFileInput.current!.click()
  }

  return (
    <React.Fragment>
      <ClickableElement onClick={handleUploadClick}>
        {children}
      </ClickableElement>
      <input
        type="file"
        ref={hiddenFileInput}
        style={{ display: 'none' }}
        onChange={handleFileChange}
        multiple={multiple}
      />
    </React.Fragment>
  )
}
 <FileUploadButton children="text" ClickableElement={Button} onClick={() => {....}/>

或者你可以删除 onClick 属性,然后传入带有此属性的元素:

<FileUploadButton children="text" ClickableElement={<Button onClick={() => {....} />} />

当然,你应该根据你选择的解决方案更改属性类型。

但我认为你想过度优化。

https://en.wikipedia.org/wiki/You_aren't_gonna_need_it

这个组件有一个简单的功能,并且包含实现它所需的全部内容。许多依赖使组件难以使用和理解。而且你可以传递的组件可能与按钮相比具有非常不同的属性和行为,这将很难处理。

答案#2

MUI Button 有一个 component 属性,它可以接受一个 elementType(任何 HTML 元素或其他组件)

这个属性可以通过你的属性传递

type Props = {
  onFileSelected?: (selectedFiles: File | File[]) => void;
  multiple?: boolean;
  component?: React.ElementType
  children: ReactNode;
};

然后被 MUI 按钮消耗

<Button component={component} onClick={handleUploadClick}>{children}</Button>

这样,MUI 的实现将确保对传入的任何 elementType 应用适当的类似按钮的可访问性