如何在ViewModel中遵循DRY原则?

How to follow the DRY principle in a ViewModel?

提问人:Rusurano 提问时间:7/26/2023 更新时间:7/28/2023 访问量:25

问:

在 MVVM 应用程序中,通常可以看到以下类型的 ViewModel:

import { nanoid } from 'nanoid';
import dayjs from 'dayjs';

export default function(entry) {
    return {
        id:                             entry.id,
        title:                          entry.title,
        slug:                           entry.slug,
        category:                       entry.category,
        summary:                        entry.summary,
        content:                        entry.body,
        type:                           entry.type,
        video:                          entry.video,
        duration:                       { 
                                            hours:      entry.duration === null ? null : Math.floor(entry.duration / 60),
                                            minutes:    entry.duration === null ? null : entry.duration % 60
                                        },
        createdAt:                      dayjs(entry.createdAt),
        updatedAt:                      dayjs(entry.updatedAt),
        isHidden:                       entry.isHidden,
        isDeleted:                      entry.isDeleted,
        notes:                          entry.notes.map(item => ({ key: nanoid(), value: item.body })),
        steps:                          entry.steps.map(item => ({ key: nanoid(), value: item.body, image: item.image })),
        keywords:                       entry.keywords.map(item => ({ key: nanoid(), value: item.body })),
        covers:                         entry.covers
    }
}

如果未提供给此函数,我想返回默认(回退)值。当然,也可以将它们列在一个单独的对象中,并在不同情况下返回不同的对象:entry

import { nanoid } from 'nanoid';
import dayjs from 'dayjs';

export default function(entry) {
    if(entry) {
        return {
            id:                             entry.id,
            type:                           0,
            title:                          entry.title,
            slug:                           entry.slug,
            category:                       entry.category,
            summary:                        entry.summary,
            content:                        entry.body,
            video:                          entry.video,
            duration:                       { 
                                                hours:      entry.duration === null ? null : Math.floor(entry.duration / 60),
                                                minutes:    entry.duration === null ? null : entry.duration % 60
                                            },
            createdAt:                      dayjs(entry.createdAt),
            updatedAt:                      dayjs(entry.updatedAt),
            isHidden:                       entry.isHidden,
            isDeleted:                      entry.isDeleted,
            notes:                          entry.notes.map(item => ({ key: nanoid(), value: item.body })),
            steps:                          entry.steps.map(item => ({ key: nanoid(), value: item.body, image: item.image })),
            keywords:                       entry.keywords.map(item => ({ key: nanoid(), value: item.body })),
            covers:                         entry.covers
        }
    }

    return {
        id:                             null,
        type:                           0,
        title:                          null,
        slug:                           null,
        category:                       null,
        summary:                        null,
        content:                        null,
        type:                           null,
        video:                          null,
        duration:                       { 
                                            hours:      null,
                                            minutes:    null
                                        },
        createdAt:                      dayjs(),
        updatedAt:                      dayjs(),
        isHidden:                       false,
        isDeleted:                      false,
        notes:                          [],
        steps:                          [],
        keywords:                       [],
        covers:                         []
    }
}

但是这样一来,我列出了两次所有键,这并不是真正的 DRY。

在这种情况下,保持 DRY 的清洁方法是什么?

javascript mvvm

评论


答:

1赞 kca 7/28/2023 #1

从某种意义上说,您的代码是 DRY,因为您不会重复 type 的相同值。{ id, type, title, ... }

键存在重复,但对象的值不同。

不同类型

如果函数的返回类型是 ,那么你可以这样写:string | null

if( typeof entry === 'string' ){
    return entry;
}

return null;

这是一个一致的返回类型 (),但该类型的 2 个不同值。这不是“重复”,即这是“DRY”。string | null

您的类型是 ,并且返回两个不同的值:{ id, type, title, ... }

{
  id:    1,
  type:  0,
  title: 'first value'
}

的值与以下值不同:

{
  id:    0,
  type:  0,
  title: null
}

因此,除了为该对象的每个单独属性设置正确的值外,别无他法。

但是,您可以做的是根据您必须定义的规则“生成”或“构造”这些单个属性的值。所以。。。

“细粒度”代码复制

如果对象变得如此之大,则避免代码重复是有意义的,这样,如有必要,不必在太多地方对对象结构进行更改。

但另一方面,以这种方式创建的对象可能会变得难以阅读,因此应仔细考虑这一点。

例如,以这种方式创建的对象可以消除很多重复,但很难理解:

{
  id: 1,
  ...staticProperties(),
  ...flags(),
  duration: createDuration().  
}

你必须找到一个好的折衷方案。

静态空对象

无论如何,我假设您可能还需要在其他地方创建一个空对象。 我建议在某个地方创建一个空对象或空对象构造函数,并在需要时使用它,例如:

const emptyObject = {
  id:                             null,
  type:                           0,
  title:                          null,
  // ...
  steps:                          [],
  keywords:                       [],
  covers:                         []
};

并像这样使用它:

if(entry) {
  return {
    id:     entry.id,
    type:   0,
    // ...
  };
}

return {
  ...emptyObject,
  createdAt: dayjs(),
  updatedAt: dayjs(),
};

// or:
return createEmptyObject();