Primitive Types

TypeScript supports 7 primitive types. A primitive data type refers to a type that is not an object and does not have any methods associated with it. In TypeScript, all primitive types are immutable, meaning their values cannot be changed once they are assigned.

Symbols

Symbols are a primitive data type that represents an immutable value which is guaranteed to be globally unique throughout the lifetime of the program.

Symbols can be used as keys for object properties and provide a way to create non-enumerable properties.

const key1: symbol = Symbol('key1');
const key2: symbol = Symbol('key2');

const obj = {
    [key1]: 'value 1',
    [key2]: 'value 2',
};

console.log(obj[key1]); // value 1
console.log(obj[key2]); // value 2

In WeakMaps and WeakSets, symbols are now permissible as keys.

Symbols are unique identifiers that can be used as property keys in objects to prevent naming conflicts.

type Obj = {
    [sym: symbol]: number;
};

const a = Symbol('a');
const b = Symbol('b');
let obj: Obj = {};
obj[a] = 123;
obj[b] = 456;

console.log(obj[a]); // 123
console.log(obj[b]); // 456

Template String Pattern Index Signatures

Template string pattern index signatures allow us to define flexible index signatures using template string patterns. This feature enables us to create objects that can be indexed with specific patterns of string keys, providing more control and specificity when accessing and manipulating properties.

TypeScript from version 4.4 allows index signatures for symbols and template string patterns.

const uniqueSymbol = Symbol('description');

type MyKeys = `key-${string}`;

type MyObject = {
    [uniqueSymbol]: string;
    [key: MyKeys]: number;
};

const obj: MyObject = {
    [uniqueSymbol]: 'Unique symbol key',
    'key-a': 123,
    'key-b': 456,
};

console.log(obj[uniqueSymbol]); // Unique symbol key
console.log(obj['key-a']); // 123
console.log(obj['key-b']); // 456

using declaration and Explicit Resource Management

A using declaration is a block-scoped, immutable binding, similar to const, used for managing disposable resources. When initialized with a value, the Symbol.dispose method of that value is recorded and subsequently executed upon exiting the enclosing block scope.

This is based on ECMAScript’s Resource Management feature, which is useful for performing essential cleanup tasks after object creation, such as closing connections, deleting files, and releasing memory.

>tags: [[Important]] [[SuppressedError]] [[Disposal]]

Notes:

  • Due to its recent introduction in TypeScript version 5.2, most runtimes lack native support. You’ll need polyfills for: Symbol.dispose, Symbol.asyncDispose, DisposableStack, AsyncDisposableStack, SuppressedError.
  • Additionally, you will need to configure your tsconfig.json as follows:
{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "esnext.disposable", "dom"]
    }
}

Example:

>tags: [[Important]] [[Error_Dispose]] [[Error]] [[Symbol]] #Polify

//@ts-ignore
Symbol.dispose ??= Symbol('Symbol.dispose'); // Simple polify

const doWork = (): Disposable => {
    return {
        [Symbol.dispose]: () => {
            console.log('disposed');
        },
    };
};

console.log(1);

{
    using work = doWork(); // Resource is declared
    console.log(2);
} // Resource is disposed (e.g., `work[Symbol.dispose]()` is evaluated)

console.log(3);

The code will log:

1
2
disposed
3

A resource eligible for disposal must adhere to the Disposable interface:

// lib.esnext.disposable.d.ts
interface Disposable {
    [Symbol.dispose](): void;
}

The using declarations record resource disposal operations in a stack, ensuring they are disposed in reverse order of declaration:

{
    using j = getA(),
        y = getB();
    using k = getC();
} // disposes `C`, then `B`, then `A`.

Resources are guaranteed to be disposed, even if subsequent code or exceptions occur. This may lead to disposal potentially throwing an exception, possibly suppressing another. To retain information on suppressed errors, a new native exception, SuppressedError, is introduced.

await using declaration

An await using declaration handles an asynchronously disposable resource. The value must have a Symbol.asyncDispose method, which will be awaited at the block’s end.

async function doWorkAsync() {
    await using work = doWorkAsync(); // Resource is declared
} // Resource is disposed (e.g., `await work[Symbol.asyncDispose]()` is evaluated)

For an asynchronously disposable resource, it must adhere to either the Disposable or AsyncDisposable interface:

// lib.esnext.disposable.d.ts
interface AsyncDisposable {
    [Symbol.asyncDispose](): Promise<void>;
}
//@ts-ignore
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose'); // Simple polify

class DatabaseConnection implements AsyncDisposable {
    // A method that is called when the object is disposed asynchronously
    [Symbol.asyncDispose]() {
        // Close the connection and return a promise
        return this.close();
    }

    async close() {
        console.log('Closing the connection...');
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log('Connection closed.');
    }
}

async function doWork() {
    // Create a new connection and dispose it asynchronously when it goes out of scope
    await using connection = new DatabaseConnection(); //  Resource is declared
    console.log('Doing some work...');
} // Resource is disposed (e.g., `await connection[Symbol.asyncDispose]()` is evaluated)

doWork();

The code logs:

Doing some work...
Closing the connection...
Connection closed.

The using and await using declarations are allowed in Statements: for, for-in, for-of, for-await-of, switch.

string

The string primitive type stores textual data, and the value is always double or single-quoted.

const x: string = 'x';
const y: string = 'y';

Strings can span multiple lines if surrounded by the backtick (`) character:

let sentence: string = `xxx,yyy`;

boolean

The boolean data type in TypeScript stores a binary value, either true or false.

const isReady: boolean = true;

number

A number data type in TypeScript is represented with a 64-bit floating point value. A number type can represent integers and fractions. TypeScript also supports hexadecimal, binary, and octal, for instance:

const decimal: number = 10;
const hexadecimal: number = 0xa00d; // Hexadecimal starts with 0x
const binary: number = 0b1010; // Binary starts with 0b
const octal: number = 0o633; // Octal starts with 0o

bigInt

A bigInt represents numeric values that are very large (253 – 1) and cannot be represented with a number.

A bigInt can be created by calling the built-in function BigInt() or by adding n to the end of any integer numeric literal:

const x: bigint = BigInt(9007199254740991);
const y: bigint = 9007199254740991n;

Notes:

  • bigInt values cannot be mixed with number and cannot be used with built-in Math, they must be coerced to the same type.
  • bigInt values are available only if target configuration is ES2020 or higher.