All streams
Search
Write a publication
Pull to refresh

Comments 2

Вариант 4 - дублирование описаний и бойлерплейт, да и условным джунам он вряд ли покажется проще. Вариант 2 бесполезен, фактически там {type: SegmentType; coords: SegmentCoords} и непонятно, что в coords, мы не сможем проверить по ключу type.

Третий в принципе жизнеспособен, только для него нужен специальный тайпгард:

// тайпгард
const isType = <T extends SegmentType>(x: PathSegment3, type: T): x is PathSegment3<T> => x.type === type;

function func(x: PathSegment3) {
    if (x.type === 'line') {
        x.coords.length;  // 2 | 4, не сработало
    }

    if (isType(x, 'line')) {
        x.coords.length; // 4, всё окей
    }
}

Но его можно совсем чуть-чуть поменять и сделать параметрической разновидностью первого, с возможностью указывать некоторое подмножество SegmentType. Тогда isType из примера выше не понадобится, всё будет работать и так.

type PathSegment3a<T extends SegmentType = SegmentType> = T extends unknown ? {
  type: T;
  coords: SegmentCoords<T>;
} : never;

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

Паттерн - это описание задачи и описание способа ее решения с указнием плюсов и минусов.

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

И вот вы показываете как пользоваться выбранным вами решением, для решения Разных задач. Это не четыре варианта типизации одной сущности. Это типизация разных сущностей, потому что вы уже в изначальном вашем решении точно указали связь между SegmentType и разными форматами coords. Вы это уже сделали в выражении SegmentCoords.

С точки зрения менеджмента, паттерн снижает техдолг в долгосрочных проектах, минимизируя ошибки в типах, но в командах с джунами может вызвать лишнюю возню, приводя к фрустрации и временным решениям, например, использованию any для быстрого исправления.

Какую возню? Причет тут фрустрация? В каком месте использования any? Почему? О чем тут вообще идет речь?


Вариант 2: Record Utility Type

type PathSegmentRecord = Record<SegmentType, {  // Ключи — 'line' | 'quadratic'
  type: SegmentType;  // Union для type, без строгого сужения внутри Record
  coords: SegmentCoords<SegmentType>;  // Union coords, менее строгий
}>;

type PathSegment2 = PathSegmentRecord[keyof PathSegmentRecord]; 

Тут вопрос сразу напрашивается, а это зачем? Как этим пользоваться? Если это у вас общий тип, то зачем тут Record? Какая задача решается, с помощью данного типа?

Если вам для вашей задачи нужен только PathSegment, то промежуточный тип Record вам не нужен и нужно сразу писать:

type PathSegment2 = {
    type: SegmentType
    coords: SegmentCoords<SegmentType>
}

А если вам все таки нужен такой Record, тогда:

type PathSegmentRecord = Record<SegmentType, PathSegment2>;


Вариант 3: Generic Type with Default

Типобезопасность высока благодаря generics, обеспечивающим сужение типа, но требует явного указания T.

Что это значит?

Вы только что писали:

Типобезопасность ниже, чем в mapped types, из-за union в coords внутри Record, что снижает строгость сужения типа без дополнительного кода.

Имеем:

type PathSermentFrom5 = PathSegment5

что эквивалентно

type PathSermentFrom5 =  {  
  type: SegmentType
  coords: SegmentCoords<SegmentType>

}

Т.е. это ровно тоже самое, что было только что. О чем вообще весь этот абзац?

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

Вариант 4: Interface Inheritance

Тут все тоже самое. Какую задачу вы решаете? Если вы хотите настрогать ясных сегментов, то почему у вас BaseSegment не имеет coords свойства? Далее, если у вас PathSegment4 является главным поставщиком информации о форме, то зачем вам вообще дублирование в SegmentType?

interface BaseSegment {
  type: string;  // Union discriminant
  coords: number[]
}
// Line: extends с override type и specific coords
interface LineSegment extends BaseSegment {
  type: 'line';  // Литерал для сужения типа
  coords: [x: number, y: number];  // Две координаты
}
// Quadratic: аналогично
interface QuadraticSegment extends BaseSegment {
  type: 'quadratic';  // Литерал
  coords: [controlX: number, controlY: number, x: number, y: number];  // Четыре
}
type PathSegment4 = LineSegment | QuadraticSegment;  // Явный union

type SegmentType = PathSegment4['type']

Для менеджмента это минимизирует техдолг, так как джуны комфортно работают с интерфейсами из-за того, что это еще более базовая база, чем generics, но при росте команды добавление новых интерфейсов может привести к дублированию, конфликтам при слиянии.

А-а-а-а, я ничего не понял. Базовая база, какое-то дублирование, какие-то конфликты...

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

Sign up to leave a comment.

Articles