I have used TypeScript for a year, and the technology used in the project is Vue + TypeScript. I deeply feel the necessity of TypeScript in medium and large-scale projects, especially in large-scale projects with a long life cycle, TypeScript should be used.
The following are the TypeScript tricks that I often use in my work.
1. Notes
The TS type can be marked with a comment in the /** */
form of , and the editor will have a better prompt:
/** This is a cool guy. */
interface Person {
/** This is name. */
name: string,
}
const p: Person = {
name: 'cool'
}
This is a good way to add comments or friendly hints to a property.
2. Interface inheritance
Like classes, interfaces can also inherit from each other.
This allows us to copy members from one interface to another, allowing for more flexibility in splitting interfaces into reusable modules.
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
An interface can inherit multiple interfaces to create a composite interface of multiple interfaces.
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
3. interface & type
There are two ways to define types in TypeScript: interfaces and type aliases.
For example, in the following example of Interface and Type alias, except for the syntax, the meaning is the same:
Interface
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
Type alias
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
And both are extensible, but the syntax is different. Also, note that interfaces and type aliases are not mutually exclusive. Interfaces can extend type aliases and vice versa.
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
The difference between them can be seen in the picture below or in TypeScript: Interfaces vs Types.
So it is not easy to use interface & type skillfully.
If you don't know what to use, remember: use interface if you can, use type if you can't.
4. typeof
typeof
Operators can be used to obtain the type of a variable or object.
We generally define the type first, and then use:
interface Opt {
timeout: number
}
const defaultOption: Opt = {
timeout: 500
}
Sometimes it can be reversed:
const defaultOption = {
timeout: 500
}
type Opt = typeof defaultOption
When an interface always has a literal initial value, this way of writing can be considered to reduce repeated code, at least two lines of code, right, haha~
5. keyof
TypeScript allows us to iterate over properties of a certain type and extract the name of its property via the keyof operator.
The keyof operator, introduced in TypeScript 2.1, can be used to get all keys of a certain type, and its return type is a union type.
keyof
Object.keys
Similar to , except that keyof
takes interface
the key of .
const persion = {
age: 3,
text: 'hello world'
}
// type keys = "age" | "text"
type keys = keyof Point;
When writing a method to get the property value in the object, most people may write it like this
function get1(o: object, name: string) {
return o[name];
}
const age1 = get1(persion, 'age');
const text1 = get1(persion, 'text');
But it will show an error
Because there is no pre-declared key in the object.
Of course, if you o: object
change to o: any
It will not report an error, but the obtained value will have no type, and it will become any.
At this time, you can use keyof
to strengthen get
the type function of the function. Interested students can look at _.get
the type
mark and implementation of
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
6. Find Type
interface Person {
addr: {
city: string,
street: string,
num: number,
}
}
When the type of addr needs to be used, in addition to the type
interface Address {
city: string,
street: string,
num: number,
}
interface Person {
addr: Address,
}
is acceptable
Person["addr"] // This is Address.
for example:
const addr: Person["addr"] = {
city: 'string',
street: 'string',
num: 2
}
In some cases the latter makes the code cleaner and easier to read.
7. Find Type + Generics + keyof
Generics (Generics) refers to a feature that does not specify a specific type in advance when defining a function, interface or class, but specifies the type when it is used.
interface API {
'/user': { name: string },
'/menu': { foods: string[] }
}
const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
return fetch(url).then(res => res.json());
}
get('');
get('/menu').then(user => user.foods);
8. Type Assertion
In Vue components, ref is often used to obtain the properties or methods of subcomponents, but it is often impossible to infer what properties and methods exist, and an error will be reported.
Subassembly:
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
props: {
msg: String,
},
})
export default class HelloWorld extends Vue {
msg!: string;
}
</script>
Parent component:
<template>
<div class="home">
<HelloWorld
ref="helloRef"
msg="Welcome to Your Vue.js + TypeScript App"
/>
</div>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
@Options({
components: {
HelloWorld,
},
})
export default class Home extends Vue {
print() {
const helloRef = this.$refs.helloRef;
console.log("helloRef.msg: ", helloRef.msg);
}
mounted() {
this.print();
}
}
</script>
Because this.$refs.helloRef
is an unknown type, an error message will be reported:
Just make a type assertion:
print() {
// const helloRef = this.$refs.helloRef;
const helloRef = this.$refs.helloRef as any;
console.log("helloRef.msg: ", helloRef.msg); // helloRef.msg: Welcome to Your Vue.js + TypeScript App
}
But type any
assertion is not good. If you know the specific type, it is better to write the specific type, otherwise it seems pointless to introduce TypeScript.
9. Explicit Generics
$('button') is a DOM element selector, but the type of the return value can only be determined at runtime. In addition to returning any, you can also
function $<T extends HTMLElement>(id: string): T {
return (document.getElementById(id)) as T;
}
// 不确定 input 的类型
// const input = $('input');
// Tell me what element it is.
const input = $<HTMLInputElement>('input');
console.log('input.value: ', input.value);
Functional generics don't necessarily have to automatically deduce the type, sometimes it's better to specify the type explicitly.
10. DeepReadonly
readonly
Properties marked with can only be assigned at declaration time or in the class constructor.
It will then be immutable (ie read-only property), otherwise a TS2540 error will be thrown.
const
Very similar to in ES6 , but readonly
can only be used on properties in classes (or interfaces in TS), which is equivalent to getter
a setter
syntactic sugar for properties that only have no .
The following implements readonly
a type:
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
const a = { foo: { bar: 22 } }
const b = a as DeepReadonly<typeof a>
b.foo.bar = 33 // Cannot assign to 'bar' because it is a read-only property.ts(2540)