TypeScript 允许一个函数作为 React prop 与函数签名的 in-param 冲突吗?

Eri*_*vic 3 typescript reactjs

为什么 TypeScript 类型检查器允许 prop 的函数参数与定义不严格匹配?

具体来说,我定义了一个 function callbackImpl = (str: string): number,并将其作为定义为 的 React prop 参数提供callback(parameter: string | undefined): number;,这出奇地有效。

这对我来说是不直观的,在我看来非常危险!

但!调用callbackImpl(undefined)我认为正确的方法是行不通的。

一个完整的例子:

import React from "react";

interface Props {
    callback(parameter: string | undefined): number;
}

class A extends React.Component<Props> {
    componentDidUpdate() {
        this.props.callback(undefined);
    }
}

class B extends React.Component {

    private callbackImpl = (str: string): number => {
        // Will crash if str is undefined
        return str.length;
    };

    // THIS IS NOT ALLOWED! And rightly so!
    private callLocalWithUndefined() {
        // TS2345: Argument of type 'undefined' is not assignable to parameter of type 'string'.
        this.callbackImpl(undefined);
    }

    render() {
        return (
            <React.Fragment>
                <A
                    // This is obviously just as illegal as what happens in callLocalWithUndefined,
                    // since callbackImpl explicitly does not accept undefined as the first parameter,
                    // but no type errors here!?
                    callback={this.callbackImpl}
                />
            </React.Fragment>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经设置"strict": true,tsconfig.json

这是一个更完整的tsconfig.json列表,省略了一些本地内容。

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "module": "esnext",
    "allowSyntheticDefaultImports": true,
    "target": "es6",
    "jsx": "react",
    "allowJs": true,
    "strict": true,
    "noEmitOnError": true,
    "plugins": [],
    "baseUrl": "./",
    "paths": {
      // Omitted
    },
    "lib": [
      "es2017", // Object.entries support
      "dom"
    ],
    "types": ["gapi", "gapi.auth2", "node"]
  },
  "exclude": [
    "node_modules"
  ]
}
Run Code Online (Sandbox Code Playgroud)

难道我做错了什么?我的 tsconfig 设置错误吗?我误解了什么吗?

谢谢!

编辑

Titian Cernicova-Dragomir 回答后的其他资源

  • TS 2.6 发行说明中描述的严格函数类型。描述了严格的函数类型检查不涵盖方法。

Tit*_*mir 5

你是对的,这是不安全的。允许这种行为简化从 JS 到 TS 的迁移是有理由的。好消息是您可以通过使用strictFunctionTypes.

例如这个代码

declare const callbackImpl: (str: string) => number

let callback: (parameter: string | undefined) => number;

callback = callbackImpl

callback(undefined);
Run Code Online (Sandbox Code Playgroud)

上面的代码编译通过,strictNullChecks即使它不是你注意到的完全类型安全。但它将无法同时编译strictNullChecksstrictFunctionTypes

注意我假设你已经在使用,strictNullChecks如果你不是那么代码会按预期工作,因为没有这个选项string|undefined只是根据定义string

编辑

上面的一般解释是在问题包含实际代码之前发布的。编译器没有捕获错误的原因是因为您将回调定义为方法。如果将其定义为函数字段,则编译器会捕获错误。我仍然不确定为什么见下文:

interface Props {
    callback: (parameter: string | undefined) => number;
}

class A extends React.Component<Props> {
    componentDidUpdate() {
        this.props.callback(undefined);
    }
}

class B extends React.Component {

    private callbackImpl = (str: string): number => {
        // Will crash if str is undefined
        return str.length;
    };


    render() {
        return (
            <React.Fragment>
                <A
                  // Error now
                    callback={this.callbackImpl}
                />
            </React.Fragment>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

此行为是设计使然。该-strictFunctionTypes标志不适用于原始PR中所述的方法

更严格的检查适用于所有函数类型,除了那些源自方法或构造函数声明的函数类型。专门排除方法以确保通用类和接口(例如 Array)继续主要协变相关。严格检查方法的影响将是更大的破坏性变化,因为大量泛型类型将变得不变(即使如此,我们可能会继续探索这种更严格的模式)。