Typescript: Misunderstanding about duck typing

Viewed 268

I'm studying Typescript reading Handbooks & Docs. And i'm just curious about how Typescript Duck Typing Works.

For example, this code provides Type error (using Typescript official Playground)

interface Point {
    x: number;
    y: number;
}

const p: Point = {
    x: 1,
    y: 2,
    z: 3
}

function printPoint(p: Point) {
    console.log(p.x + p.y);
}

printPoint(p);

but this code does not provides Type error

interface Point {
    x: number;
    y: number;
}

const p = {
    x: 1,
    y: 2,
    z: 3
}

function printPoint(p: Point) {
    console.log(p.x + p.y);
}

printPoint(p);

i thinks the problem is related to Typescript's Duck Typing, but i could not understand why argument's type system & constant's type system works in different way.

did i misunderstand about the Duck Typing? const p contains x, y, so i think const p: Point should work

3 Answers

The first code showed TypeError because the Point interface does not have a member z. The second did not have type error, because in the function, all the properties referenced(x and y) exist in type Point. This is due to Duck Typing and it being accepted as a Point or not because z member is not referenced. Inside the function, if you try to use p.z, you will get type error.

A functions arguments and return type values in TS are checked to ensure they are subtype compatible as long as strictFunctionTypes is enabled in the tsconfig. This is why you're seeing the behaviour in your second example as typeof p is as a subtype of Point.

When you type a variable or constant it is strictly typed, subtypes or extended types would only be allowed if you explicitly allow them.

interface Point {
    x: number;
    y: number;
}

// There are many ways to do this but you could for example allow
// an extended type to have a subtype of Point and any number 
// of string properties that have number values.
type ExtendedPoint = Point & Record<string, number>;

// Allow subtypes of Point
type UnrestrictedPoint = Partial<Point>;

TS Playground: Your original example but allowing subtypes

Typescript enforces different levels of strictness depending on what you're doing. When you create a variable with an explicit type, typescript is very strict about what must be there. You must exactly follow the type definition. Anything else is very likely a bug in your code, so by being as strict as possible it helps you to catch bugs.

When you try to assign one variable to another, including passing a variable into a function, typescript does a looser check. The two variables just need to be compatible with eachother, which roughly means they need to have at least the listed properties with the right types, but it's ok if it also has more properties. This looser level of checking is important to support subtypes and subclasses. You should be able to pass a subclass in where a baseclass is called for (the liskov substitution principle), but the subclass will often have extra properties

See this link for more information on type compatibility: https://www.typescriptlang.org/docs/handbook/type-compatibility.html

Related