原来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大法也不行,我说的。

相关推荐
zhougl9962 小时前
html处理Base文件流
linux·前端·html
花花鱼2 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_2 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之5 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端5 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡5 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木6 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!7 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷7 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript