我在 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”。
我可以告诉接口将我不提供的属性默认为 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()
您不能在界面中设置默认值,但您可以通过使用可选属性来完成您想做的事情(比较第 3 段):
https://www.typescriptlang.org/docs/handbook/interfaces.html
只需将界面更改为:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
然后你可以这样做:
let x: IX = {
a: 'abc'
}
如果未设置这些属性,请使用您的 init 函数为 x.b
和 x.c
分配默认值。
null
初始化 x.b
和 x.c
。当写 let x = {a: 'abc'}
时,x.b
和 x.c
是 undefined
,所以这个答案并不完全满足要求,尽管它是一个聪明的快速修复。
let a: IX = Object.assign({b:true}, x);
将导致 b,c 在实例对象中也是可选的,这可能是不需要的
虽然@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
Partial<IXOptions>
类型。这样 TS 就知道所有属性都将出现在 this.options
中,但构造函数中不需要任何属性。
你可以用一个类来实现接口,然后你可以在构造函数中处理初始化成员:
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(...);
您可以按照文档中的说明使用 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'
}
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 };
我使用以下模式:
创建实用程序类型 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 }
接口的默认值是不可能的,因为接口仅在编译时存在。
替代解决方案:
您可以为此使用工厂方法,该方法返回一个实现 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,则此错误也出现在您的示例中)。
如果您有很多参数,让用户只插入少量参数而不是按特定顺序插入,这是最好的做法。
例如,不好的做法:
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, } //继续函数代码.. }
我在寻找比我所达到的更好的方法时偶然发现了这一点。阅读答案并尝试后,我认为值得发布我正在做的事情,因为其他答案对我来说并不简洁。每次设置新界面时,我只需要编写少量代码,这对我来说很重要。我定了...
使用自定义的通用 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 就足够了,不需要实用程序 deepCopy
或 deepCopyAssign
函数。
let x : IX = object.assign({}, XDef, { a:'customInitValue' });
已知的问题
它不会以这种形式进行深度分配,但修改 deepCopyAssign 以在分配之前迭代和检查类型并不难。
解析/字符串化过程将丢失函数和引用。我的任务不需要这些,OP 也不需要。
IDE 不会提示自定义初始化值,也不会在执行时检查类型。
我的解决方案:
我已经在 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;
您可以使用两个单独的配置。一个作为具有可选属性(将具有默认值)的输入,另一个仅具有必需的属性。使用 &
和 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 中,接口没有默认值。
如果您不使用默认值
您可以将 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
作为默认值,这取决于您的用例。
您还可以有一个辅助方法/函数,它返回具有默认属性值的对象,然后调用代码可以根据需要覆盖默认值。这就是我正在遵循的方法,因为我在当前项目中遇到了同样的问题。这种对默认属性值对象进行编码的方式是一次性的,您可以在整个应用程序中重用该对象。
另一种方法是使用 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)
}
另一种方法是使用 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?”
现在解决这个问题。使用 class
而不是 interface
。
class IX {
a: String = '';
b?: any;
c: Cee = new Cee();
}
class Cee {
c: String = 'c';
e: String = 'e';
}
我需要这个作为 React 组件。
如果左侧值为 Null 或未定义,您可以使用 Nullish Coalescing Operator 将分配一个默认值:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
const ixFunction: React.FC<IX> = (props) => {
console.log(props.b?? "DefaultValue")
}
但这仅在您只想在一个地方使用该变量时才有效。
any
会破坏 TypeScript 的目的。还有其他答案没有这个缺点。