【问题标题】:Transform One Array Class into Another in Angular在 Angular 中将一个数组类转换为另一个数组类
【发布时间】:2020-06-05 06:08:20
【问题描述】:

如何使用以下两个类将数组列表从源映射到目标? 需要 DTO 映射到查找数组。一直在测试地图功能,还是不行。另外,我们应该将 mapFromSourceAddressDto 方法设为单独的 Export 函数,还是在 Lookup 类本身内部,这样会更容易吗?

后端 DTO API 模型可能会更改,因此尝试创建一种类型安全的方式来映射子集列,并在 DTO 列名称更改时收到通知。

export class SourceOfAddressDto {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;
    createDate: date;
    userId: string;
}

export class SourceOfAddressLookup {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;       
}

export function mapFromSourceOfAddressDto(sourceOfAddressDto: SourceOfAddressDto){
    const sourceOfAddressLookup = new SourceOfAddressLookup();
    sourceOfAddressLookup .sourceOfAddressId = sourceOfAddressDto.sourceOfAddressId
    sourceOfAddressLookup .sourceOfAddressCode = sourceOfAddressDto.sourceOfAddressCode;
    sourceOfAddressLookup .sourceOfAddressDescription = sourceOfAddressDto.sourceOfAddressDescription
    sourceOfAddressLookup .sourceOfAddressLabel = sourceOfAddressDto.sourceOfAddressLabel;

    return sourceOfAddressLookup ;
}

目标: Take an Array<SourceofAddressDto> ---> Array<SourceOfAddressLookup>

尝试的解决方案:

寻找更清洁的方式,

public addressSourceList: Array<SourceOfAddressLookup>; 

if (addressSourceListDto.length > 0){
    for (let i = 0; i < addressSourceListDto.length; ++i ) {
      this.addressSourceList[i] = mapFromSourceOfAddressDto(addressSourceListDto[i])
    }
  }

【问题讨论】:

  • 恐怕我不明白这个问题。您能否将代码编辑为minimal reproducible example,它清楚地显示您正在尝试做什么以及它在哪里不起作用?祝你好运!
  • 你是怎么调用mapFromSourceOfAddressDto函数的?
  • 查看以上@jcalz 尝试的解决方案
  • 查看@HereticMonkey 上面的尝试解决方案可能会有所帮助
  • 您需要类而不是接口是否有特殊原因?类应该初始化它们的属性,所以createDate: date; userId: string; 是错误的。而date 不是已知类型;你的意思是Date

标签: javascript angular typescript


【解决方案1】:

我认为您可能使事情变得比需要的更复杂。

在这种情况下确保类型安全的更有效方法是首先定义两个 typescript 接口。

一个用于您的 DTO 数据结构(我认为它来自 api 请求)。另一个用于您的“目标”对象结构。

interface SourceOfAddressDto {
  sourceOfAddressId?: number;
  sourceOfAddressCode?: string;
  sourceOfAddressDescription?: string;
  sourceOfAddressLabel?: string;
  createDate: string;
  userId: string;
}

interface SourceOfAddress {
  sourceOfAddressId?: number;
  sourceOfAddressCode?: string;
  sourceOfAddressDescription?: string;
  sourceOfAddressLabel?: string;
}

您可以单独定义您的地图函数并指定返回类型

const mapItems = (item:SourceOfAddressDto):SourceOfAddress[] => {
  return {
    sourceOfAddressId: item.sourceOfAddressId;
    sourceOfAddressCode: item.sourceOfAddressCode;
    sourceOfAddressDescription: item.sourceOfAddressDescription;
    sourceOfAddressLabel: item.sourceOfAddressLabel;
  }
};

当您检索异步数据时,您可以直接对其进行映射:

const data = await fetch("http://api") as SourceOfAddressDto[];
const mappedData = data.map(mapItems);

【讨论】:

  • 这很好,但是,现在 mapItems 返回数组,如果我想在 1 的情况下进行 1,那么函数中是否具有可重用性?
  • 另外,听说我不能创建新的接口实例,但是我可以创建类
  • 我认为您不需要创建类实例,因为您正在使用非常基本的对象结构。在这种情况下,可以使用对象字面量数组。
【解决方案2】:

新答案:

好的,我换个方式:你的 DTO 是一个接口,对应于你从 API 中获得的普通旧 JavaScript 对象:

interface SourceOfAddressDto {
    sourceOfAddressId?: number | undefined;
    sourceOfAddressCode?: string | undefined;
    sourceOfAddressDescription?: string | undefined;
    sourceOfAddressLabel?: string | undefined;
    createDate: Date;
    userId: string;
}

您的 Lookup 是一个真正的类,其中包含您需要使用的方法,例如 shout() ,我将作为示例进行编写:

class SourceOfAddressLookup {
    sourceOfAddressId?: number | undefined;
    sourceOfAddressCode?: string | undefined;
    sourceOfAddressDescription?: string | undefined;
    sourceOfAddressLabel?: string | undefined;
    shout() {
        console.log("HELLO MY ID IS " + this.sourceOfAddressId +
            " AND MY CODE IS \"" + this.sourceOfAddressCode + "\"" +
            " AND MY DESCRIPTION IS \"" + this.sourceOfAddressDescription + "\"" +
            " AND MY LABEL IS \"" + this.sourceOfAddressLabel + "\"!");
    }
}

我将定义assignProps(),而不是之前的pluck(),它接受一个目标对象、一个源对象和一个从源复制到目标的属性键列表。它是通用的,所以如果由于某种原因源的属性不是目标的正确类型,编译器应该对你大喊:

function assignProps<T, K extends keyof T>(
    destination: T, 
    source: Pick<T, K>, 
    ...keys: K[]
): T {
    keys.forEach(k => destination[k] = source[k]);
    return destination;
}

所以现在,mapFromSourceOfAddressDto 接受 SourceOfAddressDto 并在新构造的 SourceOfAddressLookup 实例上调用 assignProps()

const mapFromSourceOfAddressDto = (dto: SourceOfAddressDto) => assignProps(
    new SourceOfAddressLookup(),
    dto,
    "sourceOfAddressId",
    "sourceOfAddressCode",
    "sourceOfAddressDescription",
    "sourceOfAddressLabel"
)

这编译没有错误,所以类型应该可以工作。然后你可以像这样轻松地进行数组映射:

class Foo {
    public addressSourceList: Array<SourceOfAddressLookup>;
    constructor(addressSourceListDto: Array<SourceOfAddressDto>) {
        this.addressSourceList = addressSourceListDto.map(mapFromSourceOfAddressDto);
    }
}

让我们通过使用SourceOfAddressDto 对象数组构造一些东西来测试它:

const foo = new Foo([{
    createDate: new Date(),
    userId: "abc",
    sourceOfAddressId: 0,
    sourceOfAddressDescription: "d",
    sourceOfAddressCode: "c",
    sourceOfAddressLabel: "l"
}, {
    createDate: new Date(),
    userId: "def",
    sourceOfAddressId: 1,
    sourceOfAddressDescription: "D",
    sourceOfAddressCode: "C",
    sourceOfAddressLabel: "L"
}]);

这应该由Foo 的构造函数映射到SourceOfAddressLookup 实例的数组,所以让我们通过为每个元素调用shout() 方法来测试它:

foo.addressSourceList.forEach(x => x.shout())
// HELLO MY ID IS 0 AND MY CODE IS "c" AND MY DESCRIPTION IS "d" AND MY LABEL IS "l"! 
// HELLO MY ID IS 1 AND MY CODE IS "C" AND MY DESCRIPTION IS "D" AND MY LABEL IS "L"!

好的,看起来不错。再次祝你好运!

Playground link to code


旧答案:

我假设您的 DTO 是一个带有方法的成熟类

class SourceOfAddressDto {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;
    createDate: Date;
    userId: string;
    constructor(createDate: Date, userId: string) {
        this.createDate = createDate; this.userId = userId;
    }
}

但是查找类型可以是一个只有一些相同属性的接口。如果你想让这个类型有一个名字,你可以这样做:

// if you must name this type, you can do this:
interface SourceOfAddressLookup extends Pick<SourceOfAddressDto,
    "sourceOfAddressCode" |
    "sourceOfAddressDescription" |
    "sourceOfAddressId" |
    "sourceOfAddressLabel"
    > { }

总的来说,我会使用pluck() 之类的函数来获取现有对象并通过复制属性列表来创建新对象:

function pluck<T, K extends keyof T>(t: T, ...k: K[]) {
    return k.reduce((a, k) => (a[k] = t[k], a), {} as Pick<T, K>)
}

然后你的mapFromSourceOfAddressDto 函数可以像这样使用它:

function mapFromSourceOfAddressDto(obj: SourceOfAddressDto): SourceOfAddressLookup {
    return pluck(
        obj,
        "sourceOfAddressId",
        "sourceOfAddressCode",
        "sourceOfAddressDescription",
        "sourceOfAddressLabel"
    );
}

我们可以确保它有效:

const dto = new SourceOfAddressDto(new Date(), "abc");
dto.sourceOfAddressCode = "cod";
dto.sourceOfAddressDescription = "descrip";
dto.sourceOfAddressLabel = "lab";
dto.sourceOfAddressId = 1;

const lookup = mapFromSourceOfAddressDto(dto);

console.log(JSON.stringify(lookup));

/* {
"sourceOfAddressId":1,
"sourceOfAddressCode":"cod",
"sourceOfAddressDescription":"descrip",
"sourceOfAddressLabel":"lab"
}
*/

我觉得不错。希望这有助于给你一些方向;祝你好运!

Playground link to code

【讨论】:

  • 酷,这很有帮助,我如何使用您的 mapFromSourceAddresss Dto 将一个数组转换为另一个数组?你为单件做的,谢谢
  • sourceOfAddressDtoArray.map(mapFromSourceOfAddressDto)?
  • DTO 来自 API,没有方法,我的查找值有方法连接,大写单词等
  • 随意写答案,只是在尝试,
  • 哦,所以我是倒着做的......所以你需要用来自对象的道具而不是相反来水合一个类
猜你喜欢
  • 1970-01-01
  • 2021-10-03
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 2022-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多