Введите guard для допустимого значения enum со строкой Dynami c? - PullRequest
1 голос
/ 01 февраля 2020

Я работаю с некоторыми данными от третьих лиц, которые я хочу преобразовать в карту по идентификатору, но только если данные действительны. У меня есть список разрешенных свойств, но я не могу понять, как проверить правильность данных так, как это позволяет компилятор. Я попытался проверить с помощью оператора if с оператором in в перечислении:

/** 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'.

Ответы [ 2 ]

1 голос
/ 01 февраля 2020

Вы хотите, чтобы определяемая пользователем функция защиты типов проверяла, является ли 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 действительно основана на том факте, что ключи и значения enum идентичны, что не всегда может быть правдой. Возможно, мне было бы удобнее проверять фактические значения перечисления, а не ключи, что немного более неприятно для записи, но, по крайней мере, с меньшей вероятностью внезапно сломалось:

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

Но решать вам. Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

1 голос
/ 01 февраля 2020

Вам просто нужна уточняющая функция, которая утверждает, что какое-то значение is Actions. Любой код, который разветвляется на основе этого возвращаемого значения, будет помнить, что ваше значение действительно является тем типом, который вы настаивали. Потому что вы знаете, что это так.

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

Чтобы использовать его, просто вызовите функцию и ответвление, если оно вернет true:

let someAction: Actions = Actions.Delete

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

ссылка на игровую площадку

...