So I read that in Typescript the ‘declare’ keyword is like the ‘extern’ keyword in C in that it’s declaring a variable that’s defined elsewhere. I think in this case it’s defined in the browser:
declare var Request: {
prototype: Request;
new(input: RequestInfo | URL, init?: RequestInit): Request;
};
I really don’t get:
prototype: Request
As Request is the same as Request.
2
The var
in your example is part of a var
/interface
pair that TypeScript standard libraries use to declare class
and classlike built-in stuff in TypeScript.
There are two different things named Request
in your example. One is the value, a var
which can be expected to exist at runtime. The other is the type, a (presumably) interface
, which is erased upon compilation to JavaScript and definitely will not exist at runtime. TypeScript can tell them apart because they are in different syntactic contexts; if you see declare var x: Y
, the x
is the name of a value, and the Y
is the name of the type.
This sort of declaration is common in the TypeScript standard libraries to emulate class
declarations. Let’s look at what you get when you write
declare class Foo {
a: string;
b: number;
constructor(a: string, b: number);
}
That brings into scope both a value and a type named Foo
. The value is the Foo
constructor, which should exist at runtime, and let you write things like new Foo("abc", 123)
. The type is the type of Foo
instances and essentially the same as an interface like {a: string, b: number}
. The fact that these two different things has the same name is actually quite convenient for TypeScript developers, because it means you can write const foo: Foo = new Foo("abc", 123)
. It didn’t have to be that way; it could have been const foo: FooInstance = new Foo("abc", 123)
, but generally people expect the name of the TypeScript instance type to be the same as the constructor.
But there are some backwards compatibility and edge case reasons why most of TypeScript’s standard libraries for classes and classlike things are not declared as class
. See microsoft/TypeScript#55611. Instead they are declared as a var
/interface
pair:
interface Foo {
a: string;
b: number;
}
declare var Foo: {
prototype: Foo;
new(a: string, b: number): Foo;
}
This behaves essentially the same as the declare class
version above, but it explicitly brings into scope the type and the value separately. The value Foo
is a variable that has a construct signature, meaning it’s a constructor. And the interface Foo
is the instance type. Note that TypeScript generally models class prototype
s as being the same type as the class instance (although this isn’t usually true for class methods, it’s a convenient fiction; see microsoft/TypeScript#55904 and issues linked within for the reason), so that’s written out explicitly here too.
So you can do everything with the var
/interface
pair that you can do with class
. The following code behaves the same either way:
const foo = new Foo("abc", 123);
// ^? const foo: Foo;
foo.a.toUpperCase();
foo.b.toFixed();
So that’s what’s going on. If you look at the TypeScript library definitions for Request
you’ll see the interface
as well. This could be rewritten as something like
declare class Request {
new(input: RequestInfo | URL, init?: RequestInit): Request;
readonly body: ReadableStream<Uint8Array> | null;
readonly bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
readonly cache: RequestCache;
readonly credentials: RequestCredentials;
readonly destination: RequestDestination;
readonly headers: Headers;
readonly integrity: string;
readonly keepalive: boolean;
readonly method: string;
readonly mode: RequestMode;
readonly redirect: RequestRedirect;
readonly referrer: string;
readonly referrerPolicy: ReferrerPolicy;
readonly signal: AbortSignal;
readonly url: string;
clone(): Request;
}
and it would behave mostly the same.
Playground link to code