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()

但我不喜欢在声明对象时为每个属性指定一堆默认空值,因为它们稍后将被设置为实值。我能告诉接口默认属性我不提供为空吗?是什么让我这样做:

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

而不会产生编译器错误。现在它告诉我了

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

533030 次浏览

我能告诉接口默认属性我不提供为空吗?是什么让我这么做的

不。不能为接口或类型别名提供默认值,因为它们仅在编译时使用,默认值需要运行时支持

替代

但在JavaScript运行时,未指定的值默认为undefined。所以你可以把它们标记为可选:

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

现在当你创建它时,你只需要提供a:

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

你可以根据需要提供这些值:

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

你可以用一个类实现接口,然后你可以在构造函数中初始化成员:

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(...);

你不能在接口中设置默认值,但是你可以通过使用可选属性来完成你想做的事情:

简单地将界面更改为:

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

你可以这样做:

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

如果没有设置这些属性,则使用init函数为x.bx.c分配默认值。

虽然@Timar的答案适用于null默认值(要求的),但这里有另一个允许其他默认值的简单解决方案:定义一个选项接口以及一个包含默认值的根据常量;在构造函数中使用传播算子来设置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

这是我在寻找比我已经得到的更好的方法时偶然发现的。在阅读了答案并尝试了它们之后,我认为值得把我正在做的事情发布出来,因为其他答案对我来说感觉不那么简洁。对我来说,每次设置新界面时只需要编写少量代码是很重要的。我选定了……

使用自定义通用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,
};

然后像这样init:

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。赋值就足够了,不需要使用实用程序deepCopydeepCopyAssign函数。

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

已知的问题

    在这种伪装下,它不会深赋值,但并不太难 . modify deepCopyAssign在赋值前迭代并检查类型
  • 函数和引用将被parse/stringify进程丢失。 我的任务不需要这些,op也不需要
  • 自定义init值在执行时不会被IDE提示或类型检查。
你可以使用文档中解释的Partial映射类型: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html < / p >

在你的例子中,你会有:

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


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

我的解决方案:

我在对象上创建了包装器。指派以修复键入问题。

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;

您可以使用两个单独的配置。一个作为具有可选属性的输入(将具有默认值),另一个仅具有必需的属性。这可以通过&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({});

这要看情况和用法而定。通常,在TypeScript中,接口没有默认值。

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

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

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

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

这样,x可以是未定义的,也可以是已定义的——undefined表示对象是未初始化的,没有设置默认值(如果它们是不必要的)。这在逻辑上比定义“垃圾”要好。

< p > 如果你想要部分分配对象:
您可以使用以下可选属性定义类型:

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

在这种情况下,你必须只设置a。其他类型被标记为?,这意味着它们是可选的,并将undefined作为默认值。

甚至

let x: Partial<IX> = { ... }

这使得所有字段都是可选的。

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

接口的默认值是不可能的,因为接口只存在于编译时。

可选择的解决方案:

例子:

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 both AnotherType | null。这将是必要的,没有任何编译器错误(这个错误也出现在你的例子中,你初始化为null属性c)。

在有很多参数的情况下,最好让用户只插入几个参数,而且没有特定的顺序。

例如,不好的做法:

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
})

< >强跟进: 在前面的函数定义中,我们定义了参数对象的字段的默认值,但没有定义对象本身的默认值。 因此,我们将从以下调用得到一个提取错误(例如Cannot read property 'b' of undefined):

foo()

有两种可能的解决方案:

  1. const defaultObject = {a=undefined, b=1, c=99, d=88, e=undefined}
    function foo({a=defaultObject.a, b=defaultObject.b, c=defaultObject.c, d=defaultObject.d, e=defaultObject.e} = defaultObject)
    
  2. const defaultObject = {a=undefined, b=1, c=99, d=88, e=undefined}
    function foo(object) {
    const {a,b,c,d,e} = {
    ...defaultObject,
    ...object,
    }
    //Continue the function code..
    }
    

我使用下面的模式:

创建实用程序类型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);
}
}

创建类实例:

const myClass = new MyClass({
a: 'hello',
b: true,
});


console.log(myClass.options);
// { a: 'hello', b: true, c: 1 }

您还可以使用helper方法/函数返回具有默认属性值的对象,然后调用代码可以根据需要重写默认值。这就是我在当前项目中遇到同样问题时所遵循的方法。通过这种方式,对默认属性值对象进行编码是一次性的事情,您可以在整个应用程序中重用该对象。

另一种方法是使用https://www.npmjs.com/package/merge
这与上一个答案相同,但更简洁

让我们安装合并

yarn add -D merge
接下来让我们创建一个带有一些选项的接口。< br > 我们把它放到
/类型/ index.ts。< / >强

export interface ExampleOpts {
opt1: string,
opt2: string,
opt3: string,
}
接下来让我们创建一组默认值
你可以把它放在同一个文件中,但是让我们保持类型分离,并把它放在
/ config / index.ts。< / >强

import { ExampleOpts } from '../types'


// Defaults
export const ExampleOptsDefault : ExampleOpts = {
opt1: 'default value 1',
opt2: 'default value 2',
opt3: 'default value 3',
}
接下来让我们用
内的函数将它们连接在一起 。/ index.ts < / >强

import { ExampleOpts } from './types'
import { ExampleOptsDefault } from './config'
import merge from 'merge'


// The ? makes the parameter optional
export function test1(options?: ExampleOpts) {
// merge tries to load in the defaults first, then options next if it's defined
const merged_opts: ExampleOpts = merge.recursive(ExampleOptsDefault, options)
// log the result to the console
console.log(merged_opts)
}

另一种方法是使用Pick实用程序类型并选择您希望设置为必需的属性。

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


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

然后当你想要声明真正的IX对象时,你只需将默认值与新值合并,如下所示:

const newX: IX = {
...x,
b: 'b',
c: () => {}
}

这个答案是从“如何设置TypeScript接口的默认值?"

我们正在努力解决这个问题。使用class而不是interface

class IX {
a: String = '';
b?: any;
c: Cee = new Cee();
}


class Cee {
c: String = 'c';
e: String = 'e';
}

我需要这个React组件。

你可以使用空合并运算符,它将在左手值为Null或Undefined时分配一个默认值:

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


const ixFunction: React.FC<IX> = (props) => {
console.log(props.b?? "DefaultValue")
}

但这只适用于只在一个地方使用变量的情况。