Giá trị mặc định của giao diện Typecript


137

Tôi có giao diện sau trong TypeScript:

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

Tôi khai báo một biến kiểu đó và tôi khởi tạo tất cả các thuộc tính

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

Sau đó, tôi gán các giá trị thực cho chúng trong một hàm init sau đó

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

Nhưng tôi không thích phải chỉ định một loạt các giá trị null mặc định cho mỗi thuộc tính khi khai báo đối tượng khi chúng sẽ chỉ được đặt sau này thành giá trị thực. Tôi có thể yêu cầu giao diện mặc định các thuộc tính mà tôi không cung cấp thành null không? Điều gì sẽ cho phép tôi làm điều này:

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

mà không gặp lỗi trình biên dịch. Ngay bây giờ nó cho tôi biết

TS2322: Không thể gán loại '{}' cho loại 'IX'. Thuộc tính 'b' bị thiếu trong loại '{}'.


Tôi đã thêm tài liệu cho bạn: basarat.gitbooks.io/typescript/content/docs/tips/…
basarat

3
IMO, câu trả lời stackoverflow.com/a/35074490/129196 không nên là cách tiếp cận để thực hiện. Nếu bạn có thể có một đối tượng ở một trạng thái mà không cần khởi tạo tất cả các thuộc tính của nó và vẫn hợp lệ, thì bạn nên khai báo các thuộc tính đó là tùy chọn như được chỉ ra bởi câu trả lời này: stackoverflow.com/a/43226857/129196 . Nếu không, chúng ta sẽ mất mục đích duy nhất của việc sử dụng typecript (cho an toàn kiểu).
Charles Prakash Dasari

Câu trả lời:


98

Tôi có thể yêu cầu giao diện mặc định các thuộc tính mà tôi không cung cấp thành null không? Điều gì sẽ để tôi làm điều này

Không. Nhưng theo mặc định, chúng undefinedchủ yếu là tốt. Bạn có thể sử dụng mẫu sau, tức là có một xác nhận kiểu tại điểm tạo:

let x: IX = {} as any;

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

Tôi có cái này và các mẫu khác được ghi lại ở đây: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Cảm ơn bạn, mà không xác định loạt các giá trị mặc định tôi sử dụng để có thể thiết lập giá trị cho tài sản không xác định
Omar Isaid

42
Sử dụng anylàm suy yếu mục đích của Type Script. Có những câu trả lời khác mà không có nhược điểm này.
Jack Miller

bất kỳ ai có thể giúp tôi về câu hỏi tương tự nhưng sử dụng generic. Đây là câu hỏi này
TAB

2
Kỳ lạ là basarat sẽ đi với ví dụ 'bất kỳ' khi, trong liên kết được cung cấp, anh ấy đưa ra một lựa chọn tốt hơn nhiều với 'let foo = {} as Foo;' ('Foo "là một giao diện)
Neurothustra

1
OP đã gặp khó khăn trong việc tạo giao diện TypeScript và đang yêu cầu các giải pháp cho việc mạ lò hơi. Giải pháp của bạn là từ bỏ Interfaces hoàn toàn? Cũng có thể gợi ý rằng hãy bỏ qua TypeScript ...
Moss Palmer

98

Bạn không thể đặt các giá trị mặc định trong giao diện, nhưng bạn có thể thực hiện những gì bạn muốn bằng cách sử dụng Thuộc tính tùy chọn (so sánh đoạn # 3):

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

Chỉ cần thay đổi giao diện thành:

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

Sau đó, bạn có thể làm:

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

Và sử dụng hàm init của bạn để gán các giá trị mặc định cho x.bx.cnếu các properies đó không được đặt.


3
Trong câu hỏi, nó được yêu cầu khởi tạo x.bx.cvới null. Khi viết let x = {a: 'abc'}then x.bx.care undefined, vì vậy câu trả lời này không đáp ứng đầy đủ các yêu cầu, mặc dù đó là một bản sửa lỗi nhanh thông minh.
Benny Neugebauer

1
@BennyNeugebauer Câu trả lời được chấp nhận có cùng một lỗ hổng. Đây là câu trả lời hay nhất
điện thoại

1
nếu x là đối tượng với giá trị mặc định, sau đó tạo ra đối tượng ví dụ cuối cùng let a: IX = Object.assign({b:true}, x);sẽ gây ra b, c là không bắt buộc cũng trong đối tượng dụ, mà có thể không được mong muốn
Luckylooke

Các thuộc tính không được đánh dấu "tùy chọn" để thuận tiện cho những người sử dụng giao diện. Câu trả lời là "không", bạn không thể "yêu cầu giao diện cung cấp giá trị mặc định" nhưng bạn có thể cung cấp một phương thức gốc để khởi tạo một thể hiện của giao diện.
Rick O'Shea

35

Trong khi câu trả lời của @ Timar hoạt động hoàn hảo cho nullcác giá trị mặc định (những gì được yêu cầu), đây là một giải pháp dễ dàng khác cho phép các giá trị mặc định khác: Xác định giao diện tùy chọn cũng như một hằng số theo chứa các giá trị mặc định; trong hàm tạo, sử dụng toán tử spread để đặt optionsbiến thành viên

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

Bây giờ bạn có thể sử dụng lớp như thế này:

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

Đầu ra:

set
null
1

Đây không thể được sử dụng như một giải pháp mặc định cho mọi trường hợp, nhưng chắc chắn là một giải pháp sáng tạo cho một số trường hợp. 👍
abhay tripathi

điều gì sẽ xảy ra nếu thay vì chỉ ghi lại các giá trị tôi muốn sử dụng chúng như const calcvalue = this.options.a * 1000; điều này sẽ vẫn làm tăng cảnh báo vì nó có thể không được xác định ..
Manish Rawat

Có, bạn có thể sử dụng một lớp học hoặc viết nó bằng một ngôn ngữ khác, không câu nào trả lời được câu hỏi của anh ta.
Rick O'Shea

22

Bạn có thể triển khai giao diện với một lớp, sau đó bạn có thể xử lý việc khởi tạo các thành viên trong hàm tạo:

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];
        }
    }
}

Một cách tiếp cận khác là sử dụng một hàm nhà máy:

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

Sau đó, bạn có thể chỉ cần:

var ix: IX = null;
...

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

4

Bạn có thể sử dụng Partialloại được ánh xạ như được giải thích trong tài liệu: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Trong ví dụ của bạn, bạn sẽ có:

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

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

nếu x là đối tượng với giá trị mặc định, sau đó tạo ra đối tượng ví dụ cuối cùng let a: IX = Object.assign({b:true}, x);sẽ gây ra lỗiPartial<IX> cannot be assigned to IX
Luckylooke

Một phần thay đổi loại. xkhông còn thực hiện IX, nhưng một phần của IX. Partialphù hợp với những nơi mà mọi thuộc tính có thể là tùy chọn, ví dụ với ORM, nơi bạn có thể chuyển một phần của giao diện đối tượng và chỉ cập nhật các trường được xác định (trái ngược với undefinedđó là những gì mọi trường của a Partialcó thể là). Đối với các giao diện có các trường có giá trị mặc định, bạn có thể khai báo các ký tự đối tượng triển khai các kiểu giao diện đó mà không cần phải khai báo các giá trị mặc định bằng let x: Partial<IX> = { /* non-default fields */ } as IXcú pháp.
Paul-Sebastian Manole

1

Tôi đã vấp phải điều này trong khi tìm kiếm một con đường tốt hơn những gì tôi đã đến. Sau khi đọc các câu trả lời và thử chúng, tôi nghĩ rằng đáng để đăng những gì tôi đang làm vì các câu trả lời khác không cảm thấy ngắn gọn đối với tôi. Điều quan trọng đối với tôi là chỉ phải viết một lượng mã ngắn mỗi khi tôi thiết lập một giao diện mới. Tôi đã quyết định ...

Sử dụng một hàm deepCopy chung chung tùy chỉnh:

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

Xác định giao diện của bạn

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

... và xác định các giá trị mặc định trong một hằng số riêng biệt.

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

Sau đó, init như thế này:

let x : IX = deepCopy(XDef);

Đó là tất cả những gì cần thiết ..

.. Tuy nhiên ..

Nếu bạn muốn tùy chỉnh khởi tạo bất kỳ phần tử gốc nào, bạn có thể sửa đổi hàm deepCopy để chấp nhận các giá trị mặc định tùy chỉnh. Hàm trở thành:

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

Thay vào đó, có thể được gọi như thế này:

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

Bất kỳ cách sao chép sâu ưa thích nào khác sẽ hoạt động. Nếu chỉ cần một bản sao cạn thì Object.assign là đủ, loại bỏ nhu cầu về tiện ích deepCopyhoặc deepCopyAssignchức năng.

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

Các vấn đề đã biết

  • Nó sẽ không gán sâu trong chiêu bài này nhưng không quá khó để sửa đổi deepCopyAssignđể lặp lại và kiểm tra các loại trước khi gán.
  • Các hàm và tham chiếu sẽ bị mất bởi quá trình phân tích cú pháp / chuỗi ký tự. Tôi không cần những thứ đó cho nhiệm vụ của mình và OP cũng vậy.
  • Giá trị init tùy chỉnh không được gợi ý bởi IDE hoặc loại được kiểm tra khi thực thi.

0

Giải pháp của tôi:

Tôi đã tạo trình bao bọc trên Object.assign để khắc phục sự cố nhập.

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

Sử dụng:

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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.