ChatGPT解决这个技术问题 Extra ChatGPT

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:类型“{}”不可分配给类型“IX”。类型“{}”中缺少属性“b”。

IMO,答案stackoverflow.com/a/35074490/129196不应该是采取的方法。如果您可以在没有初始化其所有属性的情况下使对象处于某个状态并且仍然有效,那么您应该将这些属性声明为可选,如以下答案所示:stackoverflow.com/a/43226857/129196。否则我们将失去使用 typescript 的唯一目的(为了类型安全)。

b
basarat

我可以告诉接口将我不提供的属性默认为 null 吗?什么会让我这样做

不可以。您不能为接口或类型别名提供默认值,因为它们只是编译时的,默认值需要运行时支持

选择

但未指定的值在 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()

谢谢,没有定义一堆默认值,我曾经无法为未定义的属性设置值
使用 any 会破坏 TypeScript 的目的。还有其他答案没有这个缺点。
任何人都可以帮助我解决类似的问题,但使用泛型。 Here at this question
奇怪的是,当 basarat 在提供的链接中使用“让 foo = {} as Foo;”提供更好的选择时,basarat 会使用“任何”示例。 (“Foo”是一个接口)
OP 已经麻烦地创建了一个 TypeScript 接口,并正在寻求解决方案来解决问题。您的解决方案是完全放弃接口?不妨建议也跳过 TypeScript ......
T
Timar

您不能在界面中设置默认值,但您可以通过使用可选属性来完成您想做的事情(比较第 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 分配默认值。


在问题中,要求使用 null 初始化 x.bx.c。当写 let x = {a: 'abc'} 时,x.bx.cundefined,所以这个答案并不完全满足要求,尽管它是一个聪明的快速修复。
@BennyNeugebauer 接受的答案有同样的缺陷。这是最好的答案
如果 x 是具有默认值的对象,则创建最终实例对象 let a: IX = Object.assign({b:true}, x); 将导致 b,c 在实例对象中也是可选的,这可能是不需要的
为了方便使用界面的人,属性不应标记为“可选”。答案是“不”,您不能“告诉接口提供默认值”,但您可以提供工厂方法来初始化接口的实例。
o
oluckyman

虽然@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

这不能用作每种情况的默认解决方案,但绝对是某些情况下的创造性解决方案。 👍
如果不只是记录我想使用它们的值,比如 const calcvalue = this.options.a * 1000;这仍然会引起警报,因为它可能是未定义的..
@ManishRawat 例如,您可以不将任何属性声明为可选,而是让构造函数采用 Partial<IXOptions> 类型。这样 TS 就知道所有属性都将出现在 this.options 中,但构造函数中不需要任何属性。
@Thor84no 虽然 Partial 的定义是让一切都是可选的,但这就是它所做的一切
@JacksonHaenchen 将部分作为构造函数的参数与返回未列为部分的对象不同。此处的构造函数将首先将所有内容分配为默认值,然后将其作为输入值(如果提供)。因此,输入值可以是部分的,而不会影响创建的对象是否完整和类型安全。
N
Nitzan Tomer

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

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

F
F. Bauer

您可以按照文档中的说明使用 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'
}

如果 x 是具有默认值的对象,则创建最终实例对象 let a: IX = Object.assign({b:true}, x); 将导致错误 Partial<IX> cannot be assigned to IX
部分更改类型。 x 不再实现 IX,而是 IX 的一部分。 Partial 适用于每个属性都可能是可选的地方,例如使用 ORM,您可以在其中传递对象接口的一部分并仅更新已定义的字段(与 undefined 相反,这是每个字段Partial 可以)。对于具有默认值字段的接口,您可以使用 let x: Partial<IX> = { /* non-default fields */ } as IX 语法声明实现这些类型接口的对象文字,而无需声明默认值。
x 不实现 IX,但您仍然可以使用它通过使用默认值来创建实现 IX 的对象。但是,您需要为所有必需的值提供默认值,否则无法保证实现 IX。例如const defaults: IX = { a: 'default', b: {}, c: new AnotherType() }; const y = { ...defaults, ...x };
v
vitalets

我使用以下模式:

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

W
Willem van der Veen

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

替代解决方案:

您可以为此使用工厂方法,该方法返回一个实现 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,则此错误也出现在您的示例中)。


o
or soffer

如果您有很多参数,让用户只插入少量参数而不是按特定顺序插入,这是最好的做法。

例如,不好的做法:

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

有两种可能的解决方案:

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) const defaultObject = {a=undefined, b=1, c=99, d=88, e=undefined} function foo(object) { const {a,b,c,d ,e} = { ...defaultObject, ...object, } //继续函数代码.. }


M
Moss Palmer

我在寻找比我所达到的更好的方法时偶然发现了这一点。阅读答案并尝试后,我认为值得发布我正在做的事情,因为其他答案对我来说并不简洁。每次设置新界面时,我只需要编写少量代码,这对我来说很重要。我定了...

使用自定义的通用 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 不会提示自定义初始化值,也不会在执行时检查类型。


L
Luckylooke

我的解决方案:

我已经在 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;

环境开发者.ts

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

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

export default enviromentDevValues;

S
Steven Vachon

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

n
nrofis

这取决于情况和用法。通常,在 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 作为默认值。

甚至

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

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

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


J
Jose Quijada

您还可以有一个辅助方法/函数,它返回具有默认属性值的对象,然后调用代码可以根据需要覆盖默认值。这就是我正在遵循的方法,因为我在当前项目中遇到了同样的问题。这种对默认属性值对象进行编码的方式是一次性的,您可以在整个应用程序中重用该对象。


g
garlicbread

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

让我们安装合并

yarn add -D merge

接下来让我们创建一个带有一些选项的界面。我们将其放入 ./types/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)
}

T
Tim Mouskhelichvili

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

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

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

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

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

此答案取自“How To Set Up A TypeScript Interface Default Value?


M
Max Baldwin

现在解决这个问题。使用 class 而不是 interface

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

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

p
papierkorp

我需要这个作为 React 组件。

如果左侧值为 Null 或未定义,您可以使用 Nullish Coalescing Operator 将分配一个默认值:

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

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

但这仅在您只想在一个地方使用该变量时才有效。