[Typescript] Make Typescript Stick

Refer: https://www.cnblogs.com/Answer1215/p/15084496.html

A string is a primitive value, and all primitive values are immutable.

Q1: 

const a = "Frontend Masters"
let b = "Frontend Masters"
 
const c = { learnAt: "Frontend Masters" }
let d = { learnAt: "Frontend Masters" }
 
const e = Object.freeze({ learnAt: "Frontend Masters" })

.

.

.

.

.

.

Answer:

All primitive values are immutable, therefore a & b are both hold immutable values.

Repeat "All primitive values are immutable" three times...

All primitive values are immutable

All primitive values are immutable

All primitive values are immutable

 

Const and Let differ in terms of whether variables can be reassigned, but that has nothing to do with whether the values they hold can be modified.

Therefore both c & d does NOT hod ummutable values

 

Object.freeze prevents properties of an object from being changed, and prevents new properties from being added. This effectively is a "Shallow immutability". Which means Object.freeze won't help you mutate a nested object.

const e = Object.freeze({ course: 'Typescript', learnAt: {place: "Frontend Masters"} })
e.course = "Reacct" // report error
e.learnAt.place = "Udmey" // works

 

Q2:

const str = "hello"
let val =
  /* ??? */
  console.log(val)
/**
 * {
 *   '0': 'h',
 *   '1': 'e',
 *   '2': 'l',
 *   '3': 'l',
 *   '4': 'o'
 * }
 */

.

.

.

.

.

.

Answer:

let val = { ...str.split("") }
console.log(val)
/**
 * {
 *   '0': 'h',
 *   '1': 'e',
 *   '2': 'l',
 *   '3': 'l',
 *   '4': 'o'
 * }
 */

 

Q3:

Look at the types of first and second below, as well as the compile error messages. What does your mental model tell you about how string and String are different?

let first: string & number
let second: String & Number
 
first = "abc"
Type 'string' is not assignable to type 'never'.
second = "abc"
Type 'string' is not assignable to type 'String & Number'. Type 'string' is not assignable to type 'Number'.
second = new String("abc")
Type 'String' is not assignable to type 'String & Number'. Type 'String' is missing the following properties from type 'Number': toFixed, toExponential, toPrecision

for the first: both string and number are primtive types, they are not such a value is both string and number at the same time, so that it results in a `never`.

for the second: String and Number are interface types. The Union of String and Number are common methods and props of both, but it won't results in a never type.

 

Q4:

In what order will the animal names below be printed to the console?

function getData() {
  console.log("elephant")
  const p = new Promise((resolve) => {
    console.log("giraffe")
    resolve("lion")
    console.log("zebra")
  })
  console.log("koala")
  return p
}
async function main() {
  console.log("cat")
  const result = await getData()
  console.log(result)
}
console.log("dog")
main().then(() => {
  console.log("moose")
})

.

.

.

.

.

.

.

Answer:

"dog" will come first

Then main() function will be invoked, so "cat" will come second.

Then getData() function will be invoked, so "elephant" will come third.

new Promise() constructor function will be invoked, 4th. "giraffe"

resolve() function will be invoked by the time we call .then()

so "zebra" comes 5th.

then "koala" comes 6th.

after await getData(), console.log(result) will run, so "lion" 7th.

"moose" will be last

  • Are you surprised that giraffe and zebra happen so early? Remember that Promise executors are invoked synchronously in the Promise constructor
  • Are you surprised that lion happens so late? Remember that a resolve is not a return. Just because a Promise has resolved, doesn’t mean the corresponding .then (or await is called immediately)

 

Does it compile?

let age = 38
age = Number.NaN

.

.

.

Answer: Yes

Number.NaN is still a number type

 

const vector3: [number, number, number] = [3, 4, 5]
vector3.push(6)

.

.

.

Answer: Yes

Unfortunately, because tuples are a specialized flavor of arrays (and at runtime, they actually are just regular arrays) they expose the entire array API. Look at the type signature of .push()

 

type Color = {
  red: number
  green: number
  blue: number
}
interface Color {
  alpha: number
}

.

.

.

Answer: No

Duplicate identifier 'Color'

But if both are interface type `Color`, then it will compile

 

class Person {
  name: string
  friends: Person[]
  constructor(name: string) {
    this.name = name
  }
}

.

.

.

Answer: NO

Property 'friends' has no initializer and is not definitely assigned in the constructor.

If you give a default value, then it will

class Person {
  name: string
  friends: Person[] = [];
  constructor(name: string) {
    this.name = name
  }
}

 

 

abstract class Person {
  public abstract name: string
}
class Student extends Person {
  public name: string | string[] = ["Mike North"]
}

.

.

.

Answer: No

Property 'name' in type 'Student' is not assignable to the same property in base type 'Person'. Type 'string | string[]' is not assignable to type 'string'. Type 'string[]' is not assignable to type 'string'.

Basicly you cannot loosen the types, from just `string` to `string | string[]`.

About abstract:

difference between abstract class and interface is interface only provide type information, but doesn't contain implementation

But abstract class can have some impelmentation as well.

 

 

interface Color {
  red: number
  green: number
  blue: number
}
function printColor(color: Color) {
  // ... //
}
printColor({
  red: 255,
  green: 0,
  blue: 0,
  alpha: 0.4,
})

.

.

.

Answer: NO

Object literal may only specify known properties, and 'alpha' does not exist in type 'Color'.

Because in printColor(color: Color), `color` won't be able to `alpha`, because TS has limit what you can get from Color type.

If you change the code to:

interface Color {
  red: number
  green: number
  blue: number
}
function printColor(color: Color) {
  // ... //
}

const c = {
  red: 255,
  green: 0,
  blue: 0,
  alpha: 0.4,
};
printColor(c)
console.log(c.alpha)

This will compile, because now we can access `alpha` from `c`.

 

 

type Color = {
  red: number
  green: number
  blue: number
}
class ColorValue implements Color {
  constructor(
    public red: number,
    public green: number,
    public blue: number
  ) {}
}

.

.

.

Answer: YES

The same code as:

type Color = {
  red: number
  green: number
  blue: number
}

class ColorValue implements Color {
  public red;
  public green;
  public blue;
  constructor(
     red: number,
     green: number,
     blue: number
  ) {
    this.red = red;
    this.green = green;
    this.blue = blue;
  }
}

 

 

 

export class Person {
  name: string = ""
}
interface Person {
  age?: number
}

.

.

.

Answer: NO

Individual declarations in merged declaration 'Person' must be all exported or all local.

Which means:

class Person {
  name: string = ""
}
interface Person {
  age?: number
}

// or

export class Person {
  name: string = ""
}
export interface Person {
  age?: number
}

That will compile.

And class Person and interface Person will be merged.

 

 

 

class Person {
  name: string
  constructor(userId: string) {
    // Fetch user's name from an API endpoint
    fetch(`/api/user-info/${userId}`)
      .then((resp) => resp.json())
      .then((info) => {
        this.name = info.name // set the user's name
      })
  }
}

.

.

.

Answer: NO

Property 'name' has no initializer and is not definitely assigned in the constructor.

The callback passed to .then is not regarded as a “definite assignment”. In fact, all callbacks are treated this way.

 

 

enum Language {
  TypeScript = "TS",
  JavaScript,
}
enum Editor {
  SublimeText,
  VSCode = "vscode",
}
enum Linter {
  ESLint,
  TSLint = "tslint",
  JSLint = 3,
  JSHint,
}

.

.

.

Answer: NO

Once you provide a string initializer for an enum member, all following enum members need an explicit initializer of some sort, unless you go back to numeric enum values, at which point inference takes over again.

 

function handleClick(evt: Event) {
  const $element = evt.target as HTMLInputElement
  if (this.value !== "") {
    this.value = this.value.toUpperCase()
  }
}

.

.

.

Answer. NO

When you have a free-standing function like this, and refer to the this value, we need to give it a type of some sort.

'this' implicitly has type 'any' because it does not have a type annotation.

In React, we often do `this.value.bind(this)`

or we can fix like this:

function handleClick(this: HTMLInputElement, evt: Event) {
  const $element = evt.target as HTMLInputElement
  if (this.value !== "") {
    this.value = this.value.toUpperCase()
  }
}

 

 

 

class Person {
  #name: string
  private age: number
  constructor(name: string, age: number) {
    this.#name = name
    this.age = age
  }
}
class Student extends Person {
  #name: string | string[]
  private age: number
  constructor(name: string, age: number | null) {
    super(name, age || 0)
    this.#name = name
    this.age = age
  }
}

.

.

.

Answer: NO

Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.

Because TS private fields are just “checked for access at build time” and are totally accessible outside the class at runtime, there’s a collision between the two age members.

As a result Student is not a valid subclass of Person

Class 'Student' incorrectly extends base class 'Person'. Types have separate declarations of a private property 'age'.

So what if you change to:

class Person {
  #name: string
  #age: number
  constructor(name: string, age: number) {
    this.#name = name
    this.#age = age
  }
}

class Student extends Person {
  #name: string | string[]
  #age: number

  constructor(name: string, age: number | null) {
    super(name, age || 0)
    this.#name = name
    this.#age = age
  }
}

For extends class, they cannot have the same private field, but can have both public or #private field

.

.

.

Answer: NO

 

 

 

class Person {
  #name: string
  constructor(name: string) {
    this.#name = name
  }
}
 
function makeName(name: string | string[]): string {
  if (Array.isArray(name)) return name.join(" ")
  else return name
}
 
class Student extends Person {
  #name: string | string[]
 
  constructor(name: string | string[]) {
    super(makeName(name))
    this.#name = name
  }
}

.

.

.

Answer: YES
Because Ecma #private fields are not visible, even at runtime, outside of the class, there’s no collision between the two #name members.

posted @ 2022-07-13 18:53  Zhentiw  阅读(210)  评论(0编辑  收藏  举报