TypeScript 当枚举有>1个成员时,仅针对枚举进行穷举性检查,

7cwmlq89  于 9个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(143)

TypeScript版本: typescript@2.9.0-dev.20180420
搜索词: 区分,穷举,类型保护,缩小
代码

  1. // Legal action types for ValidAction
  2. enum ActionTypes {
  3. INCREMENT = 'INCREMENT',
  4. // DECREMENT = 'DECREMENT',
  5. }
  6. interface IIncrement {
  7. payload: {};
  8. type: ActionTypes.INCREMENT;
  9. }
  10. // interface IDecrement {
  11. // payload: {};
  12. // type: ActionTypes.DECREMENT;
  13. // }
  14. // Any string not present in T
  15. type AnyStringExcept<T extends string> = { [P in T]: never; };
  16. // ValidAction is an interface with a type in ActionTypes
  17. type ValidAction = IIncrement;
  18. // type ValidAction = IIncrement | IDecrement;
  19. // UnhandledAction in an interface with a type that is not within ActionTypes
  20. type UnhandledAction = { type: AnyStringExcept<ActionTypes>; };
  21. // The set of all actions
  22. type PossibleAction = ValidAction | UnhandledAction;
  23. // Discriminates to ValidAction
  24. function isUnhandled(x: PossibleAction): x is UnhandledAction {
  25. return !(x.type in ActionTypes);
  26. }
  27. type CounterState = number;
  28. const initialState: CounterState = 0;
  29. function receiveAction(state = initialState, action: PossibleAction) {
  30. // typeof action === PossibleAction
  31. if (isUnhandled(action)) {
  32. // typeof action === UnhandledAction
  33. return state;
  34. }
  35. // typeof action === ValidAction
  36. switch (action.type) {
  37. case ActionTypes.INCREMENT:
  38. // typeof action === IIncrement
  39. return state + 1;
  40. // case ActionTypes.DECREMENT:
  41. // return state - 1;
  42. }
  43. // typeof action === IIncrement
  44. // Since INCREMENT is handled above, this should be impossible,
  45. // However the compiler will say that assertNever cannot receive an argument of type IIncrement
  46. return assertNever(action);
  47. }
  48. function assertNever(x: UnhandledAction): never {
  49. throw new Error(`Unhandled action type: ${x.type}`);
  50. }

预期行为: 不会抛出错误,因为switch语句是穷举的。如果取消注解ActionTypes.DECREMENT部分(导致ActionTypes有两个可能的值),则不会出现错误。只有在ActionTypes只有一个值时才会出现错误。即使在默认情况下发生neverAssert,错误仍然会发生,这显然是从IIncrement不可达的。
实际行为: 尽管只有可能的值被显式处理,但仍然抛出错误。如果取消注解ActionTypes.DECREMENT,预期行为将出现。
Playground链接: (修复了链接)

错误
工作

相关问题:

#19904
#14210
#18056

jvidinwx

jvidinwx1#

这也是同样的情况,当你尝试为未来的联合类型准备代码,但目前只使用一种类型。
如果你取消注解所有与第二个接口(前3个注解块)相关的内容,一切都将按预期工作。

daolsyd0

daolsyd02#

是的,这确实只是归结为"当对大小大于1的工会进行区分时,穷尽性检查才能起作用"。

o3imoua4

o3imoua43#

我刚刚也遇到了这个bug。这里有一个简单的例子:

  1. function assertNever(x: never): never {
  2. throw new Error("Unexpected object: " + x);
  3. }
  4. interface Square {
  5. kind: "square";
  6. size: number;
  7. }
  8. type Shape = Square
  9. function area(s: Shape) {
  10. switch (s.kind) {
  11. case "square": return s.size * s.size;
  12. default:
  13. assertNever(s);
  14. }
  15. }
展开查看全部
xtfmy6hx

xtfmy6hx4#

我们今天也遇到了这个问题。这里有另一个代码示例,如果它能帮助的话

  1. const rejectUnexpectedValueOfPropertyInObject = (
  2. objectName: string,
  3. propertyName: string,
  4. object: never
  5. ): never => {
  6. throw new Error(
  7. `Unexpected value for ${propertyName} in ${objectName}: ${
  8. object && typeof object === "object"
  9. ? (object as any)[propertyName]
  10. : "<not an object>"
  11. }`
  12. );
  13. };
  14. type Result =
  15. | {
  16. outcome: "success";
  17. }
  18. // | {
  19. // outcome: "error";
  20. // reason: "reason_2";
  21. // }
  22. | {
  23. outcome: "error";
  24. reason: "reason_1";
  25. };
  26. const f = (a: number, b: number): Result => {
  27. if (a < 0) {
  28. return {
  29. outcome: "error",
  30. reason: "reason_1"
  31. };
  32. }
  33. return { outcome: "success" };
  34. };
  35. const run = (a: number, b: number) => {
  36. const result = f(1, 2);
  37. if (result.outcome === "error") {
  38. switch (result.reason) {
  39. case "reason_1":
  40. console.log("error reason 1");
  41. break;
  42. // case "reason_2":
  43. // console.log("error reason 2");
  44. // break;
  45. default:
  46. rejectUnexpectedValueOfPropertyInObject("result", "reason", result);
  47. }
  48. }
  49. };

错误是

  1. const result: {
  2. outcome: "error";
  3. reason: "reason_1";
  4. }
  5. Argument of type '{ outcome: "error"; reason: "reason_1"; }' is not assignable to parameter of type 'never'.

通过移除类型定义和switch中的reason_2的注解,错误消失了。

展开查看全部
moiiocjp

moiiocjp5#

今天在尝试为扩展结构化一些代码时遇到了这个问题:
https://www.typescriptlang.org/play/?ssl=22&ssc=2&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgJIgM7TAFQgDzGQG8AoZC5MATwAcIAuZAclCyjAH1JDmBucpVpwwACyYgArgFsARtAGUqBMEwxgooAOYCAvqVI16yAPL0oI4AHsQyALxpM2PIQGkYkkAjDXbETJJQEACqIEGIonCyADYQABQAbnDRkozIIBAJ0AA0yNIQGBhwWhAA-Goa2gCUEpnQJIJiUFYA7ukQbQCiUM1QcfmFxSgAPsPIAAah4QiRMSgIVgAmKKLARC1roshJKSgAJMQAypUgWonJqVW641V6Bh5ePjbIcLS00dRmcVa0TGbQlhsNW2VmAiwaSgwGzAM2Q31oADojBAqiRkIIlBQEHAsCw2NhuCpmEwyJiycgFpgrLEEdErGcfkiVLcMeSKEEwIEQIpyfo2ct4JJoqpWWz-BhAiEwhAIlFYvCWUp9LogA

相关问题