ChatGPT解决这个技术问题 Extra ChatGPT

Type definition in object literal in TypeScript

In TypeScript classes it's possible to declare types for properties, for example:

class className {
  property: string;
};

How do declare the type of a property in an object literal?

I've tried the following code but it doesn't compile:

var obj = {
  property: string;
};

I'm getting the following error:

The name 'string' does not exist in the current scope

Am I doing something wrong or is this a bug?


T
Timothy Perez

You're pretty close, you just need to replace the = with a :. You can use an object type literal (see spec section 3.5.3) or an interface. Using an object type literal is close to what you have:

var obj: { property: string; } = { property: "foo" };

But you can also use an interface

interface MyObjLayout {
    property: string;
}

var obj: MyObjLayout = { property: "foo" };

The DRY (Don't Repeat Yourself) option by @Rick Love with the cast operator seems mucho neater. Just commenting, not downvoting...
I know this has been answered a while ago, but am I mistaken in the assumptions that issues can arise when the class has methods as well as properties? Since the class doesn't get initialized and we only assign properties, calling a method on the class will cause a null exception. Basically, the object we create only 'acts' as the class because we assign its type, but it is not actually an instance of that class. I.e. we need to create the class with the 'new' keyword. And we're then back to square 1, since we can't do something like new class() {prop: 1}; in TS like we can in C#, for example.
@DeuxAlpha This is assigning to an interface, which can't have methods. I don't believe that you can assign to a class like this.
link to spec would be nice :)
@DeuxAlpha this is creating an object literal, not an class object. Also an interface can have methods. If your interface defines a method, then the object literal must also define it - it won't be null. The object literal must fulfill everything the the interface defines or the type system will show an error.
R
Rick Love

Update 2019-05-15 (Improved Code Pattern as Alternative)

After many years of using const and benefiting from more functional code, I would recommend against using the below in most cases. (When building objects, forcing the type system into a specific type instead of letting it infer types is often an indication that something is wrong).

Instead I would recommend using const variables as much as possible and then compose the object as the final step:

const id = GetId();
const hasStarted = true;
...
const hasFinished = false;
...
return {hasStarted, hasFinished, id};

This will properly type everything without any need for explicit typing.

There is no need to retype the field names.

This leads to the cleanest code from my experience.

This allows the compiler to provide more state verification (for example, if you return in multiple locations, the compiler will ensure the same type of object is always returned - which encourages you to declare the whole return value at each position - giving a perfectly clear intention of that value).

Addition 2020-02-26

If you do actually need a type that you can be lazily initialized: Mark it is a nullable union type (null or Type). The type system will prevent you from using it without first ensuring it has a value.

In tsconfig.json, make sure you enable strict null checks:

"strictNullChecks": true

Then use this pattern and allow the type system to protect you from accidental null/undefined access:



const state = {
    instance: null as null | ApiService,
    // OR
    // instance: undefined as undefined | ApiService,

};

const useApi = () => {
    // If I try to use it here, the type system requires a safe way to access it

    // Simple lazy-initialization 
    const api = state?.instance ?? (state.instance = new ApiService());
    api.fun();

    // Also here are some ways to only access it if it has value:

    // The 'right' way: Typescript 3.7 required
    state.instance?.fun();

    // Or the old way: If you are stuck before Typescript 3.7
    state.instance && state.instance.fun();

    // Or the long winded way because the above just feels weird
    if (state.instance) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans way
    if (state.instance != null) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans 
    // AND I was told to always use triple === in javascript even with null checks way
    if (state.instance !== null && state.instance !== undefined) { state.instance.fun(); }
};

class ApiService {
    fun() {
        // Do something useful here
    }
}

Do not do the below in 99% of cases:

Update 2016-02-10 - To Handle TSX (Thanks @Josh)

Use the as operator for TSX.

var obj = {
    property: null as string
};

A longer example:

var call = {
    hasStarted: null as boolean,
    hasFinished: null as boolean,
    id: null as number,
};

Original Answer

Use the cast operator to make this succinct (by casting null to the desired type).

var obj = {
    property: <string> null
};

A longer example:

var call = {
    hasStarted: <boolean> null,
    hasFinished: <boolean> null,
    id: <number> null,
};

This is much better than having two parts (one to declare types, the second to declare defaults):

var callVerbose: {
    hasStarted: boolean;
    hasFinished: boolean;
    id: number;
} = {
    hasStarted: null,
    hasFinished: null,
    id: null,
};

If you are using TSX (TS with JSX), you cannot use the angle bracket naming, so those lines become something like property: null as string wherein the important difference is the as operator.
@RickLove Does this actually constrain the type of the object variable, or is this only a way to specify the types when assigned? In other words, after you assign to the variable call in your second example, can you assign a completely different type to it?
This should be answer
not working. Error:(33, 15) TS2352:Type 'null' cannot be converted to type 'string'.
Is this just an abuse of a feature of the language or is this actually Legit? Could you provide link for mor reading to oficial docs? Thanks!
L
LogicalBranch

I'm surprised that no-one's mentioned this but you could just create an interface called ObjectLiteral, that accepts key: value pairs of type string: any:

interface ObjectLiteral {
  [key: string]: any;
}

Then you'd use it, like this:

let data: ObjectLiteral = {
  hello: "world",
  goodbye: 1,
  // ...
};

An added bonus is that you can re-use this interface many times as you need, on as many objects you'd like.

Good luck.


This is a bad idea, it makes the type system worthless. The point of typescript is to allow the type system to help prevent errors and also to help provide better autocomplete functionality in tooling - this basically disables all the benefits of Typescript. It would be better to not use any interface in the above example.
@RickLove I strongly disagree. This has been exceptionally useful when several properties are optional but we still want to clearly define all types within (e.g. containing arguments for a function). This could be simply viewed as syntactic sugar and literally works like a spread operator for Interfaces' properties.
@CPHPython why not just specify the optional parameters with their specific types? The only time this would make sense is if the names are not known at code time (I.e. they are coming from a database or external source). Also for complex combinations of arguments, union types work great. If you are coding against specific names, they should be defined if possible. If it is just data that doesn’t affect logic, then of course leave it out from the type system.
@RickLove "the names are not known at code time" -> this is a good and practical example, the values of those keys types are known and they are all the same (e.g. string). Mind that I was only advocating the use of the [key: string] part, not the any part as the value's type definition... That'd indeed take out the types usefulness.
How would I go about if I want to allow string and numbers to be added but not other types?
s
streletss

You could use predefined utility type Record<Keys, Type> :

const obj: Record<string, string> = {
  property: "value",
};

It allows to specify keys for your object literal:

type Keys = "prop1" | "prop2"

const obj: Record<Keys, string> = {
  prop1: "Hello",
  prop2: "Aloha",
  something: "anything" // TS Error: Type '{ prop1: string; prop2: string; something: string; }' is not assignable to type 'Record<Keys, string>'.
                        //   Object literal may only specify known properties, and 'something' does not exist in type 'Record<Keys, string>'.
};

And a type for the property value:

type Keys = "prop1" | "prop2"
type Value = "Hello" | "Aloha"

const obj1: Record<Keys, Value> = {
  prop1: "Hello",
  prop2: "Hey", // TS Error: Type '"Hey"' is not assignable to type 'Value'.
};

Exactly what I needed. Thanks! Typescript for the win!
I'd also use let instead of var here.
if you have a dynamic type, use this.
H
Hugo Elhaj-Lahsen

If you're trying to write a type annotation, the syntax is:

var x: { property: string; } = { property: 'hello' };

If you're trying to write an object literal, the syntax is:

var x = { property: 'hello' };

Your code is trying to use a type name in a value position.


E
Egor

If you're trying to add typings to a destructured object literal, for example in arguments to a function, the syntax is:

function foo({ bar, baz }: { bar: boolean, baz: string }) {
  // ...
}

foo({ bar: true, baz: 'lorem ipsum' });

L
LogicalBranch

In TypeScript if we are declaring object then we'd use the following syntax:

[access modifier] variable name : { /* structure of object */ }

For example:

private Object:{ Key1: string, Key2: number }

A
Audwin Oyong

Beware. It may seem obvious to some, but the type declaration:

const foo: TypeName = {}

is not the same compared to casting with as:

const foo = {} as TypeName

despite the suggestions to use it on other answers.

Example:

Thanks, type-safety!:

const foo: { [K in 'open' | 'closed']: string } = {}
// ERROR: TS2739: Type '{}' is missing the following properties from type '{ open: string; closed: string; }': open, closed

Goodbye, type-safety!:

const foo = {} as { [K in 'open' | 'closed']: string }
// No error

H
Hamza Ibrahim
// Use ..

const Per = {
  name: 'HAMZA',
  age: 20,
  coords: {
    tele: '09',
    lan: '190'
  },
  setAge(age: Number): void {
    this.age = age;
  },
  getAge(): Number {
    return age;
  }
};
const { age, name }: { age: Number; name: String } = Per;
const {
  coords: { tele, lan }
}: { coords: { tele: String; lan: String } } = Per;

console.log(Per.getAge());

Hey and welcome to SO! Can you expand your answer at all, it would be helpful to explain how/why this works.
R
Ray Foss

This is what I'm doing in 2021 with TypeScript 4.5:

const sm = {
  reg: {} as ServiceWorkerRegistration,
  quantum: null as number | null,
  currentCacheName: '' as string, // superfluous
  badSWTimer: 0 as number, // superfluous
}

This is not just a value cast, but works the same as an interface definition, for the object properties that is.

Update: I included two superfluous typings as an example. That is, these typings can be inferred automatically and thus, would not generate compiler errors.

Source: 4.4 Playground


Could you link a TS 4.5 reference please? (Especially for "not just a value cast" part.)
@JanMolnar Much obliged... see Source link
Thank you. Unfortunately I do not see a difference between the mentioned TS 4.5, linked 4.4 and e.g. the old 3.3, so I do not know what is the thing which is new in 2021. Maybe "new since 2012" was meant, not specifically in 2021.
I don't think you should ever really use as
W
Willem van der Veen

In your code:

var obj = {
  myProp: string;
};

You are actually creating a object literal and assigning the variable string to the property myProp. Although very bad practice this would actually be valid TS code (don't use this!):

var string = 'A string';

var obj = {
  property: string
};

However, what you want is that the object literal is typed. This can be achieved in various ways:

Interface:

interface myObj {
    property: string;
}

var obj: myObj = { property: "My string" };

Type alias:

type myObjType = {
    property: string
};

var obj: myObjType = { property: "My string" };

Object type literal:

var obj: { property: string; } = { property: "Mystring" };

A
Akhil Sharma

create a type using type keyword

type ObjType = {
  property: string;
}

and then you can use it to bind your object to accept this type only, like below.

const obj: ObjType = {
property: "TypeScript"
}

Z
Zoman

Just to extend @RickLove's reply...

This works great, as you only need to define the type that cannot be inferred:

const initialState = { 
   user: undefined as User | undefined, 
   userLoading: false
}; 

and it transpiles to this js code:

const initialState = { 
   user: undefined, 
   userLoading: false
};  

And if you need to extract it into a type, you can just do this:

export type InitState = typeof initialState;

C
CarbonDry

Convert Object Literal into Type With DRY

Just do:

const myObject = {
   hello: 'how are you',
   hey: 'i am fine thank you'
}
type myObjectType = keyof typeof MyObject

Job done!