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

时间:2019-07-06 12:09:54

标签: reactjs typescript

我有一个按钮组件。我只是通过了我定义的许多可选道具中的一个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。

6 个答案:

答案 0 :(得分:1)

在 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/

答案 1 :(得分:0)

  

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

Partial<T>周围使用export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>导致onClick是可选的。当我们使用Partial<T>时,所有属性都将接收?并因此成为可选属性,这意味着所有属性都可以不确定。

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

修正1:onClick仍然是可选的

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

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

修复2:必须使用onClick

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

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<T>的更多信息。

答案 2 :(得分:0)

一个明确的答案

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

如果您要定义一个函数props并希望它是可选的,请将其定义为

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

说明: 如果您想使用某个函数作为道具,则可能会有某些情况想要传递该函数(作为道具),而有些情况下则不想传递该函数。

例如,

在公共代码中调用<Home/>组件,例如 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 :(得分:0)

最好的方法是使用?.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 :(得分:0)

 (props.onClick && props.onClick(e));

答案 5 :(得分:0)

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

props = props as NativeProps

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

const {setSomething} = useContext(SomePartialContext) as MyContextType