辨析联合类型

    做为一个例子,考虑 和 Rectangle 的联合类型 ShapeSquareRectangle有共同成员 kind,因此 kind 存在于 Shape 中。

    如果你使用类型保护风格的检查(=====!=!==)或者使用具有判断性的属性(在这里是 kind),TypeScript 将会认为你会使用的对象类型一定是拥有特殊字面量的,并且它会为你自动把类型范围变小:

    1. function area(s: Shape) {
    2. if (s.kind === 'square') {
    3. // 现在 TypeScript 知道 s 的类型是 Square
    4. // 所以你现在能安全使用它
    5. return s.size * s.size;
    6. } else {
    7. // 不是一个 square ?因此 TypeScript 将会推算出 s 一定是 Rectangle
    8. return s.width * s.height;
    9. }
    10. }

    通常,联合类型的成员有一些自己的行为(代码):

    1. interface Square {
    2. kind: 'square';
    3. size: number;
    4. }
    5. interface Rectangle {
    6. kind: 'rectangle';
    7. width: number;
    8. height: number;
    9. }
    10. // 有人仅仅是添加了 `Circle` 类型
    11. // 我们可能希望 TypeScript 能在任何被需要的地方抛出错误
    12. interface Circle {
    13. kind: 'circle';
    14. radius: number;
    15. }
    16. type Shape = Square | Rectangle | Circle;

    你可以通过一个简单的向下思想,来确保块中的类型被推断为与 never 类型兼容的类型。例如,你可以添加一个更详细的检查来捕获错误:

    1. function area(s: Shape) {
    2. return s.size * s.size;
    3. } else if (s.kind === 'rectangle') {
    4. return s.width * s.height;
    5. } else {
    6. const _exhaustiveCheck: never = s;
    7. }
    8. }

    它将强制你添加一种新的条件:

    1. function area(s: Shape) {
    2. if (s.kind === 'square') {
    3. return s.size * s.size;
    4. } else if (s.kind === 'rectangle') {
    5. return s.width * s.height;
    6. } else if (s.kind === 'circle') {
    7. return Math.PI * s.radius ** 2;
    8. } else {
    9. // ok
    10. const _exhaustiveCheck: never = s;
    11. }
    12. }

    TIP

    如果你使用 strictNullChecks 选项来做详细的检查,你应该返回 _exhaustiveCheck 变量(类型是 never),否则 TypeScript 可能会推断返回值为 undefined

    1. function area(s: Shape) {
    2. switch (s.kind) {
    3. case 'square':
    4. return s.size * s.size;
    5. case 'rectangle':
    6. return s.width * s.height;
    7. case 'circle':
    8. return Math.PI * s.radius ** 2;
    9. default:
    10. const _exhaustiveCheck: never = s;
    11. return _exhaustiveCheck;
    12. }
    13. }

    Redux 库正是使用的上述例子。

    以下是添加了 TypeScript 类型注解的redux 要点

    1. import { createStore } from 'redux';
    2. type Action =
    3. | {
    4. type: 'INCREMENT';
    5. | {
    6. type: 'DECREMENT';
    7. };
    8. /**
    9. * It describes how an action transforms the state into the next state.
    10. *
    11. * The shape of the state is up to you: it can be a primitive, an array, an object,
    12. * or even an Immutable.js data structure. The only important part is that you should
    13. * not mutate the state object, but return a new object if the state changes.
    14. *
    15. * In this example, we use a `switch` statement and strings, but you can use a helper that
    16. * follows a different convention (such as function maps) if it makes sense for your
    17. * project.
    18. */
    19. function counter(state = 0, action: Action) {
    20. switch (action.type) {
    21. case 'INCREMENT':
    22. return state + 1;
    23. case 'DECREMENT':
    24. return state - 1;
    25. default:
    26. return state;
    27. }
    28. }
    29. // Create a Redux store holding the state of your app.
    30. // Its API is { subscribe, dispatch, getState }.
    31. let store = createStore(counter);
    32. // You can use subscribe() to update the UI in response to state changes.
    33. // Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
    34. // However it can also be handy to persist the current state in the localStorage.
    35. store.subscribe(() => console.log(store.getState()));
    36. // The only way to mutate the internal state is to dispatch an action.
    37. // The actions can be serialized, logged or stored and later replayed.
    38. store.dispatch({ type: 'INCREMENT' });
    39. // 1
    40. store.dispatch({ type: 'INCREMENT' });
    41. // 2
    42. // 1