这篇文章写的比较复杂,路走歪了。感兴趣的可以继续阅读。 不感兴趣,只想实现功能的可以看这篇(超简单实现): # reactHoots+ts。鉴权组件(超简单,有手就会)
现在需要对antd的 Button组件进行二次封装。来实现鉴权功能(即传入权限信息id,权限信息为true的才会渲染到页面)
本来是很简单的事情,但是我发现antd的ts类型声明有够复杂,而且并不能直接拿过来用,你敢用就敢报错。
然后基于antd组件库5.1.4的Button按钮的API,傻瓜式的一个一个加ts的类型声明。
最后,实在受不了这样的傻瓜式代码,最终进行的优化。
一:一开始图省事,直接简单封装:
import React, { ReactNode } from "react";
import { Button } from "antd";
import { useSelector } from "react-redux";
import { RootState } from "@/store";
type Props = {
// 拿到所有传入进来的所有标签属性
[x: string]: any;
children?: ReactNode;
id: number;
};
// ...rest表示结构除了 children 和 id 之外的所有传入的标签属性
function AuthButton({ children, id, ...rest }: Props) {
// 从仓库中获取按钮权限信息,这里我的权限信息经过整理,以及过滤了 权限不位true的(即:在这个数组里面存在的信息,就是有权限的)
const buttonArr = useSelector(
(state: RootState) => state.loginStore.authButtonArr
);
// 当你使用按钮的时候传递过来的id,能在权限信息里面找到的时候(或者你传-1的时候,这个-1的逻辑不需要可以删掉)才渲染,否则渲染null(即:不渲染任何元素)
return buttonArr.some((v: any) => v.id === id) || id === -1 ? (
// 把传入的标签属性给 antd 的Button组件
<Button {...rest}>{children}</Button>
) : null;
}
// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
const MemoAuthButton = React.memo(AuthButton);
export default MemoAuthButton;
二:使用
<AuthButton
id={102}
type="text"
danger
onClick={() => addObject(item.id)}
>
编辑
</AuthButton>
确实可以使用,也不会报错。但是在使用的时候没有字段提示。
输入ty,不会自动提示type,后面的text也不会自己补全
(使用ts我不能允许这样的事情发生,不然使用ts干嘛!)
三:使用 ts 的 interface 接口 继承 antd 的 Button 按钮 类型
鼠标移入 antd 的 Button 组件,显示类型为 CompoundedComponent
于是:导入 CompoundedComponent,并且继承
import { CompoundedComponent } from "antd/es/float-button/interface";
interface Props extends CompoundedComponent {
// 拿到所有传入进来的所有标签属性
[x: string]: any;
children?: ReactNode;
id: number;
};
然后:坑爹的地方来了,antd的封装,并不是我们简单理解的类似原生元素的封装,继承之后直接使用,你敢用就敢报错
于是我又开始研究 antd 封装的关于 Button 组件的TS类型源码
以下是源码内容
import * as React from 'react';
import type { SizeType } from '../config-provider/SizeContext';
import Group from './button-group';
declare const ButtonTypes: ["default", "primary", "ghost", "dashed", "link", "text"];
export type ButtonType = typeof ButtonTypes[number];
declare const ButtonShapes: ["default", "circle", "round"];
export type ButtonShape = typeof ButtonShapes[number];
declare const ButtonHTMLTypes: ["submit", "button", "reset"];
export type ButtonHTMLType = typeof ButtonHTMLTypes[number];
export type LegacyButtonType = ButtonType | 'danger';
export declare function convertLegacyProps(type?: LegacyButtonType): ButtonProps;
export interface BaseButtonProps {
type?: ButtonType;
icon?: React.ReactNode;
/**
* Shape of Button
*
* @default default
*/
shape?: ButtonShape;
size?: SizeType;
disabled?: boolean;
loading?: boolean | {
delay?: number;
};
prefixCls?: string;
className?: string;
ghost?: boolean;
danger?: boolean;
block?: boolean;
children?: React.ReactNode;
}
export type AnchorButtonProps = {
href: string;
target?: string;
onClick?: React.MouseEventHandler<HTMLElement>;
} & BaseButtonProps & Omit<React.AnchorHTMLAttributes<any>, 'type' | 'onClick'>;
export type NativeButtonProps = {
htmlType?: ButtonHTMLType;
onClick?: React.MouseEventHandler<HTMLElement>;
} & BaseButtonProps & Omit<React.ButtonHTMLAttributes<any>, 'type' | 'onClick'>;
export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>;
type CompoundedComponent = React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLElement>> & {
Group: typeof Group;
};
declare const Button: CompoundedComponent;
export default Button;
发现,里里外外封装了好多层,可能是考虑到其他组件的复用。
当时脑子短路了,然后因为项目比较急,用了傻瓜式的办法:
四:直接把 antd 关于 Button 的所有属性,自己来定义。
import React, { ReactNode } from "react";
import { Button } from "antd";
import { useSelector } from "react-redux";
import { RootState } from "@/store";
type Props = {
children?: ReactNode;
id: number;
danger?: boolean; //设置危险按钮
block?: boolean; //将按钮宽度调整为其父宽度的选项
disabled?: boolean; //设置按钮失效状态
ghost?: boolean; //幽灵属性,使按钮背景透明
href?: string; //点击跳转的地址,指定此属性 button 的行为和 a 链接一致
htmlType?: "button" | "submit" | "reset" | undefined; //设置 button 原生的 type 值,可选值请参考 HTML 标准
icon?: ReactNode; //设置按钮的图标组件
loading?: boolean | { delay: number }; //设置按钮载入状态
shape?: "default" | "circle" | "round"; //设置按钮形状
size?: "large" | "middle" | "small"; //设置按钮大小
target?: string; //相当于 a 链接的 target 属性,href 存在时生效
type?: "primary" | "ghost" | "dashed" | "link" | "text" | "default"; //设置按钮类型
onClick?: () => void; //点击按钮时的回调
};
// console.log('有权限的按钮集合',buttonArr);
function AuthButton({
children,
id,
danger = false,
block = false,
disabled = false,
ghost = false,
href,
htmlType = "button",
icon,
loading = false,
shape = "default",
size = "middle",
target,
type = "default",
onClick,
}: Props) {
const buttonArr = useSelector(
(state: RootState) => state.loginStore.authButtonArr
);
return buttonArr.some((v: any) => v.id === id) || id === -1 ? (
<Button
danger={danger}
block={block}
disabled={disabled}
ghost={ghost}
href={href}
htmlType={htmlType}
icon={icon}
loading={loading}
shape={shape}
size={size}
target={target}
type={type}
onClick={onClick}
>
{children}
</Button>
) : null;
}
const MemoAuthButton = React.memo(AuthButton);
export default MemoAuthButton;
确实完美解决。但是写这种代码我不能忍啊!真的不能忍!
在项目交付初版之后,闲下来又研究了一下 antd ButtonProps 的源码,然后发现我们只需要拿 ButtonProps 这个接口来继承就可以了(当时怎么没注意…………)
于是:
五:接口继承于 ButtonProps
import React, { ReactNode } from "react";
import { Button, ButtonProps } from "antd";
import { useSelector } from "react-redux";
import { RootState } from "@/store";
interface Props extends ButtonProps {
children?: ReactNode;
id: number;
[x: string]: any;
}
function AuthButton({ children, id, ...rest }: Props) {
const buttonArr = useSelector(
(state: RootState) => state.loginStore.authButtonArr
);
return buttonArr.some((v: any) => v.id === id) || id === -1 ? (
<Button {...rest}>{children}</Button>
) : null;
}
const MemoAuthButton = React.memo(AuthButton);
export default MemoAuthButton;
然后,悲剧的又报错了,原因是 antd 里面的 ButtonProps 接口 已经定义过 id 这个属性。我这里又重复定义,所以会报错。
这里最简单的解决办法就是 把自己定义的参数 id 修改为其他,比如:Aid,就能解决
但是!!问题又来了,这样修改之后,我在页面所有用到的地方全部都要修改之前传递的参数 id 改成 Aid。我不能接受!(如果是在项目初期,还没有使用这个组件传递 id 参数的话就没关系,可以不用往下看了。)
最后:最终优化版来了。
六:保留自定义 id 参数的接口继承
使用泛型来覆盖 ButtonProps 里定义的 id 属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
interface Props extends Omit<ButtonProps, "id"> {
children?: ReactNode;
id: number;
[x: string]: any;
}
完整代码:
import React, { ReactNode } from "react";
import { Button, ButtonProps } from "antd";
import { useSelector } from "react-redux";
import { RootState } from "@/store";
// 定义一个接口继承,过滤重复的字段id
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// 过滤接口 ButtonProps 里面的字段id,使用自己定义的 id
interface Props extends Omit<ButtonProps, "id"> {
children?: ReactNode;
id: number;
[x: string]: any;
}
function AuthButton({ children, id, ...rest }: Props) {
const buttonArr = useSelector(
(state: RootState) => state.loginStore.authButtonArr
);
return buttonArr.some((v: any) => v.id === id) || id === -1 ? (
<Button {...rest}>{children}</Button>
) : null;
}
const MemoAuthButton = React.memo(AuthButton);
export default MemoAuthButton;
终于舒服了~,启动项目,完美,没有报错,鉴权功能也完美。