interface IFoo {}
class Point {}
type Baz = IFoo & Point
其实我们关心的是这是否是一个「类型」,不论它是 interface 或 class 或 type,都作为「类型」,其它的都不加前缀,没必要给 interface 加个前缀去独立出来。
延申:不同于 Java 等静态类型语言,TypeScript 考虑到 JavaScript 本身的灵活特性,采用的是 Structural Type System。带来的好处就是不会像 Java 的 Nominal Typing System 一样感觉处处被束缚,使用上会更加简单,同时也达到了类型安全的作用,甚至比 Java 更强大。
TypeScript 比较的并不是类型定义本身,而是类型定义的形状(Shape),即各种约束条件。
示例一:
interface Foo {
name: string
}
type Bar = {
name: string
}
const foo: Foo = { name: 'shiyu' }
const bar: Bar = foo // Okay.
示例二:
class Foo {
say(input: string): number {}
}
class Bar {
say(input: string): number {}
}
const foo: Foo = new Foo() // Okay.
const bar: Bar = new Foo() // Okay.
将 Foo 实例赋值给 Bar 类型的变量时,TypeScript 编译器检查发现该实例上具有 Bar 类型需要的所有约束条件,即一个名为 say 的接受一个 string 参数并返回一个 number 的方法(say(input: string): number),所以不会有任何报错。
使用 PascalCase 为枚举对象本身和枚举成员命名。
// Bad
enum color {
red,
}
// Good
enum Color {
Red,
}
使用 camelCase 为函数命名。
使用 camelCase 为属性或本地变量命名。
// Bad
const DiskInfo
function GetDiskInfo() {}
// Good
const diskInfo
function getDiskInfo() {}
使用 PascalCase 为类命名,类成员使用 camelCase 方式命名。
// Bad
class Foo {
Bar: number
Baz(): number {}
}
// Good
class Foo {
bar: number
baz(): number {}
}
import A from 'a'
import A from 'b'
import MyComponent from '@/components/MyComponent'
import Redirect from '@/components/Redirect'
// 按照相对路径的深度
import foo from '../../foo'
import bar from '../bar'
import bar from './bar'
// interface
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void
}
// type
type User = {
name: string
age: number
}
type SetUser = (name: string, age: number) => void
都允许继承
interface 和 type 都可以继承,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface。虽然效果差不多,但是两者语法不同。
// interface extends interface
interface Name {
name: string
}
interface User extends Name {
age: number
}
// type extends type
type Name = {
name: string
}
type User = Name & { age: number }
// interface extends type
type Name = {
name: string
}
interface User extends Name {
age: number
}
// type extends interface
interface Name {
name: string
}
type User = Name & {
age: number
}
不同点:
type 可以声明基本类型别名、联合类型、交叉类型、元组等类型,而 interface 不行。
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong()
}
interface Cat {
miao()
}
type Pet = Dog | Cat
// 元组类型,具体定义数组每个位置的类型
type PetList = [Dog, Pet]
type 语句中还可以使用 typeof 获取实例的 类型进行赋值。
// 当你想获取一个变量的类型时,使用 typeof
const div = document.createElement('div')
type B = typeof div
interface 能够声明合并,重复声明 type 会报错。
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
总结:
如果使用联合类型、交叉类型、元组等类型的时候,用 type 类型别名
如果需要使用 extends 进行类型继承时,使用 interface
其他类型定义能使用 interface,优先使用 interface
所以,当需要声明用于对象的类型时,应当使用接口,而非对象字面量表达式的类型别名:
// 应当这样做!
interface User {
firstName: string
lastName: string
}
// 不要这样做!
type User = {
firstName: string
lastName: string
}
TypeScript 的 any 类型是所有其它类型的超类,又是所有其它类型的子类,同时还允许解引用一切属性。因此,使用 any 十分危险,它会掩盖严重的程序错误,并且它从根本上破坏了对应的值“具有静态属性”的原则。
尽可能不要使用 any。如果出现了需要使用 any 的场景,可以考虑下列的解决方案:
缩小 any 的影响范围
function f1() {
const x: any = expressionReturningFoo() // 不建议,后续的 x 都是 any 了
processBar(x)
}
function f2() {
const x = expressionReturningFoo()
processBar(x as any) // 建议,只有这里是 any
}
any 类型的值可以赋给其它任何类型,还可以对其解引用任意属性。一般来说,这个行为不是必需的,也不符合期望,此时代码试图表达的内容其实是“该类型是未知的”。在这种情况下,应当使用内建的 unknown 类型。它能够表达相同的语义,并且,因为 unknown 不能解引用任意属性,它较 any 而言更为安全。一个 unknown 类型的变量可以再次赋值为任意其它类型。
类型断言
谨慎使用类型断言和非空断言
类型断言(x as SomeType)和非空断言(y!)是不安全的。这两种语法只能够绕过编译器,而并不添加任何运行时断言检查,因此有可能导致程序在运行时崩溃。因此,除非有明显或确切的理由,否则不应使用类型断言和非空断言。
// 不要这样做!
;(x as Foo).foo()
y!.bar()
如果希望对类型和非空条件进行断言,最好的做法是显式地编写运行时检查。
// 应当这样做!
// 这里假定 Foo 是一个类。
if (x instanceof Foo) {
x.foo()
}
if (y) {
y.bar()
}
// Bad
function fn(x: () => any) {
x()
}
// Good
function fn(x: () => void) {
x()
}
使用 void 相对安全,因为它防止了你不小心使用 x 的返回值:
function fn(x: () => void) {
const k = x() // oops! meant to do something else
k.doSomething() // error, but would be OK if the return type had been 'any'
}
// Bad
declare function fn(x: any): any
declare function fn(x: HTMLElement): number
declare function fn(x: HTMLDivElement): string
let myElem: HTMLDivElement
let x = fn(myElem) // x: any, wat?
// Good
declare function fn(x: HTMLDivElement): string
declare function fn(x: HTMLElement): number
declare function fn(x: any): any
let myElem: HTMLDivElement
let x = fn(myElem) // x: string, :)
优先使用使用可选参数,而不是重载:
// Bad
interface Example {
diff(one: string): number
diff(one: string, two: string): number
diff(one: string, two: string, three: boolean): number
}
// Good
interface Example {
diff(one: string, two?: string, three?: boolean): number
}
使用联合类型,不要为仅在某个位置上的参数类型不同的情况下定义重载:
// Bad
interface Moment {
utcOffset(): number
utcOffset(b: number): Moment
utcOffset(b: string): Moment
}
// Good
interface Moment {
utcOffset(): number
utcOffset(b: number | string): Moment
}
class Animal {
eat() {
console.log('food')
}
}
// Bad
class Dog extends Animal {
eat() {
console.log('bone')
}
}
// Good
class Dog extends Animal {
override eat() {
console.log('bone')
}
}
风格与语言特性
变量申明
必须使用 const 或 let 声明变量。尽可能地使用 const,除非这个变量需要被重新赋值。禁止使用 var。
const foo = otherValue // 如果 foo 不可变,就使用 const。
let bar = someValue // 如果 bar 在之后会被重新赋值,就使用 let
// 不要这么做!
var foo = someValue
每个变量声明语句只声明一个变量:
// Good
let a = 1
let b = 2
// Bad
let c = 1,
d = 2
箭头函数
使用箭头函数代替匿名函数表达式。
// Good
bar(() => {
this.doSomething()
})
// Bad
bar(function () {})
只有当函数需要动态地重新绑定 this 时,才能使用 function 关键字声明函数表达式,但是通常情况下代码中不应当重新绑定 this。
// 应当这样做!
for (const x in someObj) {
if (!someObj.hasOwnProperty(x)) continue
}
// 应当这样做!这里使用 for of 语法
for (const x of Object.keys(someObj)) {
}
// 应当这样做!
for (const [key, value] of Object.entries(someObj)) {
}
数组迭代
不要在数组上使用 for in 进行迭代。这是一个违反直觉的操作,因为它是对数组的下标而非元素进行迭代,还会将其强制转换为 string 类型。
// 不要这样做!
for (const x in someArray) {
// 这里的 x 是数组的下标!(还是 string 类型的!)
}
如果要在数组上进行迭代,应当使用 for of 语句或者传统的 for 循环语句。
// 应当这样做!
for (const x of someArr) {
// 这里的 x 是数组的元素
}
// 应当这样做!
for (let i = 0; i < someArr.length; i++) {
// 如果需要使用下标,就对下标进行迭代,否则就使用 for/of 循环。
const x = someArr[i]
}
// 应当这样做!返回的是索引和值
for (const [i, x] of someArr.entries()) {
}
空格
小括号里开始不要有空白。逗号,冒号,分号后要有一个空格。比如:
for (let i = 0, n = str.length; i < 10; i++) {}
if (x < 10) {
}
function f(x: number, y: string): void {}