Расширение различимого объединения простых объектов, определенных в библиотечном пакете, в коде с использованием этой библиотеки только для чтения - PullRequest
1 голос
/ 12 мая 2019

У меня есть библиотека, которая записывает простые объекты JS в хранилище. Все эти объекты имеют типы. Я использую дискриминационный союз с общим свойством "type".

Проблема в том, что код, использующий эту библиотеку (тогда в node_modules/), не может обновить объединение.

Я только что переключил проект с Flow на TypeScript. В Flow я «решил» это, предоставив глобальный тип в flow-typed/. У меня был пустой глобальный тип в каталоге flow-typed/ кода библиотеки, но он перезаписывается (фактически, весь файл игнорируется) приложениями, имеющими глобальные определения flow-typed/.

Однако в TypeScript я не думаю, что у меня может быть как (пустой) локальный тип, так и включаемый в определение типа объединения, который позже будет перезаписан приложением?

Я прочитал довольно много похожих вопросов, но они всегда были немного нерешительными, и предложенные решения не сработали, или, может быть, я просто не понимаю, как переписать их для моего случая. Одно из отличий состоит в том, что мой собственный вариант использования кажется еще более простым: никаких классов, ничего сложного, просто набор простых объектов JavaScript .

Вот ссылка на игровую площадку с примером подхода (который терпит неудачу).

Вот код, немного отличающийся от примера, не показывающий предпринятый подход, а только то, что есть:

// ============================================================
// LIBRARY
// ============================================================

// Core (plain) object types defined within the library itself 
interface O1 {
    type: 'O1';
    a: string;
}
interface O2 {
    type: 'O2';
    b: number;
}

type oUnion = O1 | O2;

// Let's have a function to test the union on
declare function libraryFn<T extends oUnion>(p: T): T;

// Works (good)
const { b } = libraryFn({ type: 'O', b: 42 });
// Doesn't work (good)
libraryFn({ type: 'noSuchType' });

// ============================================================
// CODE THAT USES LIBRARY
// ============================================================

// HOW DO I ADD THIS TO THE UNION?
interface MyType {
    type: 'myO1';
    someProp: string;
}

const o: MyType = { type: 'myO1', someProp: 'a word' };
const {someProp} = libraryFn(o);

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

Конечно, я мог бы довольно легко заставить его работать, включив в него члена профсоюза общего характера, но это сделало бы весь дискриминационный союз бесполезным. Прямо сейчас, когда функция, принимающая объединение, получает один конкретный член объединения, ее точный тип члена отражается в возвращаемом типе. Поэтому, если элемент имеет тип "Person" со свойством email, тогда средство проверки типов знает, что возвращенный объект будет иметь свойство email, поскольку оно может решить, какой именно член объединения используется здесь.

Я экспериментировал с множеством подходов, и все, что мне удалось достичь, - это постоянно увеличивающаяся сложность, но без решения. Хакерский подход, который я использовал с Flow, перезаписывая глобально определенную переменную типа в приложении, кажется наименее дурацким, но а) я не знаю, как сделать то же самое в TS, б) я не знаю, есть ли лучше?

1 Ответ

2 голосов
/ 12 мая 2019

Если вы хотите изменить определения типов библиотеки (либо вносить такие изменения в апстрим, либо, что более вероятно, используя локально измененную версию определений библиотеки), вы можете сделать тип oUnion настраиваемым пользователями библиотека (при условии, что библиотека во время выполнения на самом деле может обрабатывать произвольные значения, а не только те типы, о которых знает библиотека).

Часто вы можете использовать объявление слияния для настройки типов, предоставляемых библиотекой. Но объединение объявлений может только расширять интерфейсы, то есть оно может добавлять новые свойства / методы и делать эти типы более специфичными путем сужения их. То, что вы пытаетесь сделать, это взять тип oUnion и сделать его более общим путем расширения . Это не поддерживается напрямую слиянием объявлений. Кроме того, oUnion - это псевдоним типа, который также нельзя изменить при объединении объявлений, даже если вы хотите сузить его.

К счастью, вы можете изменить код для предоставления интерфейса, из которого получен oUnion, и вы можете объединить новые свойства с этим интерфейсом, что приведет к расширению oUnion так, как вы ожидаете. Вот пример.

Код библиотеки будет выглядеть так:

module Library {
    // Core (plain) object types defined within the library itself 
    export interface O1 {
        type: 'O1';
        a: string;
    }
    export interface O2 {
        type: 'O2';
        b: number;
    }
    export interface O3 {
        type: 'O3';
        a: number;
    }
    // here's the interface you should merge stuff into
    export interface SupportedInterfaces {
        O1: O1,
        O2: O2,
        O3: O3
    }

    export type oUnion = SupportedInterfaces[keyof SupportedInterfaces];

    // Let's have a function to test the union on
    export declare function libraryFn<T extends oUnion>(p: T): T;

}

И тогда пользователи библиотеки объединятся в объявление SupportedInterfaces:

// Works (good)
const { a } = Library.libraryFn({ type: 'O3', a: 42 });
// Doesn't work (good)
Library.libraryFn({ type: 'noSuchType' });

// use declaration merging:
module Library {
    export interface SupportedInterfaces {
        OMyType: { type: 'myType', cc: 'cc' };
    }
}

Library.libraryFn({ type: 'myType', cc: "cc" }); // okay
Library.libraryFn({ type: 'myType', cc: "nope" }); // error, incompatible

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

Надеюсь, это даст вам возможный путь вперед. Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...