原来any大法并不万能

any大法是万能的

在typescript中,都说遇事不决就用any类型,any类型是万能的。比如我们假设存在这样一个封装好的组件。

tsx 复制代码
// 封装好的内部封装好的一个列表组件
export interface ListDataProps {
    data?: any[]
}
const ListDataComponent: React.FC<ListDataProps> = ({ data }) => {
    return (<>{ data?.map((item,index) => (<xxx key={`${item}-${index}`}>
        {/* 内部组件逻辑 */}
    </xxx>))}</>)
}
export default ListDataComponent;

如以上代码所示,此时的data我们很难确定data的类型,因为我们是将用户使用的任意数组数据渲染成一个列表组件,此时any就成了不错的选择,嗯,果然any大法万能。

又比如,我们在不确定一个数据值类型,此时我们就可以使用any来解决这个问题。如:

ts 复制代码
// 假设我们有一个JSON对象,但我们不知道它的具体结构  
const someJson = '{"name": "John", "age": 30, "city": "New York"}';  
  
// 通常,我们会使用JSON.parse来解析这个JSON字符串  
// 但如果我们不想(或不能)事先定义这个对象的类型,我们可以使用as any  
const parsedData: any = JSON.parse(someJson);  
  
// 现在,我们可以像处理普通对象一样处理parsedData,而不需要担心TypeScript的类型检查  
console.log(parsedData.name); // 输出: John  
console.log(parsedData.age); // 输出: 30  
console.log(parsedData.city); // 输出: New York  
  
// 甚至可以访问不存在的属性,而不会在编译时得到错误(但可能会在运行时得到undefined)  
console.log(parsedData.country); // 输出: undefined  
  
// 但是,这样做会失去TypeScript提供的类型安全特性  
// 例如,下面的代码在编译时不会报错,但可能在运行时出错  
const ageAsString = parsedData.age.toString(); // 这里实际上不会出错,因为parsedData.age是数字  
// 但如果我们错误地访问了一个非数字的属性,并尝试调用toString()  
// 例如:const invalidToString = parsedData.name.toString(); // 这没问题  
// 但如果我们不小心写了:const invalidToString = parsedData.someNonExistentNumber.toString(); // 这将在运行时出错

如以上代码所示,我们在不确定需要解析的数据类型时,但是我们确实又能正确读取数据,此时any就帮我们解决了问题,果然any大法是万能的,当然我们也有更好的处理方式,比如使用泛型,不过相当于泛型,any方便简洁快捷,我凭啥要用泛型,我就用any,你能奈我何。

any大法也不万能

示例代码

但是,你想不到万能的any大法也有不万能的时候,废话不多说,我们先来看示例代码,且看如下这样的一个简化版的示例代码。

tsx 复制代码
import React from "react";
import { Fragment } from "react";

interface ComponentAProps {
     mode?: "multiple" | "tags";
}
const ComponentA: React.FC<ComponentAProps> = ({mode}) => {
    return (
        <div>这是一个{mode}</div>
    )
}

interface ComponentBProps {
    mode?: "date" | "year" | "month"
}

const ComponentB: React.FC<ComponentBProps> = ({mode}) => {
    return (
        <div>这是一个{mode}</div>
    )
}

interface ComponentCprops {

}

const ComponentC: React.FC<ComponentCprops> = () => {
    return (<div>没有mode</div>)
}

const components = [
    {
        type: ComponentA,
        componentProps: {
            mode: "tags"
        }
    },
    {
        type: ComponentB,
        componentProps: {
            mode: "date"
        }
    },
    {
        type: ComponentC,
        componentProps: {}
    }
]

type ComponentItem = typeof components[number];

const ComponentD = () => {
    const getChild = (item: ComponentItem, index: number) => {
        const { type: Component, componentProps } = item;
        
        return <Fragment key={`${item}-${index}`}><Component {...componentProps } /></Fragment>
    }
    return (<>{ components?.map((item,index) => getChild(item, index))}</>)
}

export default ComponentD;

以上我们定义了A,B,C三个组件,每个组件分别定义了mode属性的类型,其中C组件是不需要传递mode属性的,但是这里也定义了类型,主要在于C组件可能会有其它props属性,别看这里定义了一个空的接口就觉得没用了。

然后我们定义了一个数组,将组件和组件的props都当作数据定义在里面,并根据这个数组来渲染每一个列表项。此时我们就会发现Component报错,如下所示:

改写1

根据错误提示,我们可能要改写mode类型,如下所示:

tsx 复制代码
type ComponentItem = typeof components[number] & { componentProps: ComponentAProps | ComponentBProps | ComponentCprops };

实际上我们还是会得到如下错误:

改写2

到了这里,我们通常就不会去搞类型了,直接上any大法,如下所示:

tsx 复制代码
type ComponentItem = typeof components[number] & { componentProps: { mode?: any } };

这下总该可以了吧,可往往事实不会如你所期望。如下所示:

改写3

没想到吧?any也有解决不了的问题,就算我们将mode断言成any也不行,如下所示:

tsx 复制代码
// ComponentD组件内部
<Component {...componentProps as { mode: any }} />

此时再看,还是提示类型错误,如下图所示:

解决方式1

唯一的解决方式,那就是将ComponentProps直接断言成any类型,即:

tsx 复制代码
// ComponentD组件内部
<Component {...componentProps as any } />

解决方式2

又或者使用类型注释:

arduino 复制代码
// ComponentD组件内部
// @ts-ignore
<Component {...componentProps } />

也许有人会挺好奇,为什么会存在类似这样的示例代码,实际上真实的业务场景是存在这种场景的,什么情况呢,就是比如我们有3个页面,每个页面就是一个crud,crud中的表单项都有共同的表单项,此时我们就可以封装一个公共的表单项组件,我们把它叫做filter组件。此时我们就可以以数据驱动的方式来渲染这些表单项。

解决方式3

归根结底,这里的问题在于typescript编译器并不能推导出组件的componentProps类型,因此我们需要显示的声明组件的类型和props的类型。如下所示:

tsx 复制代码
// ...
type ComponentItem<T> = {
    type: typeof ComponentA | typeof ComponentB | typeof ComponentC;
    componentProps: Record<string, T>;
};
const components: ComponentItem [] = [ /*与原来代码一致*/ ]
  const getChild = (item: ComponentItem<ComponentAProps | ComponentBProps | ComponentCprops>, index: number) => {
       /*与原来代码一致*/
    }

最后

any大法又如何?any大法也不行,我说的。

相关推荐
sg_knight4 分钟前
VSCode如何修改默认扩展路径和用户文件夹目录到D盘
前端·ide·vscode·编辑器·web
一个处女座的程序猿O(∩_∩)O13 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv14 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯20 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552641 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254882 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl