TypeScript 入门

课程地址

ts 开发环境搭建

npm i -g typescript

查看安装位置:

$ npm root -g
C:\Users\Daniel\AppData\Roaming\npm\node_modules

创建 hello.ts

console.log("hello, ts");

编译 ts 文件,得到 js 文件:

$ tsc foo.ts

类型声明

后置的类型声明

let a: number;

a = 10;
a = 33;
// a = "hello";

let b: string = "hello";
let c = true;   // Automatic type inference

function sum(a: number, b: number): number {
    return a + b;
}

console.log(sum(1, 2));

类型

TypeScript 入门_第1张图片

// 字面量类型
let a: 10;  // const a = 10;

// 联合类型
let b: "male" | "female";
let c: boolean | string;
c = true;
c = "hello"

// any 与 unknown
let d: any;
d = "hello";

let e: unknown;
e = "hello";

let s: string = d;
// s = e;   Type 'unknown' is not assignable to type 'string'
s = e as string;
s = <string>e;

function f(): void {
    return;
}

function f2(): never {  // 永远不会返回结果,终止进程
    throw new Error("error");
}

unknown 本质上是一个类型安全的 any

// 对象类型
let b: {
    name: string,
    age?: number,   // ? -> optional
};

b = {
    name: "sunwukong"
};

let c: {
    name: string,
    [propName: string]: any,
};

// 函数类型
let d: (a: number, b: number) => number;

d = function(a, b) {
    return a + b;
}

// 数组类型
let e: string[];
e = ["a", "b", "c"];

let g: Array<number>;
g = [1, 2, 3];

// 元组
let h: [string, string];

// enum
enum Gender {
    Male = 0,
    Female = 1,
}

let p: {name: string, gender: Gender}

p = {
    name: "sunwukong",
    gender: Gender.Male,
}

// & 类型
let j: {name: string} & {age: number};

// 类型别名
type t1 = 1 | 2 | 3 | 4 | 5;
let m: t1;

tsc 编译选项

自动编译文件

tsc foo.ts -w	# 文件改变时自动编译
tsc		# 根据 tsconfig.json 编译

tsconfig.json

{
    /*
        ** 表示任意目录
        * 表示任意文件
    */
    "include": ["./*"],
    "exclude": ["foo.ts"],
    // "files": ["./app.ts", "./index.ts"],
    "compilerOptions": {
        "target": "ES6",     // 指定 es 版本
        // "module": "ES6",
        // "lib": ["DOM"]
        "outDir": "./dist",
        // "outFile": "./dist/app.js",  // 将编译结果合并到 app.js
        "allowJs": false,
        "checkJs": false,   // 对 js 也做类型检查
        "removeComments": false,
        "noEmit": true,     // 不生成编译后的文件,只检查
        "noEmitOnError": true,   // 当有错误时不生成编译后的文件
        "strict": true,     // 打开下面的所有严格检查
        /*
            "alwaysStrict": true,   // -> use strict
            "noImplicitAny": true,
            "noImplicitThis": true,
            "strictNullChecks": true,
        */
    },
}

使用 webpack 打包 ts 代码

npm init -y		# 初始化项目
npm i -D webpack webpack-cli typescript ts-loader

配置 webpack.config.js

const path = require("path");

module.exports = {
    entry: "./src/index.ts",
    output: {
        path: path.resolve(__dirname + "/dist"),
        filename: "bundle.js",
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: "ts-loader",
                exclude: /node_modules/
            }
        ]
    },
    mode: 'development',
}

配置 tsconfig.json

{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "strict": true,
    }
}

package.json 中增加 script 配置

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },

最后 npm run build 即可

安装 3 个插件:

npm i -D html-webpack-plugin	# 生成 html 测试文件
npm i -D webpack-dev-server		# 编辑后自动重启服务
npm i -D clean-webpack-plugin	# 清除 dist 目录再构建
npm i -D @babel/core @babel/preset-env babel-loader core-js

package.json 中编写 webpack-server 的启动脚本:

  "scripts": {
    "start": "webpack serve --open"
  },

webpack.config.ts 中引入插件(使用 template.html 作为生成模板):

const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
    entry: "./src/index.ts",
    output: {
        path: path.resolve(__dirname + "/dist"),
        filename: "bundle.js",
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: [
                    {
                        loader: "babel-loader",
                        options: {
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        targets: {
                                            "chrome": "88"
                                        },
                                        "corejs": "3",
                                        "useBuiltIns": "usage"  // 按需加载
                                    }
                                ]
                            ]
                        }
                    },
                    "ts-loader"
                ],
                exclude: /node_modules/
            }
        ]
    },
    mode: 'development',
    plugins: [
        new HTMLWebpackPlugin({
            // title: "mytitle"
            template: "./src/template.html"
        }),
        new CleanWebpackPlugin(),
    ],
    resolve: {  // 设置引用模块
        extensions: [".ts", ".js"]
    }
}

OOP

class

class Person {
    name: string = "sunwukong";
    static age: number = 18;
    readonly gender: string = "M";
    sayHello() {
        console.log("hello");
    }
}

let p = new Person();

console.log(p);
console.log(Person.age);
p.sayHello();

构造函数和 this

class Dog {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    bark() {
        console.log(`${this.name}: wangwang`);
    }
}

let d = new Dog("wangcai", 4);

console.log(d);
d.bark();

继承

class Animal {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log("sayHello()");
    }
}

class Dog extends Animal{
    run(){
        console.log(`${this.name} is running`);
    }
    sayHello(): void {  // 子类重写父类方法
        console.log("wangwangwang");
    }
}

class Cat extends Animal{
    sayHello(): void {  // 子类重写父类方法
        console.log("miaomiaomiao");
    }
}

const d1 = new Dog("wangcai", 5);
console.log(d1);
d1.sayHello();

const c1 = new Cat("mimi", 3);
c1.sayHello();

super

super 表示父类

class Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    sayHello() {
        console.log("sayHello()");
    }
}

class Dog extends Animal {
    age: number;

    constructor(name: string, age: number) {
        super(name);
        this.age = age;
    }

    sayHello(): void {
        console.log("wangwangwang");
    }
}

const d1 = new Dog("wangcai", 3);
console.log(d1);    // Dog { name: 'wangcai', age: 3 }
d1.sayHello();      // wangwangwang

抽象类

使用 abstract 修饰的类,不能用于实例化对象,只能被继承

抽象类中可以添加抽象方法,抽象方法必须被子类重写

abstract class Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    abstract sayHello(): void;  // 必须被子类重写
}

class Dog extends Animal {
    sayHello(): void {
        console.log("wangwangwang");
    }
}

const d1 = new Dog("wangcai");
console.log(d1);    // Dog { name: 'wangcai', age: 3 }
d1.sayHello();      // wangwangwang

接口

用来定义一个类的结构(一个类应该包含哪些属性和方法,做类型限制)

接口中的所有属性都不能带有实际的值,接口只定义对象的结构(全是抽象方法),而不考虑实际值

interface Person{
    name: string;
    age: number;
}

// 接口可以分离定义
interface Person {
    gender: string;
    sayHello(): void;
}

class Male implements Person {
    name: string;
    age: number;
    gender: string;
    constructor(name: string, age: number, gender: string) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    sayHello(): void {
        console.log("hello");
    }
}

属性访问控制

class Person {
    private _name: string;
    private _age: number;

    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
    }

    get name() {        // per.name
        return this._name;
    }

    set name(name: string) {
        this._name = name;
    }

    get age() {
        return this._age;
    }

    set age(age: number) {
        if (age >= 0) {
            this._age = age;
        }
    }
}

const p1 = new Person("sunwukong", 18);
console.log(p1);

p1.name = "zhubajie";
console.log(p1);
console.log(p1.age);

class C {
    // 直接将属性和访问控制定义在构造函数中
    constructor(public name: string, public age: number) {

    }
}

泛型

function fn<T> (a: T): T {
    return a;
}

fn(10);
fn<string>("hello");

function bar<T, K>(a: T, b: K): T {
    console.log(b);
    return a;
}

bar<number, string>(10, "hello");

interface Inter {
    length: number;
}
// 使用接口约束泛型的类型参数
function foo<T extends Inter>(a: T): number {
    return a.length;
}

foo("123");     // string 有 length
foo({length: 10});

// 类的泛型
class C<T> {
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const c = new C<string>("sunwukong");

贪吃蛇练习

// package.json
{
  "name": "snake",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.23.9",
    "@babel/preset-env": "^7.23.9",
    "babel-loader": "^9.1.3",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.35.1",
    "css-loader": "^6.10.0",
    "html-webpack-plugin": "^5.6.0",
    "less": "^4.2.0",
    "less-loader": "^12.2.0",
    "style-loader": "^3.3.4",
    "ts-loader": "^9.5.1",
    "typescript": "^5.3.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }
}
// tsconfig.json
{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "strict": true,
        "noEmitOnError": true
    }
}
// webpack.config.ts
const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
    entry: "./src/index.ts",
    output: {
        path: path.resolve(__dirname + "/dist"),
        filename: "bundle.js",
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: [
                    {
                        loader: "babel-loader",
                        options: {
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        targets: {
                                            "chrome": "88"
                                        },
                                        "corejs": "3",
                                        "useBuiltIns": "usage"  // 按需加载
                                    }
                                ]
                            ]
                        }
                    },
                    "ts-loader"
                ],
                exclude: /node_modules/
            },
            {
                test: /\.less$/,
                use: [
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ]
            }
        ]
    },
    mode: 'development',
    plugins: [
        new HTMLWebpackPlugin({
            // title: "mytitle"
            template: "./src/template.html"
        }),
        new CleanWebpackPlugin(),
    ],
    resolve: {  // 设置引用模块
        extensions: [".ts", ".js"]
    }
}
// index.ts
import "./style/index.less"
import Food from "./modules/food"
import ScorePanel from "./modules/score_panel"
import GameControl from "./modules/game_control";

const f = new Food();
f.change();

const gc = new GameControl();
// snake.ts
class Snake {
    head: HTMLElement;
    bodies: HTMLCollection;
    element: HTMLElement;

    constructor() {
        this.head = document.querySelector("#snake > div")!;
        this.bodies = document.getElementById("snake")!.getElementsByTagName("div");
        this.element = document.getElementById("snake")!;
    }

    get X() {
        return this.head.offsetLeft;
    }

    get Y() {
        return this.head.offsetTop;
    }

    set X(x: number) {
        if (this.X === x) return;

        if (x < 0 || x > 290) {
            throw new Error("snake_dead");
        }
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === x) {
            if (x > this.X) {
                x = this.X - 10;
            } else {
                x = this.X + 10;
            }
        }
        this.moveBody();
        this.head.style.left = x + "px";
        this.checkHeadBody();
    }

    set Y(y: number) {
        if (this.Y === y) return;
        if (y < 0 || y > 290) {
            throw new Error("snake_dead");
        }
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === y) {
            if (y > this.Y) {
                y = this.Y - 10;
            } else {
                y = this.Y + 10;
            }
        }

        this.moveBody();
        this.head.style.top = y + "px";
        this.checkHeadBody();
    }

    addBody() {
        this.element.insertAdjacentHTML("beforeend", "
"
); } moveBody() { for (let i = this.bodies.length - 1; i > 0; i--) { let X = (this.bodies[i - 1] as HTMLElement).offsetLeft; let Y = (this.bodies[i - 1] as HTMLElement).offsetTop; (this.bodies[i] as HTMLElement).style.left = X + "px"; (this.bodies[i] as HTMLElement).style.top = Y + "px"; } } checkHeadBody() { for (let i = 1; i < this.bodies.length; i++) { let bd = this.bodies[i] as HTMLElement; if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) { throw new Error("snake_dead"); } } } } export default Snake;
// score_panel.ts
class ScorePanel {
    score = 0;
    level = 1;
    scoreEle: HTMLElement;
    levelEle: HTMLElement;

    maxLevel: number;
    upScore: number;

    constructor(maxLevel: number = 10, upScore: number = 10) {
        this.scoreEle = document.getElementById("score")!;
        this.levelEle = document.getElementById("level")!;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    }

    addScore() {
        this.scoreEle.innerHTML = ++this.score + "";
        if (this.score % this.upScore === 0) {
            this.levelUp();
        }
    }

    levelUp() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level + "";
        }
    }
}
export default ScorePanel;
// game_control.ts
import Snake from "./snake";
import Food from "./food";
import ScorePanel from "./score_panel";

class GameControl {
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    direction: string = "";
    isLive = true;

    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel();
        this.init();
    }

    keydownHandler = (event: KeyboardEvent) => {
        this.direction = event.key;
    }

    init() {
        document.addEventListener("keydown", this.keydownHandler);
        this.run();
    }

    run() {
        let X = this.snake.X;
        let Y = this.snake.Y;

        switch(this.direction) {
            case "ArrowUp":
                Y -= 10;
                break;
            case "ArrowDown":
                Y += 10;
                break;
            case "ArrowLeft":
                X -= 10;
                break;
            case "ArrowRight":
                X += 10;
                break;
        }
        
        this.checkEat(X, Y);

        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e) {
            alert((e as Error).message);
            this.isLive = false;
        }
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
    }

    checkEat(x: number, y: number) {
        if (x === this.food.X && y === this.food.Y) {
            console.log("ate food");
            this.food.change();
            this.scorePanel.addScore();
            this.snake.addBody();
        }
    }

}

export default GameControl;
// food.ts
class Food {
    element: HTMLElement;
    constructor() {
        // `!` 表示判定该语句的结果不可能为空
        this.element = document.getElementById("food")!;
    }

    get X() {
        return this.element.offsetLeft;
    }

    get Y() {
        return this.element.offsetTop;
    }

    change() {
        let left = Math.round(Math.random() * 29)* 10;
        let top = Math.round(Math.random() * 29)* 10;
        this.element.style.left = top + "px";
        this.element.style.top = left + "px";
    }
}
export default Food;
// index.less
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font: bold 20px Courier;
}

#main {
    width: 360px;
    height: 420px;
    background-color: #b7d4a8;
    margin: 100px auto;
    border: 10px solid black;
    border-radius: 10px;

    display: flex;
    flex-flow: column;
    align-items: center;
    justify-content: space-around;

    #stage {
        width: 304px;
        height: 304px;
        border: 2px solid black;
        position: relative;

        #snake {
            &>div {
                width: 10px;
                height: 10px;
                background-color: black;
                border: 1px solid #b7d4a8;
                position: absolute;
            }
        }

        #food {
            width: 10px;
            height: 10px;
            position: absolute;
            left: 40px;
            top: 100px;
            display: flex;
            flex-flow: row wrap;
            justify-content: space-between;
            align-content: space-between;

            &>div {
                width: 4px;
                height: 4px;
                background-color: black;
            }
        }
    }

    #score-panel {
        width: 300px;
        display: flex;
        justify-content: space-between;
    }


}

你可能感兴趣的:(JavaScript,typescript,javascript,前端)