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

相关推荐
王哲晓1 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4114 分钟前
无网络安装ionic和运行
前端·npm
理想不理想v6 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云16 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:1379712058718 分钟前
web端手机录音
前端
齐 飞24 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹41 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试