Home > front end >  JavaScript Behavior - Thinking in TypeScript
JavaScript Behavior - Thinking in TypeScript

Time:12-19

Before going further, let me show you an example in JavaScript:

let a = 5
function fn() {
  console.log(a)
}
fn() // 5
a = 10
fn() // 10

The first function call logs the output 5, and the last call logs to 10.

In this sense, what I am thinking of TypeScript's interface behavior to merge. An example would be good to illustrate this:

Interfaces are merged:

interface Example {
  foo: string
}
interface Example {
  bar: string
}

So, this becomes:

interface Example {
  foo: string
  bar: string
}

Now, let me show you an example representing the thoughts:

interface Person {
  name: string
}

function myPersonFn() {
  interface Person {
    age: number
  }
  const inPerson: Person = {name: 'Bhojendra', age: 37}
  console.log(inPerson)
}

interface Person {
  address: string
}

const outPerson: Person = {name: 'Rauniyar', address: 'Kathmandu'}
console.log(outPerson)

This throws an error: (Good, the interface Person is function scoped.)

Type '{ name: string; age: number; }' is not assignable to type 'Person'.

  • Object literal may only specify known properties, and 'name' does not exist in type 'Person'.

Now, let's try extending it:

interface Person {
  name: string
}
function myPersonFn() {
  // type ScopedPerson = Person & {
  //   age: number
  // }
  interface ScopedPerson extends Person {
    age: number
  }
  const inPerson: ScopedPerson = {name: 'Bhojendra', age: 37}
  console.log(inPerson)
}
myPersonFn()
interface Person {
  address: string
}
const outPerson: Person = {name: 'Rauniyar', address: 'Kathmandu'}
console.log(outPerson)

This throws an error:

Property 'address' is missing in type '{ name: string; age: number; }' but required in type 'ScopedPerson'.

This is in fact typescript behavior. As it merges the interfaces the Person interface expects address to be in it.

But I could ask to TypeScript, can I ignore that?

Well, what if the function call at an end?

interface Person {
  address: string
}
myPersonFn()

Hmm, this makes us think that TypeScript is doing best thing for us not allowing to miss the address property.


Wait! What I am thinking for this is behave like JavaScript code as the a value logged in first code block. Mmm, generic something like to meet both behavior?

interface ScopedPerson<T, Optional> extends Person {

I don't know if this is even possible? You might have an idea, if you get my point?

What I want is, do not throw error, let it compile OK this line of code inside the function block:

const inPerson: ScopedPerson = {name: 'Bhojendra', age: 37}
console.log(inPerson)

Pretty well, I am not talking about optional address property:

interface Person {
  address?: string
}

CodePudding user response:

As it merges the interfaces the Person interface expects address to be in it. But I could ask to TypeScript, can I ignore that?

This is not possible. Type declarations completely are unordered. That's why this is fine:

type A = { a: B }
type B = number

But that also means that this:

interface Person { name: string }
function myPersonFn() {
  interface ScopedPerson extends Person { age: number }
  const inPerson: ScopedPerson = {name: 'Bhojendra', age: 37} // error
}
interface Person { address: string }

And this:

interface Person { name: string }
interface Person { address: string } // moved this before the function
function myPersonFn() {
  interface ScopedPerson extends Person { age: number }
  const inPerson: ScopedPerson = {name: 'Bhojendra', age: 37} // error
}

Are 100% identical in every way.

So there is no possible way for Typescript to know you want one piece if of an interface, but not another. They don't have different names or identifiers, and there's no record in the type itself that this was merged at all.


If you want to reason about sub sets of this type, then they need to be separate types.

For example:

// This is just a piece of a `Person`, so call it `PersonName`.
interface PersonName { name: string }

// Maybe create another type for only a persons address
interface PersonAddress { address: string }

// Then create a full person from its pieces
interface Person extends PersonName, PersonAddress {}

function myPersonFn() {
  interface ScopedPerson extends PersonName { age: number }
  const inPerson: ScopedPerson = {name: 'Bhojendra', age: 37} // fine
}


Here PersonName is just the name property, ScopedPerson adds the age since that's only used interally in that function, and Person extends PersonName and adds address.

See Playground

  • Related