Typescript接口默认值

时间:2016-01-28 23:47:02

标签: typescript

我在TypeScript中有以下界面:

interface IX {
    a: string,
    b: any,
    c: AnotherType
}

我声明了该类型的变量,并初始化了所有属性

let x: IX = {
    a: 'abc',
    b: null,
    c: null
}

然后我在稍后的init函数中为它们分配实际值

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

但我不喜欢在声明对象时要为每个属性指定一堆默认空值,因为它们稍后会被设置为实际值。我可以告诉界面默认我不提供给null的属性吗?什么让我这样做:

let x: IX = {
    a: 'abc'
}

没有收到编译器错误。现在它告诉我

  

TS2322:类型“{}”不能分配给类型   “九”。类型“{}”中缺少属性“b”。

12 个答案:

答案 0 :(得分:62)

  

我可以告诉界面默认我不提供给null的属性吗?什么会让我这样做

没有。但默认情况下它们是undefined,这大部分都很好。您可以使用以下模式,即在创建时具有类型断言:

let x: IX = {} as any;

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

我在此处记录了这个和其他模式:https://basarat.gitbooks.io/typescript/content/docs/tips/lazyObjectLiteralInitialization.html

答案 1 :(得分:43)

您无法在界面中设置默认值,但可以使用“可选属性”(比较第3段)完成您要执行的操作:

https://www.typescriptlang.org/docs/handbook/interfaces.html

只需将界面更改为:

interface IX {
    a: string,
    b?: any,
    c?: AnotherType
}

然后你可以这样做:

let x: IX = {
    a: 'abc'
}

如果未设置这些属性,则使用init函数将默认值分配给x.bx.c

答案 2 :(得分:15)

您可以使用类实现接口,然后您可以在构造函数中处理初始化成员:

class IXClass implements IX {
    a: string;
    b: any;
    c: AnotherType;

    constructor(obj: IX);
    constructor(a: string, b: any, c: AnotherType);
    constructor() {
        if (arguments.length == 1) {
            this.a = arguments[0].a;
            this.b = arguments[0].b;
            this.c = arguments[0].c;
        } else {
            this.a = arguments[0];
            this.b = arguments[1];
            this.c = arguments[2];
        }
    }
}

另一种方法是使用工厂功能:

function ixFactory(a: string, b: any, c: AnotherType): IX {
    return {
        a: a,
        b: b,
        c: c
    }
}

然后你可以简单地说:

var ix: IX = null;
...

ix = new IXClass(...);
// or
ix = ixFactory(...);

答案 3 :(得分:4)

虽然@Timar的答案对于null默认值(要求的)非常有效,但是这里还有一个简单的解决方案,它允许其他默认值:定义一个选项接口以及一个包含默认值的常量。在构造函数中使用spread operator来设置options成员变量

interface IXOptions {
    a?: string,
    b?: any,
    c?: number
}

const XDefaults: IXOptions = {
    a: "default",
    b: null,
    c: 1
}

export class ClassX {
    private options: IXOptions;

    constructor(XOptions: IXOptions) {
        this.options = { ...XDefaults, ...XOptions };
    }

    public printOptions(): void {
        console.log(this.options.a);
        console.log(this.options.b);
        console.log(this.options.c);
    }
}

现在您可以使用这样的类:

const x = new ClassX({ a: "set" });
x.printOptions();

输出:

set
null
1

答案 4 :(得分:1)

工厂方法:

您可以为此使用工厂方法,该方法返回一个实现XI接口的对象。

示例:

class AnotherType {}

interface IX {
    a: string,
    b: any,
    c: AnotherType | null
}

function makeIX (): IX {
    return {
    a: 'abc',
    b: null,
    c: null
    }
}

const x = makeIX();

x.a = 'xyz';
x.b = 123;
x.c = new AnotherType();

我对您的示例所做的唯一更改是使属性c均为AnotherType | null。对于没有任何编译器错误,这将是必需的(在示例中,如果将null初始化为属性c,也会出现此错误)。

答案 5 :(得分:1)

我使用以下模式:

创建实用程序类型 O(1)

Defaults<T>

使用选项/默认值声明类:

type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
type Defaults<T> = Required<Pick<T, OptionalKeys<T>>>

创建类实例:

// options passed to class constructor
export interface Options {
    a: string,
    b?: any,
    c?: number
}

// defaults
const defaults: Defaults<Options> = {
    b: null,
    c: 1
};

export class MyClass {
    // all options in class must have values
    options: Required<Options>;

    constructor(options: Options) {
        // merge passed options and defaults
        this.options = Object.assign({}, defaults, options);
    }
}

答案 6 :(得分:0)

我偶然发现了这一点,同时寻找了一种比我所达到的更好的方法。阅读答案并进行尝试后,我认为值得一提我正在做的事情,因为其他答案对我而言并不那么简洁。对我而言,每次设置新接口时只需编写少量代码就很重要。我选择了...

使用自定义的通用deepCopy函数:

deepCopy = <T extends {}>(input: any): T => {
  return JSON.parse(JSON.stringify(input));
};

定义界面

interface IX {
    a: string;
    b: any;
    c: AnotherType;
}

...并在单独的const中定义默认值。

const XDef : IX = {
    a: '',
    b: null,
    c: null,
};

然后像这样初始化:

let x : IX = deepCopy(XDef);

仅此而已。.

....

如果您要自定义初始化任何根元素,则可以修改deepCopy函数以接受自定义默认值。该功能变为:

deepCopyAssign = <T extends {}>(input: any, rootOverwrites?: any): T => {
  return JSON.parse(JSON.stringify({ ...input, ...rootOverwrites }));
};

然后可以这样调用:

let x : IX = deepCopyAssign(XDef, { a:'customInitValue' } );

任何其他首选的深层复制方式都可以使用。如果仅需要浅表副本,则Object.assign就足够了,而无需使用实用程序deepCopydeepCopyAssign函数。

let x : IX = object.assign({}, XDef, { a:'customInitValue' });

已知问题

  • 不会以此幌子进行深层分配,但是很难 修改deepCopyAssign,以在分配之前迭代并检查类型。
  • 函数和引用将在解析/字符串化过程中丢失。 我不需要这些来完成任务,OP也不需要。
  • IDE不会提示自定义初始化值,也不会在执行时检查类型。

答案 7 :(得分:0)

您可以按照文档中的说明使用Partial映射类型: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

在您的示例中,您将拥有:

interface IX {
    a: string;
    b: any;
    c: AnotherType;
}

let x: Partial<IX> = {
    a: 'abc'
}

答案 8 :(得分:0)

我的解决方案:

我已经在Object.assign上创建了包装器,以解决输入问题。

export function assign<T>(...args: T[] | Partial<T>[]): T {
  return Object.assign.apply(Object, [{}, ...args]);
}

用法:

env.base.ts

export interface EnvironmentValues {
export interface EnvironmentValues {
  isBrowser: boolean;
  apiURL: string;
}

export const enviromentBaseValues: Partial<EnvironmentValues> = {
  isBrowser: typeof window !== 'undefined',
};

export default enviromentBaseValues;

env.dev.ts

import { EnvironmentValues, enviromentBaseValues } from './env.base';
import { assign } from '../utilities';

export const enviromentDevValues: EnvironmentValues = assign<EnvironmentValues>(
  {
    apiURL: '/api',
  },
  enviromentBaseValues
);

export default enviromentDevValues;

答案 9 :(得分:0)

您可以使用两个单独的配置。一个作为带有可选属性(将具有默认值)的输入,另一个作为仅带有必需属性的输入。使用&Required可以很方便:

interface DefaultedFuncConfig {
  b?: boolean;
}

interface MandatoryFuncConfig {
  a: boolean;
}

export type FuncConfig = MandatoryFuncConfig & DefaultedFuncConfig;
 
export const func = (config: FuncConfig): Required<FuncConfig> => ({
  b: true,
  ...config
});

// will compile
func({ a: true });
func({ a: true, b: true });

// will error
func({ b: true });
func({});

答案 10 :(得分:0)

取决于情况和用法。通常,在TypeScript中,没有接口的默认值。

如果不使用默认值
您可以将x声明为:

let x: IX | undefined; // declaration: x = undefined

然后,您可以在init函数中设置实数值:

x = {
    a: 'xyz'
    b: 123
    c: new AnotherType()
};

通过这种方式,x可以未定义或定义-undefined表示对象是未初始化的,无需设置默认值(如果不需要)。从逻辑上讲,这比定义“垃圾”要好。

如果要部分分配对象:
您可以使用可选属性来定义类型,例如:

interface IX {
    a: string,
    b?: any,
    c?: AnotherType
}

在这种情况下,您只需要设置a。其他类型标有?,表示它们是可选的,并且默认值为undefined

在任何情况下,您都可以使用undefined作为默认值,这仅取决于您的用例

答案 11 :(得分:-1)

如果您有很多参数,最好让用户只插入几个参数,而不是按特定顺序插入。

例如,不好的做法:

foo(a?, b=1, c=99, d=88, e?)
foo(null, null, null, 3)

因为您必须在实际需要的参数之前提供所有参数 (d)。

好的做法是:

foo({d=3})

实现方式是通过接口。 您需要将参数定义为如下接口:

interface Arguments {
    a?;
    b?; 
    c?;
    d?;
    e?;
}

并定义如下函数:

foo(arguments: Arguments)

现在接口变量不能获取默认值,那么我们如何定义默认值?

简单,我们为整个界面定义默认值:

foo({
        a,
        b=1,
        c=99,
        d=88,
        e                    
    }: Arguments)

现在如果用户通过:

foo({d=3})

实际参数为:

{
    a,
    b=1,
    c=99,
    d=3,
    e                    
}

另一个不声明接口的选项是:

foo({
        a=undefined,
        b=1,
        c=99,
        d=88,
        e=undefined                    
    })

我从以下链接中理解了这一点,功劳很大:) https://medium.com/better-programming/named-parameters-in-typescript-e32c763d2b2e