【问题标题】:Cannot invoke an object which is possibly 'undefined'.ts(2722)无法调用可能是“未定义”的对象。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
  • 去掉可选标记后还是一样的人

标签: reactjs typescript


【解决方案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 之前定义了 onClickgithub.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}

        【讨论】:

          猜你喜欢
          • 2021-10-27
          • 2021-12-17
          • 2021-06-03
          • 2020-10-02
          • 1970-01-01
          • 2021-08-07
          • 1970-01-01
          • 1970-01-01
          • 2019-09-16
          相关资源
          最近更新 更多