使用动态字符串为允许的枚举值键入保护?

Type guard for allowed enum value with dynamic string?

我正在处理来自第 3 方的一些数据,我想通过 ID 将其转换为地图,但前提是数据有效。我有一个允许属性的枚举,但无法弄清楚如何以编译器允许的方式检查数据是否有效。我试图检查在枚举上使用带有 in 运算符的 if 语句:

/** Allowed action values */
enum Actions {
    Update = 'Update',
    Delete = 'Delete'
}

/** Validated app events */
type AppEvent = {
    id: string;
    action: Actions;
}

/** 3rd party data I want to validate */
type RawEvent = {
    id: string;
    // ❗️I want to make sure this string is an allowed enum
    action: string
}

type AppEventsById = {
    [id: string]: AppEvent
}

const possiblyInvalidEvents: RawEvent[] = [
    {
        id: 'typescript',
        action: 'Update'
    },
    {
        id: 'uh oh',
        action: 'oops'
    }
]

const eventsById: AppEventsById = {}

possiblyInvalidEvents.forEach(event => {
    // ❓Here I'm attempting to check that 3rd party action field is one of the allowed enums
    if (event.action in Actions) {
        eventsById[event.id] = {
            id: event.id,
            // Type 'string' is not assignable to type 'Actions'
            action: event.action
        }
    }
})
// => I want eventsById to include the { id: 'typescript' } object, but not { id: 'uh oh' }

尝试分配给 action 引发此错误:Type 'string' is not assignable to type 'Actions'.

您只需要一个断言某个值 is Actions 的优化函数。任何基于此 return 值分支的代码都会记住您的值确实是您坚持的类型。因为你知道它是。

function isAction(input: string): input is Actions {
  return input in Actions
}

要使用它,只需调用函数并在 return 为真时进行分支:

let someAction: Actions = Actions.Delete

const actionName: string = 'whatever'
if (isAction(actionName)) {
  // Legal here, since we have verified the type.
  someAction = actionName
}

Playground link

您需要一个 user-defined type guard 函数来检查 string 是否是一个有效的 Actions 成员。这是一种明确告诉编译器应该使用某些 boolean 值表达式来缩小值类型的方法,如果它变成 true。最简单的代码重构是这样的:

function isValidAction(str: string): str is Actions {
    return str in Actions;
}

possiblyInvalidEvents.forEach(event => {
    if (isValidAction(event.action)) {
        eventsById[event.id] = {
            id: event.id,
            action: event.action // no error anymore
        }
    }
})

str in Actions 检查确实依赖于枚举的键和值相同的事实,这可能并不总是正确的。我可能会更愿意检查枚举的实际 values 而不是键,写出来有点令人讨厌,但至少不太可能突然中断:

function isValidAction(str: string): str is Actions {
    return (Object.keys(Actions) as Array<keyof typeof Actions>).
        some(k => Actions[k] === str);
}

但这取决于你。好的,希望有所帮助;祝你好运!

Playground link to code