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