Item 59: Use Never Types to Perform Exhaustiveness Checking
要点
- Use an assignment to the
never
type to ensure that all possible values of a type are handled (an "exhaustiveness check"). - Add a return type annotation to functions that return from multiple branches. You may still want an explicit exhaustiveness check, however.
- Consider using template literal types to ensure that every combination of two or more types is handled.
- 使用赋值给
never
类型来确保所有可能的类型值都被处理(“穷尽性检查”)。 - 为返回多个分支的函数添加返回类型注解。尽管如此,你仍然可能需要显式的穷尽性检查。
- 考虑使用模板字面量类型来确保每种两种或更多类型的组合都被处理。
正文
ts
type Coord = [x: number, y: number]
interface Box {
type: 'box'
topLeft: Coord
size: Coord
}
interface Circle {
type: 'circle'
center: Coord
radius: number
}
type Shape = Box | Circle
ts
function drawShape(shape: Shape, context: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
context.rect(...shape.topLeft, ...shape.size)
break
case 'circle':
context.arc(...shape.center, shape.radius, 0, 2 * Math.PI)
break
}
}
ts
interface Line {
type: 'line'
start: Coord
end: Coord
}
type Shape = Box | Circle | Line
ts
function processShape(shape: Shape) {
switch (shape.type) {
case 'box':
break
case 'circle':
break
case 'line':
break
default:
shape
// ^? (parameter) shape: never
}
}
ts
function processShape(shape: Shape) {
switch (shape.type) {
case 'box':
break
case 'circle':
break
// (forgot 'line')
default:
shape
// ^? (parameter) shape: Line
}
}
ts
function assertUnreachable(value: never): never {
throw new Error(`Missed a case! ${value}`)
}
function drawShape(shape: Shape, context: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
context.rect(...shape.topLeft, ...shape.size)
break
case 'circle':
context.arc(...shape.center, shape.radius, 0, 2 * Math.PI)
break
default:
assertUnreachable(shape)
// ~~~~~
// ... type 'Line' is not assignable to parameter of type 'never'.
}
}
ts
function drawShape(shape: Shape, context: CanvasRenderingContext2D) {
switch (shape.type) {
case 'box':
context.rect(...shape.topLeft, ...shape.size)
break
case 'circle':
context.arc(...shape.center, shape.radius, 0, 2 * Math.PI)
break
case 'line':
context.moveTo(...shape.start)
context.lineTo(...shape.end)
break
default:
assertUnreachable(shape) // ok
}
}
ts
function getArea(shape: Shape): number {
// ~~~~~~ Function lacks ending return statement and
// return type does not include 'undefined'.
switch (shape.type) {
case 'box':
const [width, height] = shape.size
return width * height
case 'circle':
return Math.PI * shape.radius ** 2
}
}
ts
function getArea(shape: Shape): number {
switch (shape.type) {
case 'box':
const [width, height] = shape.size
return width * height
case 'circle':
return Math.PI * shape.radius ** 2
case 'line':
return 0
default:
return assertUnreachable(shape) // ok
}
}
ts
function processShape(shape: Shape) {
switch (shape.type) {
case 'box':
break
case 'circle':
break
default:
const exhaustiveCheck: never = shape
// ~~~~~~~~~~~~~~~ Type 'Line' is not assignable to type 'never'.
throw new Error(`Missed a case: ${exhaustiveCheck}`)
}
}
ts
function processShape(shape: Shape) {
switch (shape.type) {
case 'box':
break
case 'circle':
break
default:
shape satisfies never
// ~~~~~~~~~ Type 'Line' does not satisfy the expected type 'never'.
throw new Error(`Missed a case: ${shape}`)
}
}
ts
type Play = 'rock' | 'paper' | 'scissors'
function shoot(a: Play, b: Play) {
if (a === b) {
console.log('draw')
} else if (
(a === 'rock' && b === 'scissors') ||
(a === 'paper' && b === 'rock')
) {
console.log('A wins')
} else {
console.log('B wins')
}
}
ts
function shoot(a: Play, b: Play) {
const pair = `${a},${b}` as `${Play},${Play}` // or: as const
// ^? const pair: "rock,rock" | "rock,paper" | "rock,scissors" |
// "paper,rock" | "paper,paper" | "paper,scissors" |
// "scissors,rock" | "scissors,paper" | "scissors,scissors"
switch (pair) {
case 'rock,rock':
case 'paper,paper':
case 'scissors,scissors':
console.log('draw')
break
case 'rock,scissors':
case 'paper,rock':
console.log('A wins')
break
case 'rock,paper':
case 'paper,scissors':
case 'scissors,rock':
console.log('B wins')
break
default:
assertUnreachable(pair)
// ~~~~ Argument of type "scissors,paper" is not
// assignable to parameter of type 'never'.
}
}