uni*_*dle 7 type-inference typescript
下面是一些示例代码。TypeScript 推断validStudentsas的类型Students[]。任何阅读代码的人都应该很清楚,因为所有无效记录都被过滤掉了,validStudents所以可以安全地将ValidStudents[].
interface Student {
name: string;
isValid: boolean;
}
type ValidStudent = Student & { isValid: true };
const students: Student[] = [
{
name: 'Jane Doe',
isValid: true,
},
{
name: "Robert'); DROP TABLE Students;--",
isValid: false,
}
];
const validStudents = students.filter(student => student.isValid);
function foo(students: ValidStudent[]) {
console.log(students);
}
// the next line throws compile-time errors:
// Argument of type 'Student[]' is not assignable to parameter of type 'ValidStudent[]'.
// Type 'Student' is not assignable to type 'ValidStudent'.
// Type 'Student' is not assignable to type '{ isValid: true; }'.
// Types of property 'isValid' are incompatible.
// Type 'boolean' is not assignable to type 'true'.ts(2345)
foo(validStudents);
Run Code Online (Sandbox Code Playgroud)
可以通过添加类型断言来使此代码工作:
const validStudents = students.filter(student => student.isValid) as ValidStudent[];
Run Code Online (Sandbox Code Playgroud)
......但感觉有点hacky。(或者也许我只是比我自己更相信编译器!)
有没有更好的方法来处理这个问题?
这里正在发生一些事情。
第一个(次要)问题是,对于您的Student接口,编译器不会将检查isValid属性视为类型保护:
const s = students[Math.random() < 0.5 ? 0 : 1];
if (s.isValid) {
foo([s]); // error!
// ~
// Type 'Student' is not assignable to type 'ValidStudent'.
}
Run Code Online (Sandbox Code Playgroud)
如果对象的类型是判别联合并且您正在检查其判别属性,则编译器只能在检查属性时缩小对象的类型。但Student接口不是联合,歧视或其他;它的isValid属性是联合类型,但Student它本身不是。
幸运的是,您可以Student通过将联合推到顶级来获得几乎等效的受歧视联合版本:
interface BaseStudent {
name: string;
}
interface ValidStudent extends BaseStudent {
isValid: true;
}
interface InvalidStudent extends BaseStudent {
isValid: false;
}
type Student = ValidStudent | InvalidStudent;
Run Code Online (Sandbox Code Playgroud)
现在编译器将能够使用控制流分析来理解上述检查:
if (s.isValid) {
foo([s]); // okay
}
Run Code Online (Sandbox Code Playgroud)
此更改并不重要,因为修复它不会突然使编译器能够推断出您的filter()缩小范围。但是,如果有可能做到这一点,您就需要使用类似可区分联合之类的东西,而不是具有联合值属性的接口。
主要问题是 TypeScript 不会将函数实现内的控制流分析结果传播到调用函数的范围。
function isValidStudentSad(student: Student) {
return student.isValid;
}
if (isValidStudentSad(s)) {
foo([s]); // error!
// ~
// Type 'Student' is not assignable to type 'ValidStudent'.
}
Run Code Online (Sandbox Code Playgroud)
在内部 isValidStudentSad(),编译器知道这student.isValid意味着它student是 a ValidStudent,但在外部 isValidStudentSad(),编译器只知道它返回 aboolean而对传入参数的类型没有影响。
处理这种缺乏推理的一种方法是将此类boolean返回函数注释为用户定义的类型保护函数。编译器无法推断它,但您可以断言它:
function isValidStudentHappy(student: Student): student is ValidStudent {
return student.isValid;
}
if (isValidStudentHappy(s)) {
foo([s]); // okay
}
Run Code Online (Sandbox Code Playgroud)
的返回类型isValidStudentHappy是一个类型谓词,student is ValidStudent。现在编译器将理解这isValidStudentHappy(s)对s.
请注意,在microsoft/TypeScript#16069 中已经建议,也许编译器应该能够为 的返回值推断类型谓词返回类型student.isValid。但是它已经开放了很长时间,我没有看到任何明显的迹象表明它正在处理,所以现在我们不能指望它被实施。
另请注意,您可以将箭头函数注释为用户定义的类型保护......相当于isValidStudentHappy:
const isValidStudentArrow =
(student: Student): student is Student => student.isValid;
Run Code Online (Sandbox Code Playgroud)
我们快到了。如果您将回调注释filter()为用户定义的类型保护,则会发生奇妙的事情:
const validStudents =
students.filter((student: Student): student is ValidStudent => student.isValid);
foo(validStudents); // okay!
Run Code Online (Sandbox Code Playgroud)
调用foo()类型检查!这是因为 TypeScript 标准库类型Array.filter()被赋予了一个新的调用签名,当回调是用户定义的类型保护时,它会缩小数组的范围。万岁!
所以这是编译器可以为您做的最好的事情。缺乏类型保护函数的自动推断意味着在一天结束时,您仍然告诉编译器回调进行了缩小,并且并不比您在问题中使用的类型断言安全多少。但它更安全一些,也许有一天会自动推断类型谓词。
无论如何,希望有帮助。祝你好运!