Я разрабатывал пакет и обнаружил, что хотел бы показать проблему (имеется в виду что-то вроде скриншота, только с другим сообщением)
когда одной из функций передается неправильное значение.
У меня есть код, который выглядит примерно так:
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 может быть любой строкой, которую пожелает разработчик.
Вам понадобится общий класс, но я не скажу, как это сделать, пока вы не получите минимальный воспроизводимый пример.
@jcalz Извините, теперь должно работать нормально.
Вам будет намного лучше, если вы будете использовать аргументы конструктора, чем использовать отдельный шаг init()
. Почему вы используете init
здесь? Какой смысл иметь Package()
, который может находиться в неинициализированном состоянии?
Так что эта ссылка на игровую площадку показывает варианты, как я их вижу. Первый вариант — использовать аргументы конструктора, а не init()
; это простой общий класс. Второй вариант — сделать init()
метод утверждения, который сужает тип экземпляра после его создания, но это неудобно в использовании (для этого требуется аннотация типа, как показано). Они полностью отвечают на ваш вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?
@jcalz Я, скорее всего, в конечном итоге использую конструктор, в противном случае на ваше усмотрение.
Чтобы компилятор мог отслеживать строковые литеральные типы , соответствующие действительным идентификаторам ребер, нам нужно сделать 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
Не могли бы вы предоставить автономный минимально воспроизводимый пример, который действительно демонстрирует вашу проблему при вставке в автономную IDE? Прямо сейчас, если я это сделаю, я получу несколько ошибок, не связанных с вашим вопросом.