无法调用可能是“未定义”的对象。ts(2722)

Posted

技术标签:

【中文标题】无法调用可能是“未定义”的对象。ts(2722)【英文标题】:Cannot invoke an object which is possibly 'undefined'.ts(2722) 【发布时间】:2019-11-16 17:33:39 【问题描述】:

我有一个按钮组件。我只是在我定义的许多可选道具中只传递了一个 onClick 道具:

const Button = (props: ButtonProps) => 
    const handleClick: React.MouseEventHandler<htmlButtonElement | HTMLAnchorElement> = e => 
        props.onClick(e);
    
    return (
        <StyledButton onClick=handleClick>
            props.children
        </StyledButton>
    );
;

那我就是这样用的:

<Button onClick=(e) => 
    console.log(e);
>Click me!</Button>

现在,根据所提到的错误,对象怎么可能是未定义的?我清楚地将函数传递给它,并且根据类型定义也是如此。所以,我将一个对象传递给它。很简单!

...
onClick?: React.MouseEventHandler<HTMLElement>
...

我最近在这个项目中增加了一些更严格的检查,相关的是:

"strictFunctionTypes": true,
"strictNullChecks": true

strict:true 已经存在,这个错误从未发生过。

这里有什么问题?

更新 - 添加类型

export interface IBaseButtonProps 
    type?: ButtonType;
    disabled?: boolean;
    size?: ButtonSize;
    block?: boolean;
    loading?: boolean |  delay?: number ;
    icon?: string;
    className?: string;
    prefixCls?: string;
    children?: React.ReactNode;


export type AnchorButtonProps = 
    href: string,
    target?: string,
    onClick: React.MouseEventHandler<HTMLElement>
 & IBaseButtonProps & Omit<React.AnchorHTMLAttributes<any>, 'type' | 'onClick'>;


export type NativeButtonProps = 
    onClick: React.MouseEventHandler<HTMLElement>,
    htmlType?: ButtonHTMLType
 & IBaseButtonProps & Omit<React.ButtonHTMLAttributes<any>, 'type' | 'onClick'>;

export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>

注意事项:

可能的解决方案是解构道具并添加默认道具。或者使用来自 React 的 defaultProps。但不确定我是否应该对 Typescript 真正要求。

【问题讨论】:

onClick?: React.MouseEventHandler&lt;HTMLElement&gt; 表示onClick 可以未定义。 因为它是可选的? 删除? 以使onClick 成为必需。如果它是可选的,它可以定义为未定义 因为我无法从onClick 中删除?,所以将defaultProps 与打字稿一起添加是个好主意吗?看来我现在必须这样做了。但是我读过很多文章来处理 defaultProps 只用 TS 去掉可选标记后还是一样的人 【参考方案1】:

现在,根据所提到的错误,对象怎么可能是未定义的? [原文]

export type ButtonProps = Partial&lt;AnchorButtonProps &amp; NativeButtonProps&gt; 周围使用Partial&lt;T&gt; 导致onClick 是可选的。当我们使用Partial&lt;T&gt; 时,所有的属性都会接收到?,从而成为可选的,这意味着它们都可以是未定义的。

有两种方法可以解决:一种是保持ButtonPropsonClick 相同,作为可选,并在调用之前检查onClick 是否已定义(修复1);另一种是更改ButtonProps 以使onClick 成为必需(修复2 和3)。

修复 1:onClick 仍然是可选的

使用您已有的ButtonProps,然后在调用它之前检查onClick 是否已定义。这是您在 cmets 中链接的代码中的 what antd does。

const Button = (props: ButtonProps) => 
  const handleClick: React.MouseEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  > = e => 
    if (props.onClick) props.onClick(e); // works
  ;
;

修复 2:onClick 成为必需项

通过不将Partial 应用于NativeButtonProps 来更改ButtonProps

type ButtonProps1 = Partial<AnchorButtonProps> & NativeButtonProps;

const Button1 = (props: ButtonProps1) => 
  const handleClick: React.MouseEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  > = e => 
    props.onClick(e); // works
  ;
;

修复 3:onClick 也成为必需项

定义一个RequireKeys 类型,让您可以指定非可选的键。

type RequireKeys<T, TNames extends keyof T> = T &
   [P in keyof T]-?: P extends TNames ? T[P] : never ;

type ButtonProps2 = RequireKeys<ButtonProps, "onClick">;

const Button2 = (props: ButtonProps2) => 
  const handleClick: React.MouseEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  > = e => 
    props.onClick(e); // works
  ;
;

Mapped Types: removing optional modifier 的答案有更多关于我如何定义RequireKeys&lt;T&gt; 的信息。

【讨论】:

对 Typescript 非常陌生。因此,从这里的生产级代码逐步了解 - github.com/ant-design/ant-design/blob/master/components/button/… 他们使用相同的方式。会不会是版本问题? 如果您看到他们没有在 Button 的类组件中为 onClick 指定任何 defaultProps 加上这一切都很好。但不知何故,我觉得仅仅因为有道具类型就变得复杂了。重要的是,使用上述任何一种方法都会让我在 Button props 本身上出现错误,以便分配。 Type ' children?: ReactNode; ' is missing the following properties from type ' href: never; target: never; onClick: (event: MouseEvent&lt;HTMLElement, MouseEvent&gt;) =&gt; void; type: never; disabled: never; size: never; block: never; loading: never; icon: never; className: never; ... 266 more ...; value: never; ': href, target, onClick, type, and 272 more.ts(2322) 如果我将React.FC 的类型引入到上面的Button 组件中,这将不起作用 您链接的 antd 代码确保在调用 onClick 之前定义了 onClick:github.com/ant-design/ant-design/blob/master/components/button/… 我已经更新了我的答案以表明这是一个可能的解决方案。【参考方案2】:

只是一个明确的答案

if (props.onClick) props.onClick(e);

如果你正在定义一个函数 props 并希望它是可选的,那么将它定义为,

export type ButtonProps = 
  function?: () => void;
;

解释: 如果你想使用一个函数作为 props,可能会有你想要传递该函数(作为 props)的实例,也可能存在你不想传递它的其他实例。

例如,

Common Code WHERE 调用 &lt;Home/&gt; 组件,例如 index.ts/index.js

function myfunction()
  //do something
  alert("hello")


return (
  <>
     <Home myfunction=myfunction/>    //passing prop
     <Home/>                            // not passing
  </>
)

在 JS 中,home.js

export default function Home(myfunction) 
  const const1 = "Hello World"
  return (
    //do something
    myfunction();      //IMPORTANT line
  )

现在,它几乎等同于 TS,home.ts

在 TS 中,我们定义了所有事物的类型。所以,在这种情况下,我们还必须定义这个函数的类型myfunction,我们正在传递。

所以,对于这个函数,我们意识到,

它不接收任何参数,所以()(空括号)就足够了,如果有任何参数,我们也需要为它们定义类型。 什么都不返回,所以返回类型void
export type HomeProps = 
  myfunction?: () => void;
;

export default function Home( myfunction : HomeProps) 
  const const1 = "Hello World"
  return (
    //do something
    if (myfunction) myfunction();      //IMPORTANT line
  )


提示:以上答案

【讨论】:

【参考方案3】:

最好的变体是使用?.call(this: unknown, ...args: any[])?.apply(this: unknown, args: any[]) 方法

所以,假设我们有下一个声明

type callback = ((x: number, y: number) => number) | null;

let a: callback;
let b: callback;

a = (x, y) => x + y;   // it works with arrow functions
b = function (x, y)   // and normal functions
  return x + y;
;

function x(cb1: callback, cb2: callback) 
  console.log(cb1?.call(0, 5, 6));     // in this case you
  console.log(cb2?.call(0, 5, 6));     // cant invoke cb1() or cb2()
  console.log(cb1?.apply(0, [5, 6]));  // but you can use call or apply
  console.log(cb2?.apply(0, [5, 6]));  // where first parameter can be any value


x(a, b); // 11 11 11 11

class C 
  public f?: callback;
  public setF() 
    this.f = (x, y) => 
      return x + y;
    ;
  

const c = new C(); // same with objects
c.setF();
console.log(c?.f?.call(c, 2, 3)); // 5

【讨论】:

【参考方案4】:
 (props.onClick && props.onClick(e));

【讨论】:

虽然这个代码块可能会回答这个问题,但最好能稍微解释一下为什么会这样。【参考方案5】:

使用 Typescript 3.7+,您还可以使用可选链来调用可选的 prop 方法:

const Button = (props: ButtonProps) => 
  const handleClick: React.MouseEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  > = e => 
    props.onClick?.(e); // works
  ;
;

您可以阅读有关使用可选链接的更多信息 - https://www.stefanjudis.com/today-i-learned/optional-chaining-helps-to-avoid-undefined-is-not-a-function-exceptions/

【讨论】:

这比例外的答案好得多!【参考方案6】:

对于接下来的任何人。另一种选择是使用类型转换。 喜欢:

props = props as NativeProps

根据我的经验,我使用了一个返回 Partial 类型对象的上下文,我需要进行类型转换来克服未定义的错误。喜欢:

const setSomething = useContext(SomePartialContext) as MyContextType

【讨论】:

【参考方案7】:

这是帮助我解决此问题的语法。

获得解决方案的不同方法:

search.prop('onSearch')!('text' as unknown)
search.prop('onSearch')!( as unknown)
search.prop('onSearch')!( as any)

关键部分是:!( as any

【讨论】:

以上是关于无法调用可能是“未定义”的对象。ts(2722)的主要内容,如果未能解决你的问题,请参考以下文章

即使在未定义检查之后,打字稿“错误 TS2532:对象可能是‘未定义’”

在类型保护错误 TS2532 之后,Typescript 对象可能未定义

在遍历两个数组时无法读取未定义的属性'id':Angular

即使使用 if 语句,对象也可能未定义

无法读取未定义的属性“URL” - 访问 JSON 对象

在调用 useRef 当前对象的焦点时无法读取未定义的属性(读取“焦点”)