Как расширить анонимный тип в Typescript

При объединении функций в typescript с анонимными типами, например, так:

  let array = [{ seed: 2 }, { seed: 3 }];
  array
    .map(i => ({ seed: i.seed, square: i.seed * i.seed }))
    .forEach(i => console.log(`square for ${i.seed} is ${i.square}`));

Мне нужно определить новый анонимный тип для функции карты. Если бы у меня было несколько шагов, каждый из которых создавал бы новые свойства, я бы в конечном итоге написал много кода определения, чтобы перенести все свойства. Я мог бы использовать $.extend (или Object.assign), но таким образом я потеряю интеллигентность и строгую типизацию.

  array
    .map(i => $.extend(i, { square: i.seed * i.seed }))
    .forEach(i => console.log(`square for ${i.seed} is ${i.square}`));

Как я могу расширить анонимный объект без повторного определения всех свойств, сохраняя при этом строгую типизацию?


person Jukka Varis    schedule 12.04.2016    source источник


Ответы (2)


Я наконец нашел решение. Этого можно добиться с помощью типов пересечений. Их можно использовать с анонимными типами и классами. В приведенном ниже примере функция расширения скопирует свойства каждого из объектов и вернет объект с типом пересечения. Это уменьшит значительный объем кода определения типа без потери интеллекта и строгой типизации.

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

let array = [{ seed: 2 }, { seed: 3 }];

array
    .map(i => extend(i, { square: i.seed * i.seed }))
    .map(i => extend(i, { cube: i.square * i.seed }))
    .forEach(i => console.log(`square for ${i.seed} is ${i.square} and cube is ${i.cube}`));

То же самое в Игровая площадка

Это реализовано напр. в core-js и его определения типов возвращают тип пересечения:

assign<T, U>(target: T, source: U): T & U;
person Jukka Varis    schedule 06.10.2016
comment
Не могли бы вы поделиться примером того, как импортировать и использовать реализацию core-js? Это поможет таким людям, как я, которые не хотят вводить собственное определение функции extend() в свой проект. Спасибо! - person Mike; 24.07.2019

Как насчет:

interface A {
    seed: number;
}

interface B extends A {
    square: number;
}

let array: A[] = [{ seed: 2 }, { seed: 3 }];
array
    .map<B>(a => { 
        return { seed: a.seed, square: a.seed * a.seed } 
    })
    .forEach(b => console.log("square for ${b.seed} is ${b.square}"));

или (если вы хотите сохранить анонимность):

let array = [{ seed: 2 }, { seed: 3 }];
array
    .map<{seed: number, square: number}>(a => {
        return { seed: a.seed, square: a.seed * a.seed }
    })
    .forEach(b => console.log("square for ${b.seed} is ${b.square}"));

(используйте его в детская площадка)

person Nitzan Tomer    schedule 12.04.2016
comment
Вы все еще снова определяете seed в возвращаемом значении. Как насчет того, если есть 5 параметров, а карта добавит 1 параметр. Какой бы это был беспорядок. - person Jukka Varis; 12.04.2016
comment
@JukkaVaris, поэтому используйте первое решение с интерфейсами. Нет никакого способа (насколько мне известно) расширить его иначе - person Nitzan Tomer; 12.04.2016
comment
Я боюсь этого. Мой фактический вариант использования — с RxJS, и цепочки могут быть довольно длинными. Интерфейсный граф легко вырастет из-под контроля. Я использую анонимные типы, чтобы уменьшить объем кода, который мне нужно написать, и я хотел бы, чтобы компилятор мог выводить типы из использования. - person Jukka Varis; 12.04.2016
comment
@JukkaVaris Весь смысл машинописного текста состоит в том, чтобы объявлять типы, если вы не хотите делать это суетой, просто используйте javascript. Да, определение всех ваших типов с помощью интерфейсов является более подробным, но тогда вы получаете преимущества. - person Nitzan Tomer; 12.04.2016
comment
Если вы используете последний машинописный текст, 1.8 на данный момент, вам не нужно явно объявлять все типы. Существует концепция, называемая контекстной типизацией, которая выводит типы для параметров функции. Поэтому ваш второй пример имеет много накладных расходов по сравнению с моим исходным примером. - person Jukka Varis; 13.04.2016
comment
@JukkaVaris Ну тогда в чем проблема? если ваш исходный пример хорошо работает для вас, то что именно вы спрашиваете? - person Nitzan Tomer; 13.04.2016
comment
Мой пример работает за счет строгой типизации, но он вводит больше кода, чем мне хотелось бы написать. Упрощенно иметь только 2 свойства, но в реальном мире обычно больше параметров. [{a:1, b:2, c:3, d:4}].map(data => ({a:data.a, b: data.b, c: data.c, d: data.d, result: calculate(data) }).map(data => ({a: data.a, b: data.b, c: data.c: d: data.d, result: data.result, checksum: calculateChecksum(data)}). Вы видите здесь проблему? Было бы проще просто как-то расширить анонимный тип. Сколько изменений потребуется, чтобы добавить еще одно свойство в начальные данные? - person Jukka Varis; 13.04.2016
comment
Ну, я думаю, ты просто думаешь об этом неправильно. Почему вы хотите, чтобы каждый шаг расширял предыдущий? почему бы просто не инкапсулировать это? Это решит вашу проблему, и, на мой взгляд, это лучший и более надежный дизайн. - person Nitzan Tomer; 13.04.2016