Сравнение строк в машинописном тексте перед компиляцией

Я разрабатывал пакет и обнаружил, что хотел бы показать проблему (имеется в виду что-то вроде скриншота, только с другим сообщением)

Сравнение строк в машинописном тексте перед компиляцией

когда одной из функций передается неправильное значение.

У меня есть код, который выглядит примерно так:

interface IEdge {
    id: string
}

interface IModule {
    id: string
    data: {
        inputEdges: IEdge[]
    }
}
const sampleModule = {
    id: "sampleModule",
    data: {
        inputEdges: [
            {
                id: "sampleEdge"
            },
            {
                id: "anotherEdge"
            }
        ]
    }
}

class Package {
    module: IModule|null = null
    init(module: IModule) {
        this.module = module
    }
    recieveOnEdge(
        edgeId: string,
        callback: any
    ){
       console.info(edgeId, callback)
    }
}

const packageInstance = new Package()
packageInstance.init(sampleModule)
packageInstance.recieveOnEdge("sampleEdge", "doesNotMatter")

Я хочу показать проблему в редакторе, когда первый параметр .recieveOnEdge не является одним из идентификаторов свойства inputEdges на packageInstance.module, но я не знаю, как это сделать, так как ID может быть любой строкой, которую пожелает разработчик.

Не могли бы вы предоставить автономный минимально воспроизводимый пример, который действительно демонстрирует вашу проблему при вставке в автономную IDE? Прямо сейчас, если я это сделаю, я получу несколько ошибок, не связанных с вашим вопросом.

jcalz 27.11.2022 22:09

Вам понадобится общий класс, но я не скажу, как это сделать, пока вы не получите минимальный воспроизводимый пример.

Dimava 27.11.2022 22:13

@jcalz Извините, теперь должно работать нормально.

MalwareMoon 27.11.2022 22:26

Вам будет намного лучше, если вы будете использовать аргументы конструктора, чем использовать отдельный шаг init(). Почему вы используете init здесь? Какой смысл иметь Package(), который может находиться в неинициализированном состоянии?

jcalz 27.11.2022 22:27

Так что эта ссылка на игровую площадку показывает варианты, как я их вижу. Первый вариант — использовать аргументы конструктора, а не init(); это простой общий класс. Второй вариант — сделать init() метод утверждения, который сужает тип экземпляра после его создания, но это неудобно в использовании (для этого требуется аннотация типа, как показано). Они полностью отвечают на ваш вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?

jcalz 27.11.2022 22:33

@jcalz Я, скорее всего, в конечном итоге использую конструктор, в противном случае на ваше усмотрение.

MalwareMoon 27.11.2022 22:42
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
6
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы компилятор мог отслеживать строковые литеральные типы , соответствующие действительным идентификаторам ребер, нам нужно сделать Package универсальным в объединении этих типов, а также всех типов и интерфейсов, которые содержат такие идентификаторы также должны быть общими.

Что-то вроде этого:

interface IEdge<K extends string = string> {
    id: K
}

interface IModule<K extends string = string> {
    id: string
    data: {
        inputEdges: readonly IEdge<K>[]
    }
}

Здесь я добавил параметр универсального типа K к IEdge и IModule. Если вы их не укажете, они по умолчанию будут string похожи на вашу версию. Кроме того, когда вы создаете sampleModule, компилятор будет делать вывод только string для этих идентификаторов, если вы не дадите ему подсказку, что ему следует уделить больше внимания, например, с утверждением const:

const sampleModule = {
    id: "sampleModule",
    data: {
        inputEdges: [
            {
                id: "sampleEdge"
            },
            {
                id: "anotherEdge"
            }
        ]
    }
} as const;

Это as const заставляет компилятор вывести sampleModule как этот тип:

/* const sampleModule: {
    readonly id: "sampleModule";
    readonly data: {
        readonly inputEdges: readonly [{
            readonly id: "sampleEdge";
        }, {
            readonly id: "anotherEdge";
        }];
    };
} */
    

Итак, теперь компилятор точно знает, что "sampleEdge" и "anotherEdge" являются допустимыми идентификаторами ребер. Также обратите внимание, что inputEdges был выведен как тип массива только для чтения, который технически шире, чем массив для чтения и записи. Вот почему я расширил шрифт IModule<K> до readonly IEdge<K>[]. Это, вероятно, не будет иметь значения, если вам не нужно начинать изменять этот массив постфактум (но я надеюсь, что вы этого не сделаете, поскольку это может изменить допустимые идентификаторы ребер).


Чтобы упростить задачу, я заменю ваш метод init() аргументом конструктора. Таким образом, тип вашего экземпляра пакета может знать об идентификаторах краев с момента его создания. В противном случае нам пришлось бы пытаться сузить тип при вызове init(), что сложно сделать правильно. Технически вы можете сделать это, создав init()метод утверждения, но это не весело. Если кому-то не нужно, чтобы экземпляр пакета сидел без дела до его инициализации, нам следует выполнить инициализацию в конструкторе, что в любом случае более традиционно.

Вот:

class Package<K extends string> {
    constructor(public module: IModule<K>) { }
    recieveOnEdge(
        edgeId: K,
        callback: any
    ) {
        console.info(edgeId, callback)
    }
}

Итак, вы можете видеть, что receiveOnEdge() принимает только edgeId типа K. Давайте проверим это:

const packageInstance = new Package(sampleModule);
// const packageInstance: Package<"sampleEdge" | "anotherEdge">    

Таким образом, компилятор делает вывод, что packageInstance имеет тип Package<"sampleEdge" | "anotherEdge">, что приводит к желаемому поведению с receiveOnEdge():

packageInstance.recieveOnEdge("sampleEdge", "doesNotMatter"); // okay
packageInstance.recieveOnEdge("badEdge", "doesNotMatter"); // error

Площадка ссылка на код

Другие вопросы по теме