9

Summarize knowledge points

The need to use ts for vue3 may be the biggest obstacle for friends who have not started playing vue3. After 1 year of vue3+ts development, I have summarized the ts knowledge points that must be learned (the content is only 1/4 of the official website content), which is convenient for everyone to learn Check it out when vue3, to ensure that everyone has learned this article to easily play vue3 development.

🌕Wish everyone a happy Mid-Autumn Festival

vue3 basics

If you haven't started contacting vue3, you can also watch the vue3 basic tutorial (with video) .

Basic data type

You can test the code in the course at here , and you can try to see what the js compiled by ts looks like.
image.png
https://www.typescriptlang.org/zh/play

Type annotation

In typescript, we can mark the type of the variable. In the subsequent code, ts will automatically check whether the variable has an incorrect read/write operation .

let is:boolean;
is =  true;
is =  123; // 报错, 提示number类型不能赋值给boolean类型的变量

The syntax is very simple, just add " : " and type after the variable, this action is called type annotation.

Automatic type inference

If the value of the variable is a literal, ts can automatically infer the type.

let is = false;
is = true;
is =  123; // 报错, 提示number类型不能赋值给boolean类型的变量

let o = {a:1,b:'2'}
o.a ='33' // 报错, 不能吧string分配给number

Literal

The literal quantity is the form of the data. We know the content of the data when we see it, such as the data on the right side of "=" above. The concept itself is very simple, but I am afraid that students who hear the word for the first time will have doubts.

Other cases

There are several situations where ts will automatically infer the variable type, which we will discuss in the next lesson.

Basic data type

Next, let's take a look at what data types are available in the system.

boolean

Boolean type.

let is:boolean;
is =  true;
is =  123; // 报错, 提示is是数字不能赋值给boolean类型的变量

number

Number type, not only supports decimal system.

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
decLiteral = '123'; // 报错

string

String type.

let s1:string = 'hello world!'; 
let s2:string = 'hello ${name}`;
s1 = 123 // 报错

Array

There are 2 ways to represent the array type.

"Type+[]"
let numbers:number[] = [1,2,3,4,5];

// number|string代表联合类型, 下面的高级类型中会讲
let numbers:(number|string)[] = [1,2,3,4,'5'];
Array<type>
let numbers:Array<number> = [1,2,3,4,5];

Tuple

tuple type represents a known element number and type of array , not necessarily the same types of elements:

let list1:[number, string] = [1, '2', 3]; // 错误, 数量不对, 元组中只声明有2个元素
let list2:[number, string] = [1, 2]; // 错误, 第二个元素类型不对, 应该是字符串'2'
let list3:[number, string] = ['1', 2]; // 错误, 2个元素的类型颠倒了
let list4:[number, string] = [1, '2']; // 正确

Enum

in ts but not in js. After compilation, it will be converted into object . The value of the default element starts from 0, for example, the value of Color.Red below is 0, and so on. Color.Green Is 1, Color.Blue is 2:

enum Color {Red, Green, Blue}
// 等价
enum Color {Red=0, Green=1, Blue=2}

We can also pass the value backward to get the key:

enum Color {Red=1, Green=2, Blue=4}
Color[2] === 'Green' // true

Look at the enumeration code compiled into js, you will understand why the key value can be obtained in reverse:

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
// Color的值为: {0: "Red", 1: "Green", 2: "Blue", Red: 0, Green: 1, Blue: 2}

any (any type)

Any stands for any type, that is, if you don't know what type of variable is, you can mark it with any. For example, if you introduce some older js libraries without declaring the type, you can mark it as any type when you use it, so ts There will be no errors. Of course, you can't use any everywhere, so ts will have no meaning to use.

void

The meaning of void any , which means it is not of any type, and generally appears in the function to mark that the function has no return value:

function abc(n:number):void{
    console.log(n);
}

The void type corresponds to two values, one is undefined and the other is null:

const n1:void = undefined;
const n2:void = null;

null and undefined

By default, null and undefined are subtypes of all types, such as:

const n1:null = 123;
const n2:undefined = '123';

never

never means unreachable, used in the case of throw:

function error():never{
    throw '错了!';
}

unknown

unknown represents an unknown type.The same place as any is that the marked variable can be assigned to , but when accessing the attributes on the variable, the type of the variable must be asserted before the operation can be operated.

let a:unknown;
a = '汉字';
a.length // 提示类型为unknown, 不能访问length
Type assertion (as or <>)

When operating, I can force ts to tell ts what type the variable is now. For example, the variable a above is unknown and we cannot operate on it in ts. At this time, we need to make a "type assertion":

let a:unknown;
a = '汉字';

(a as string).length

// 等价写法
(<string>a).length

The two grammatical effects are the same. I personally recommend that you use the first one, which makes writing easier.

symbol

let s:symbol;
s = Symbol('a');
s = 1 //报错 

object

Object represents a non-primitive type, i.e. addition Number / String / Boolean / Symbol / null / undefined except the type:

let o1:object = [];
let o2:object = {a:1,b:2};

However, our basically does not use the object type, because the type it annotates is not specific. Generally, interface used to annotate more specific object types. In the next lesson, we will talk about "how to define an interface".

Automatic type inference (type protection)

In the previous section, we said that ts can infer variable types from "literals". This "automatic inference" ability of ts is officially called " type protection ".

let o = {a:1,b:'2'} // 可以识别对象中的字段和类型
o.a ='33' // 报错, 不能吧string分配给number

In this section, we continue to introduce other situations that trigger automatic inference.

typeof

Determine the type.

let n:number|string = 0.5 < Math.random()? 1:'1';

// 如果没有typeof, n*=2会报错, 提示没法推断出当前是number类型, 不能进行乘法运算
if('number' === typeof n) {
    n*= 2;
} else  {
    n= '2';
}

Typeof

The "typeof" mentioned before is the api in js, in fact, there is also the "typeof" keyword in ts, which can extract the type of js object.

function c(n:number){
  return n;
}

// typeof c获取函数c的类型
function d(callback :typeof c){
  const n = callback(1);
  console.log(n);
}

In the vscode prompt, we can see that ts has correctly deduced the type of "typeof c".
image.png

instanceof

Determine whether it is an instance.

let obj = 0.5 < Math.random() ? new String(1) : new Array(1);

if(obj instanceof String){
    // obj推断为String类型
    obj+= '123'
} else {
    // obj为any[]类型
    obj.push(123);
}

in

Determine whether there is a field.

interface A {
  x: number;
}

interface B {
  y: string;
}

function ab(q: A | B) {
  if ('x' in q) {
    // q: A
  } else {
    // q: B
  }
}

Custom type protection (is)

Before, we all used the keywords that came with the system to trigger "type protection", let's customize one:

  1. First we need to define a function to judge and return the boolean value.
  2. The return value should not be marked with boolean directly, but "is". The front of is is usually the parameter, and the latter is the type after judgment.
  3. For example, in the "isBird" function below, if the return value is true, then "animal" is Bird.
  4. When using "isBird" with "if", automatic type inference will be triggered.

    interface Animal {
     name: string;
    }
    
    interface Bird {
     name: string;
     hasWing: boolean;
    }
    
    // 关键看函数返回值的类型标注
    function isBird(animal:Animal): animal is Bird {
     return animal.name.includes('雀');
    }
    
    
    // 使用isBird对触发自动类型推断
    const who = { name: '金丝雀' };
    if (isBird(who)) {
     who.hasWing = true;
    }

"is" is used in vue3 source code

Just understand.

// 是否是对象
export const isObject = (val: any): val is Record<any, any> =>
  val !== null && typeof val === 'object'

// 是否ref对象
export function isRef(v: any): v is Ref {
  return v ? v[refSymbol] === true : false
}

// 是否vnode
export function isVNode(value: any): value is VNode {
  return value ? value._isVNode === true : false
}

// 是否插槽节点
export const isSlotOutlet = (
  node: RootNode | TemplateChildNode
): node is SlotOutletNode =>
  node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT

interface

A format for defining complex types, such as defining object type :

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite: string;
}

const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
    fromSite: 'baidu.com'
}

// 报错不存在titleA字段, 同时缺少title/count/content/fromSite
const article: Article = {
    titleA: '为vue3学点typescript(2), 类型',
}

When we assign a value to article, if fields are not assigned or the field corresponds to the data type of , ts will prompt an error to avoid misspelling or omission of the field name.

Scalability

Sometimes the field of the object may be allowed to be unknown, for example, Article may also have "a" field and "b" field. In order to be compatible with this situation, we modify it:

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite: string;
      [k:string]:string;
}

Through "[k:string]:string" we allow any string key value to appear in the Article type, as long as the content is also a string.

Not required (?)

Use "?" to mark the fromSite field as not required.

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

// 缺少fromSite字段也不会报错
const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
}

Read only

The field marked with "readonly" cannot be modified.

interface Article {
    readonly title: string;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

let a:Article =  {title:'标题',count:1,content:'内容',}
a.title = 123; // 报错, title是只读属性

Define function

// 声明接口
interface Core {
    (n:number, s:string):[number,string]
}

// 使用接口进行标注
const core:Core = (a,b)=>{
    return [a,b];
}

Functions are also objects, so the interface Core can also declare properties.

interface Core {
    (n:number, s:string):[number,string];
        n:number;
}

const core:Core = (a,b)=>{
    return [a,b];
}
core.n = 100;

Definition class

The constraint class uses the "implements" keyword.

// 定义
interface Animal {
    head:number;
    body:number;
    foot:number;
    eat(food:string):void;
    say(word:string):string;
}

// implements
class Dog implements Animal{
    head=1;
    body=1;
    foot=1;

    constructor(head:number){
        this.head = head;
    }
    eat(food:string){
        console.log(food);
    }
    say(word:string){
        return word;
    }
}

By comparison, it can be found that the interface part specifies the instance attributes of the class, and the constructor is not defined. Define the constructor:

inherit

Let one interface have all the characteristics of another interface.

interface Animal {
    color: string;
}

interface Cat extends Animal {
    foot: number;
}

let cat:Cat;
cat.color = "blue";
cat.foot = 10;

merge

Interfaces with the same name will be merged automatically.

interface Apple {
    color: string;
}
interface Apple {
    weight: number;
}

Equivalent to

interface Apple {
    color: string;
    weight: number;
}

Type alias (type)

You can use the "type" keyword to change the name of an existing type.

type N = number;
type S = string;

interface F1{
    (a:number):string;
}
type F2 = F1;

At first glance, you must think this is not very useful, and it is possible to define the function "interface", so let's talk about its special features.

Define function

As mentioned earlier, interfaces can define functions, and types can also be used, but the syntax is a bit different:

interface F1{
    (a:number):string;
}

type F2 = (a:number)=>string;

"F1" and "F2" define the same type, the difference between the main parameter and the return value, one is ":" and the other is "=>".

Define literal type

This is what "type" can do but "interface" can't.

type N = 1|2|3;
type S = 'a'|'b'|'c';

// 正确
const n1:N = 1;
// 错误, 类型N中没有11;
const n2:N = 11

Union type (|)

Connect multiple types with " | " to indicate the "or" relationship between the types.

type F1 = (a:string|number)=>any;
const f1:F1 = (a)=>a;

// 正确
f1(1);
f1('A');

// 错误, 参数类型应该是string或number
f1({a:1});
interface Cat  {
      hand:string;
    foot: string;
}

interface Duck{
    body:string;
}

type Animal  = Cat|Duck;

// 错误, Cat没有body字段
const animal:Cat = {hand:'手',body:'身体',foot:'脚'};
// 正确
const animal:Animal = {hand:'手',body:'身体',foot:'脚'};

Cross type (&)

Connect multiple types with " & ", and take the union of the types.

interface A {a:number};
interface B {b:string};
type AB = A & B;

const a:A = {a:1};
const b:B = {b:'1'};
// 错误, 缺少a字段
const ab1:AB = {b:'2'};
// 正确
const ab2:AB = {a:1,b:'2'};

Generic

Generics can be understood as using "undefined types" to describe classes/functions/interfaces, and then determine the specific types when using them. We call this "undefined type" type variable , which can represent any type , Generally we use capital letters to indicate the type, such as "T" or "U".

function echo<T>(input:T):T{
    return input;
}

Generic function

In the above we define a "generic function" , first define the type variable T, surrounded by "<>", and then mark the type of the parameters and return value of the .
Although the parameter "input" can be of any type when using "echo", but different from the type marked "any", the return value of the function here will change synchronously according to the change of the parameter type.

// n1是number类型
const n1 = echo<number>(1);

// s会推断为string类型
const s = echo<string>('1') 

If the parameter is literal, then can omit the preceding "<number>" and "<string>" when using the function, and ts can automatically infer the type of the parameter, thereby inferring the value of the type variable T:

// n1是number类型
const n1 = echo(1);

// s会推断为string类型
const s = echo('1') 

Generic class

Declare a type variable U with "<>" after the class name. The methods and properties of the class can use this U type.

class Person<U> {
    who: U;
    
    constructor(who: U) {
        this.who = who;
    }

    say(code:U): string {
        return this.who + ' :i am ' + code;
    }
}

Next, we use the next generic class :

let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 错误, 会提示参数应该是个string
a.say('007') // 正确

We specify the type variable as (string) and tell ts that the U of this class is of string type. Through the definition of Person, we know that the parameter of the say method is also of string type, so a.say(007) will report an error because 007 is a number. So We can pass in type to constrain the generic .

Generic method

It is defined in the same way as a generic function:

class ABC{
    // 输入T[], 返回T
    getFirst<T>(data:T[]):T{
        return data[0];
    }
}

Generic type

We can use the type variable to describe a type. can be implemented by combining type and interface:

type

type A<T> = T[];
// 正确
const a: A<number> = [1,2,3]
// 错误
const b: A<number> = ['1','2','3'];

Generic interface (interface)

interface Goods<T>{
    id:number;
    title: string;
    size: T;
}

// 正确
let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};
let shoes:Goods<number> = {id:1,title: '苹果', size: 43};

Defaults

type A<T=string> = T[];
// 正确
const a  = ['1','2','3'];

interface Goods<T=string>{
    id:number;
    title: string;
    size: T;
}
// 正确
let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};

Generic constraints

function echo<T>(input: T): T {
    console.log(input.name); // 报错, T上不确定是否由name属性
    return input;
}

As mentioned earlier, T can represent any type, but not all types have the " name " field, and the range of "T" can be restricted extends

// 现在T是个有name属性的类型
function echo<T extends {name:string}>(input: T): T {
    console.log(input.name); // 正确
    return input;
}

Multiple type variables

You can use multiple type variables at the same time.

function test<T,U>(a:T,b:U):[T,U]{
    return [a,b];
}

Don't abuse generics

Generics are mainly for constraints, or to narrow the range of types. If you can't restrict the function, it means that you don't need to use generics:

function convert<T>(input:T[]):number{
    return input.length;
}

There is no point in using generics in this way, and it is no different from the any type.

Tool type

There are many tool types preset in the system.

Partial<T>

Make attributes optional

type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}

Required<T>

Make all attributes mandatory.

type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}

Pick<T,K>

Only keep the attributes you choose, K represents the key value of the attribute you want to keep

type A  = Pick<{a:number,b:string,c:boolean}, 'a'|'b'>
type A1 = Pick<A, 'a'|'b'> //  {a:number,b:string}

Omit<T,K>

The implementation excludes the selected attributes.

type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}

Record<K,T>

Create a type, K represents the type of key value, T represents the type of value

type A1 = Record<string, string> // 等价{[k:string]:string}

Exclude<T,U>

Filter the same (or compatible) types in T and U

type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number

// 兼容
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉

Extract<T,U>

Extract the same (or compatible) type in T and U.

type A  = {a:number, b:string}
type A1 = Extract<number|string, string|number[]> // string

NonNullable

Remove undefined and null in T.

type A1 = NonNullable<number|string|null|undefined> // number|string

ReturnType

Get the type of the return value of T.

type A1= ReturnType<()=>number> // number

InstanceType

Return the instance type of T

There are two types of classes in ts, the type of the static part and the type of the instance, so T is a constructor type, then InstanceType can return his instance type:

interface A{
    a:HTMLElement;
}

interface AConstructor{
    new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{
    return new AClass();
}

Parameters

Get the function parameter type, the return type is the primitive ancestor, and the order of the elements is the same as the order of the parameters.

interface A{
    (a:number, b:string):string[];
}

type A1 = Parameters<A> // [number, string]

ConstructorParameters

Get the parameter type of the constructor, Parameters , except that T is the constructor type here.

interface AConstructor{
    new(a:number):string[];
}

type A1 = ConstructorParameters<AConstructor> // [number]

More

In fact, you can "node_modules/typescript/lib/lib.es5.d.ts" . With the update of ts, there may be more types here.

image.png

Custom tool type

In the last lesson, we learned a lot of the tool types preset by the system, and then we learn to implement the tool types ourselves.

keyof

First look at the source code of Partial preset by the system.

type Partial<T> = {
    [P in keyof T]?: T[P];
};

Here are two new keywords " keyof " and " in ", " keyof " is used to take all the key values of the object, for example:

type A = keyof {a:string,b:number} // "a"|"b"

in

Used to traverse the union type.

type A = "a"|"b"
type B = {
    [k in A]: string;
}
// B类型:
{
    a: string;
    b: string;
}

extends and trinocular operations

Before we " generic constraints" that section of " the extends " used to constrain the range of types, we combine the conditions where three types of operations to achieve entry determination.
The syntax is:

type A = T extends U ? X : Y;

If the range of U is greater than T, for example, U has a field in T, then the value of A is X, otherwise it is Y.

type T1 = {a:number,x:number};
type U1 = {a:number,b:number};
type A1 = T1 extends U1 ? number : string; // string

type T2 = {a:number,b:number};
type U2 = {a:number};
type A2 = T2 extends U2 ? number : string; // number

The "Exclude" in the system preset type is also implemented in the same way:

type Exclude<T, U> = T extends U ? never : T;

This means that if you filter out the types in T and U:

type T1 = number|string;
type U1 = number|string[];
type A = Exclude<T1,U1> // string

infer (type inference)

The word itself means "inference", here it means that declares to be inferred type variable in the extends conditional statement. Let’s take a look at the default type Parameters:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

In the above declaration, a P is used to indicate the possible types of...args. If the range of (...args: infer P) is greater than T, then the type corresponding to...args is returned, that is, the parameter type of the function, otherwise it returns never.
Note: The T extends (...args: any) => any at the beginning of

Application infer

Next, we use infer to implement "delete the first element in the primitive type":

export type Tail<T extends unknown[]> = T extends [a: any, ...args: infer P] ? P : never

type A = Tail<[number,string,null]> // [string,null]

Type assertion (as)

In some cases, the system cannot automatically infer the correct type, so we need to mark it ourselves:

document.body.addEventListener('click', e=>{
    e.target.innerHTML = 'test'; // 报错, 因为e.target的类型是EventTarget, 其上没有innerHTML
    (e.target as HTMLElement).innerHTML = 'test';// 正确
});

Because the " addEventListener " method is not only supported by the dom element, the type description of the system " e.target " cannot be directly marked as " HTMLElement ".

However, when using the above code, we know e.target is dom elements, so we need to tell ts: "This is dom", which is " type assertion", syntax is "AS", also Use " <> " to express:

document.body.addEventListener('click', e=>{
    (e.target as HTMLElement).innerHTML = 'test';
      // 等价
      (<HTMLElement>e.target).innerHTML = 'test';
});

Application in axios

Axios is the most commonly used http request library for development. For the data we request asynchronously, its type must be unknown, so we need to mark the type:

axios.get<{n:number}>('/xx').then(data=>{
    console.log(data.data.n+1);
});

Note that we passed in "type parameters" after the "get" method, indicating that the get method used "generic" when it was defined. The type passed in "<>" indicates the type of the returned data, which is equivalent here in label return type .

But if you use the "axios interceptor" to change the return value, it is not easy to mark it here, for example:


http.interceptors.response.use(function (response) {
   // 让返回数据值返回data字段的值
   return data.data;
}, function (error) {
    return Promise.reject(error);
});

Since we modify the return value, but axios does not know that his own return value has changed, so it is definitely not enough to mark the return value type as above, then we can use type assertion:

axios.get('/xx').then(data=>{
    console.log((data as {n:number}).n+1);
});

Enumeration (Enum)

Enumeration is typescript's own data type, javascript does not have this type , but there is no compatibility problem, because enumeration will be compiled into js code after compilation.

uses the "enum" keyword to define enumerations, can have the format of enum and Class very similar, and the members are assigned with "=" :

enum Direction {Up, Right, Down, Left};

Look at the compiled js code:

var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Right"] = 1] = "Right";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
})(Direction || (Direction = {}));

From here we can see that the values and keys of the enumeration members are treated as the key value of the object for two-way assignment. In fact, we are accessing this object, so the enumeration supports two-way access :

Direction[0] // 'Up'
Direction.Up // 0

Application scenario

It is mainly used for the definition of constants. The enumeration name is equivalent to a scope, which is more readable. At the same time, ts's type inference is clearer.

Value increment

If the value of the enumeration member is not specified, then the default value is incremented from 0:

Direction.Up // 0
Direction.Right // 1
Direction.Down // 2
Direction.Left // 3

If a member is assigned a number, then increment from that number:

enum Direction {Up=5, Right, Down=10, Left};
Direction.Up // 5
Direction.Right // 6
Direction.Down // 10
Direction.Left // 11

The value can only be number and string

// 正确
enum Direction {Up = '上', Right = 1};

// 错误
enum Direction {Up = ['上]', Right = {r:1}};

It can also be a calculated value

If the expression is a number or string, it can also be used as the value of an enumeration member:

function greenValue(){
    return 2;
}

enum Color {Red = 1, Green = greenValue(), Blue='aaa'.length};

But pay attention: if a member has no value, then the member before him must be a literal value:

function greenValue(){
    return 2;
}
// 错误, Blue要求Green是个字面量的数字或字符串
enum Color {Red = 1, Green = greenValue(), Blue};

// 没问题, 首个成员前面没有成员, 所以不受上面的限制
enum Color1 {Red, Green = greenValue(), Blue='aaa'.length}; 

Constant enumeration (const)

Use "const enum" to declare constant enumeration,

const enum Enum {
  A = 1,
  B = A * 2,
}

Enum.A
Enum.B

Different from ordinary enumeration, after the constant enumeration is compiled into js, will not generate extra code :

// 编译后的结果
"use strict";
1 /* A */;
2 /* B */;

In development, I personally prefer to use constant enumeration, because it does not generate redundant code, and at the same time guarantees good semantics.

What is a declaration file

If the js file is introduced in the ts project, then you need to write a "declaration file" to tell ts to introduce the variable type in the js file.

Application scenario

  1. If a third-party js library is introduced into html, such as "jq", then "**declaration**" is required to use the "$" variable in the ts code, because ts does not know the existence of $.
  2. Supplement the missing (or custom) fields on the global object "window/doucument" etc.
  3. The js package in the existing npm, supplement its type declaration.
  4. Expand the types of ts packages that already exist on npm.For example, the $xx attribute added to the vue instance needs to be declared in ts before it can be used.

    for example

    The implementation code for the first case is as follows:

    // global.d.ts
    declare var $: (selector: string) => any;

    Here "declare" is the keyword of the declaration, and other scene examples will be explained in later lessons.

    Declaration file on npm

    For common js libraries like jq, someone has actually written the declaration file and published it in npm. For example, the package of the declaration file of jq is "@types/jquery", and the declaration file of lodash is "@types/lodash" , If the package you installed through npm does not have a declaration file, you can execute the corresponding installation command:

    npm i @types/xx

    File location

    The declaration file is generally placed in root directory of the project, so that the ts file of the entire project can read the declared type, and the naming format is "xx.d.ts".
    image.png

    Global interface / type

    We said above that we are adding type declarations to variables. In fact, we can also add interfaces and types in "global.d.ts". After adding, the interfaces will become global, so that they can be read in any file in the project. The type.

    // global.d.ts
    interface Abc{
     n:number
    }
    // index.ts
    const abc:Abc = {n:100};

    Introduce(///)

    If you want to reuse existing types, we can introduce them through the "///" syntax:

    // test.d.ts
    type ABC = number;
    // global.d.ts
    /// <reference path="./test.d.ts" />
    declare const a:ABC // number

    Now that we have a preliminary understanding of the declaration file, we will learn to write the declaration file by ourselves in the next section.

Declaration of global variables

For the declaration of global variables, we use the declare ". Once again, the declaration files are usually placed in the root directory of the project, and they are named "global.d.ts" for semantics.

The following shows the declaration of various types of data.

declare var

Declare global variables

// global.d.ts
declare var n:number;
declare let s:string;
declare const version:string;

The "var" here can also be "let" or "const", the semantics are the same as in js, and it can be determined by yourself according to the situation.

declare function

Declare global functions

// global.d.ts
declare function translate(words: string): string;

declare enum

Declare a global enum

// global.d.ts
declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

declare class

Declare the global class

// global.d.ts
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}

declare namespace

Declaring a namespace can be understood as declaring an object.

// global.d.ts
declare namespace req {
    function get(url: string, params?: Record<string,any>): Promise<any>;
}

Expand global variables

If the global variable is expanded, then the expanded field needs to be declared. For example, add a cut method to "String".

String.prototype.cut = function(n){
    return this.substr(0,n);
};

'12345'.cut(3); // 123

Corresponding declaration file:

// global.d.ts
interface String {
  // 自定义字段
  cut(s:string): string;
}

Because the "String" type is written in the declaration file that comes with the ts system, it is described by the interface itself. Remember that the interface type will automatically merge . Here, write an interface with the same name to implement type merging.
image.png
[
](https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts#L394)

System declaration file

The api of js has type declaration in ts, we can see all the system declaration files node_modules/typescript/lib/
image.png
can also be viewed on github

API with browser prefix

I have observed that the system’s own statement does not declare the API with "browser prefix". If we write our own plug-in, we need to add this part, such as the "requestFullScreen" api:

// global.d.ts
interface HTMLElement {
    webkitRequestFullscreen(options?: FullscreenOptions): Promise<void>;
    webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
    msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
    mozRequestFullScreen(options?: FullscreenOptions): Promise<void>;
}

Source code: https://github.com/any86/be-full/blob/master/global.d.ts

Expand the type of npm module (declare module)

The "package" downloaded by npm comes with a declaration file. If we need to expand its type declaration, we can use the " declare module " syntax.

Let vue3 support this.$axios

// main.ts
app.config.globalProperties.$axios = axios;

Functionally, we have implemented "this.$axios", but ts cannot automatically infer that we have added the $axios field, that is, using this.$axios in the component will prompt the lack of type, so add the following declaration file:

// global.d.ts

import { ComponentCustomProperties } from 'vue'

// axios的实例类型
import { AxiosInstance } from 'axios'

// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module '@vue/runtime-core' {
  
  // 给`this.$http`提供类型
  interface ComponentCustomProperties {
    $axios: AxiosInstance;
  }
}

Here, expand the " ComponentCustomProperties " interface, because it is the type of the instance property in vue3.

More comprehensive example

In the above example, we expanded the interface in the original declaration, but if the export is a Class, how should we write it? Next, we expand the type of "any-touch", where the default export of "any-touch" is a Class Suppose we make the following changes to the "any-touch" code:

  1. Export adds "aaa" variable, which is of string type.
  2. The instance of the class adds the "bbb" attribute, which is of type number.
  3. The class adds a static attribute "ccc", which is a function.

    // global.d.ts
    
    // AnyTouch一定要导入, 因为只有导入才是扩充, 不导入就会变成覆盖.
    import AnyTouch from 'any-touch'
    
    declare module 'any-touch' {
     // 导出增加"aaa"变量, 是个字符串.
     export const aaa: string;
         
     export default class {
       // 类增加静态属性"ccc", 是个函数.
       static ccc:()=>void
       // 类的实例增加"bbb"属性, 是number类型.
       bbb: number
     }
    }

    Note : AnyTouch must be imported, because only import is type expansion, and if it is not imported, it will become an overwrite.

Under the test, the types have been added correctly:

// index.ts
import AT,{aaa} from 'any-touch';

const s = aaa.substr(0,1);

const at = new AT();
at.bbb = 123;

AT.ccc = ()=>{};

Type expansion of non-ts/js file modules

ts only supports the import and export of modules, but sometimes you may need to import css/html and other files. At this time, you need to use wildcards to make ts treat them as modules. The following is the import support for ".vue" files (from vue official) :

// global.d.ts
declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
// App.vue
// 可以识别vue文件
import X1 from './X1.vue';
export default defineComponent({
    components:{X1}
})

Declare that the vue file is used as a module, and at the same time mark that the default export of the module is the "component" type. In this way, the module can be correctly identified by registering the module in the components field of Vue.

vuex

The following is officially provided by vuex. Declare the addition of the $store attribute on the vue instance. With the previous knowledge, it should be easy to see this.

// vuex.d.ts

import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'

// 声明要扩充@vue/runtime-core包的声明
declare module '@vue/runtime-core' {
  // declare your own store states
  interface State {
    count: number
  }

  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

Not all

So far the content of the statement is over, but in fact there are some "module declarations" that are not covered, because the ultimate purpose of this course is based on the development of vue3 and does not involve the development of npm packages, so the other content is not Expanded, students in need can read the ts document to learn, with the basis of this article, I believe you will easily learn more.

WeChat group

Thank you for reading, if you have any questions, you can add me to WeChat, I will pull you into the WeChat group (Because Tencent has a limit of 100 people in WeChat groups, after more than 100 people must be pulled in by group members)

github

My personal open source is based on ts, welcome everyone to visit https://github.com/any86

image.png


铁皮饭盒
5k 声望1.2k 粉丝

喜欢写程序: [链接]