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

相关推荐
世俗ˊ21 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92121 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_26 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人35 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript