Effective TypeScript Item 26

TypeScript

发布于 2021

上下文对类型推导的影响

TypeScript 不止是根据变量的值进行类型推导的,上下文也对类型推导有影响。如果你不清楚上下文如何影响类型推导,可能会对推导出的类型产生疑惑。

setLanguage("JavaScript");
// 在 js 里是等价的操作
let language = "JavaScript";
setLanguage(language);

// 在 ts 里,这样做也没有什么问题
function setLanguage(language: string) {
  /* ... */
}

setLanguage("JavaScript"); // OK

let language = "JavaScript";
setLanguage(language); // OK

稍微对例子进行一些修改:

type Language = "JavaScript" | "TypeScript" | "Python";
function setLanguage(language: Language) {
  /* ... */
}

setLanguage("JavaScript"); // OK

let language = "JavaScript";
setLanguage(language);
// ~~~~~~~~ Argument of type 'string' is not assignable
//          to parameter of type 'Language'

let language = 'JavaScript'; 语句意味着,ts 推断出 languagestring 类型的,这很合理。但是 setLanguage 函数想要的是 Language 类型,所以会出错。

要解决这个问题,有两种方法:

let language: Language = "JavaScript";
setLanguage(language); // OK

const language = "JavaScript";
setLanguage(language); // OK

The fundamental issue here is that we’ve separated the value from the context in which it’s used.

更多例子

Tuple Types

function panTo(where: [number, number]) {
  /* ... */
}

panTo([10, 20]); // OK

const loc = [10, 20];
panTo(loc);
//    ~~~ Argument of type 'number[]' is not assignable to
//        parameter of type '[number, number]'

解决:

const loc: [number, number] = [10, 20];
panTo(loc); // OK

但是如果想使用 const 会有别的问题:

const loc = [10, 20] as const;
panTo(loc);
// ~~~ Type 'readonly [10, 20]' is 'readonly'
//     and cannot be assigned to the mutable type '[number, number]'

为了解决以上问题,函数得声明成这样 function panTo(where: readonly [number, number]) { /* ... */ }

Objects

type Language = "JavaScript" | "TypeScript" | "Python";
interface GovernedLanguage {
  language: Language;
  organization: string;
}

function complain(language: GovernedLanguage) {
  /* ... */
}

complain({ language: "TypeScript", organization: "Microsoft" }); // OK

const ts = {
  language: "TypeScript",
  organization: "Microsoft",
};
complain(ts);
//       ~~ Argument of type '{ language: string; organization: string; }'
//            is not assignable to parameter of type 'GovernedLanguage'
//          Types of property 'language' are incompatible
//            Type 'string' is not assignable to type 'Language'

可以使用 const ts: GovernedLanguagelanguage: 'TypeScript' as const,解决。

Callbacks

function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
  fn(Math.random(), Math.random());
}

// TypeScript 根据回调函数的上下文推断出参数的类型
callWithRandomNumbers((a, b) => {
  a; // Type is number
  b; // Type is number
  console.log(a + b);
});

如果没有回调函数上下文,ts 不能推断出参数的类型,只能说是 any 类型的。

const fn = (a, b) => {
  // ~    Parameter 'a' implicitly has an 'any' type
  //    ~ Parameter 'b' implicitly has an 'any' type
  console.log(a + b);
};
callWithRandomNumbers(fn);

总结