Electron笔记

news2024/10/5 15:25:04

基础环境搭建

官网:https://www.electronjs.org/zh/

这一套笔记根据这套视频而写的

创建项目

方式一:

官网点击GitHub往下拉找到快速入门就能看到下面这几个命令了

git clone https://github.com/electron/electron-quick-start  //克隆项目
cd electron-quick-start  //切换到项目目录
npm install //这里可能会有报错问题,本人查了gpt说是什么连接远程失败,这里本人建议使用cnpm淘宝镜像,没有的可以百度安装一下,yarn也不行(亲测yarn安装还是报错)
npm start //启动项目

目录结构

在这里插入图片描述

方式二:

先安装

cnpm install --save-dev electron   //这里也是建议使用cnpm淘宝镜像

直接使用npm或者yarn都可能会报错,官方也有解释,这里本人使用cnpm就没问题

在这里插入图片描述

然后在package.json里面创建如下内容

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Hello World!",
  "main": "main.js", //这里是主入口
  "author": "萧寂",
  "license": "MIT",
  "scripts": {
    "start": "electron ." //运行的命令
  },
  "devDependencies": {
    "electron": "^26.0.0"
  }
}

如果安装了nodemon,可以在script节点进行如下配置,可以监听js的代码并实时变化
在html代码改变后不会立即监听,需要切换到应用里面按下CTRL+R即可刷新

"scripts": {
  "start": "nodemon --watch main.js --exec npm run build",
  "build": "electron ."
},

在同级创建main.js,然后在main.js中插入以下内容

const { app, BrowserWindow } = require("electron");
const createWindow = () => {
  // 创建窗口
  let win = new BrowserWindow({
    width: 800,
    height: 600,
  });
  //当前窗口显示的页面
  win.loadFile("index.html");
};

// app启动之后创建窗口
app.on("ready", () => {
  console.log("窗口加载");
  createWindow();
});

// 生命周期
// 通过on监听事件

// 监听关闭的
app.on("close", () => {
  console.log("当前窗口关闭");
});

app.on("window-all-closed", () => {
  console.log("所有窗口关闭");
  //退出应用
  app.quit();
});

CTRL+SHIFT+I可以打开调试面板
同级下创建index.html文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>萧寂</title>
  </head>
  <body>
    <h1>你好!</h1>
    我们正在使用 Node.js <span id="node-version"></span>, Chromium
    <span id="chrome-version"></span>, 和 Electron
    <span id="electron-version"></span>.
  </body>
</html>

然后直接运行

npm start

目录结构

在这里插入图片描述

因为第二种方式目录结构简单,所以在这里演示的所有代码都以第二种创建方式基础上写的

electron生命周期事件

ready:app初始化完成  //重要
dom-ready:一个窗口中的文本加载完成  //重要
did-finsh-load:导航完成时触发   //重要
window-all-closed:所有窗口都被关闭时触发  //重要
before-quit:在关闭窗口之前触发
will-quit:在窗口关闭并且应用退出时触发
quit:当所有窗口被关闭时触发
close:当窗口关闭时触发,此时应删除窗口引用

main.js代码

const { app, BrowserWindow } = require("electron")
const createWindow = () => {
  // 创建窗口
  let win = new BrowserWindow({
    width: 800,
    height: 600,
  })
  //当前窗口显示的页面
  win.loadFile("index.html")

  // 这个webContents对象可以控制dom元素加载事件
  win.webContents.on('did-finish-load', () => {
    console.log('3333->did-finish-load')
  })
  win.webContents.on('dom-ready', () => {
    console.log('2222->dom-ready')
  })
  // 窗口关闭
  win.on('close', () => {
    console.log('8888->close')
    // 从性能考虑,应该释放窗体这个变量,删除窗体引用
    win = null
  })
}

// 生命周期
// 通过on监听事件
app.on('ready', () => {
  console.log("1111->ready")
  createWindow()
})

app.on("window-all-closed", () => {
  // 如果监听了window-all-closed这个事件,需要在事件里面主动退出应用,没有监听事件的话默认会直接退出应用
  // 但如果监听了此事件,但没有退出操作的话,后续的567生命周期也不会执行
  console.log("4444->window-all-closed")
  //退出应用
  app.quit()
})

app.on("before-quit", () => {
  console.log("5555->before-quit")
})

app.on("will-quit", () => {
  console.log("6666->will-quit")
})

app.on("quit", () => {
  console.log("7777->quit")
})

从打开窗体到关闭窗体打印结果如下

在这里插入图片描述

创建窗体时所携带的一些属性

也是main.js代码

const { app, BrowserWindow } = require("electron")
const createWindow = () => {
  // 创建窗口
  let win = new BrowserWindow({
    x: 100, 
    y: 50, //窗体坐标
    show: false, //不展示窗体
    width: 800,
    height: 600, //长宽
    maxHeight: 600,
    maxWidth: 1000, //最大宽高
    minHeight: 200,
    minWidth: 300, //最小宽高
    resizable: false, //不允许缩放
    title: "萧寂", //标题(加上这个属性,在页面中就不要有title标签了)
    icon: "./及格.png", //设置icon图标
    // frame: false, //只保留主体部分,不保留其他的选项卡窗口了,隐藏菜单栏
    // transparent: true, //将窗体完全透明化
    autoHideMenuBar: true, //只保留标题,不保留其他的选项卡部分
  })

  // show需要设置false,意思就是默认不显示窗体,然后执行下面这个事件,ready-to-show:等待完毕准备加载执行,适用于页面显示,监听到了再执行show()
  win.on('ready-to-show', () => {
    win.show()
  })
  //当前窗口显示的页面
  win.loadFile("index.html")
  // 窗口关闭
  win.on('close', () => {
    console.log('close')
    win = null
  })
}

// 窗口加载和关闭
app.on('ready', createWindow)
app.on("window-all-closed", () => {
  console.log("window-all-closed")
  app.quit()
})

窗口标题及环境(创建窗口)

这里要说明一下,低版本直接可以使用一个remote,主进程稍微配置一下就能创建窗口了,高版本就不行了,高版本需要安装一个@electron/remote模块,通过对这个模块稍微配置一下也能创建窗口了,本人之前版本是"electron": "^26.0.0","@electron/remote": "^2.0.11",[具体配置可以可以看看这个小哥的博客](https://blog.csdn.net/qq_39077394/article/details/125667918?ops_request_misc=%7B%22request%5Fid%22%3A%22169651971016800213043799%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=169651971016800213043799&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-125667918-null-null.142v94insert_down28v1&utm_term=enableRemoteModule%3A true不能使用remote&spm=1018.2226.3001.4187),我下面的代码和这两个不一样我是在主进程创建窗口,渲染进程向主进程发请求才能创建窗体(下面代码示例就是这个方法),因为方式和另两种不一样,因此记录一下

ctrl+r 可以刷新当前窗口的index.html样式,ctrl+shift+i可以打开调试窗口

这里强调一下main.js为主进程,创建的js里面重新创建的窗口为渲染进程

主进程main.js代码如下

const { app, BrowserWindow, ipcMain } = require("electron") // ipcMain用于渲染进程创建窗体使用
const createWindow = () => {
  // 创建窗口
  let win = new BrowserWindow({
    x: 100,
    y: 50, //窗体坐标 
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,  //加入这两行代码就可以正常使用require了,不会报错了
    }
  })
  //当前窗口显示的页面
  win.loadFile("index.html")
  // 窗口关闭
  win.on('close', () => {
    console.log('close')
    win = null
  })
}
// 窗口加载和关闭
app.on('ready', createWindow)
app.on("window-all-closed", () => {
  console.log("window-all-closed")
  app.quit()
})

// 下面代码就是创建渲染进程窗体代码
// 在主进程中监听渲染进程的请求
// open-window后面的回调函数,参数一默认是事件对象,参数二为渲染进程传递来的数据
// pageFileName为ipcRenderer.send()的第二个参数,ipcRenderer.send()由渲染进程发起,参数一为事件名,参数二为页面配置(大小,位置等等)
ipcMain.on('open-window', (event, winconfig) => {
  console.log('winconfig', winconfig)
  // 创建新窗口并设置相应的配置(配置由渲染进程提供)
  let newWindow = new BrowserWindow(winconfig)
  // 这里设置的是winconfig.pageFileName,所以渲染进程的请求的配置中必须pageFileName代表页面
  newWindow.loadFile(winconfig.pageFileName)
  // 监听创建的窗体关闭事件
  newWindow.on('close', () => {
    console.log('close')
    newWindow = null
  })
})

主进程页面index.html代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <!-- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
  <title>萧寂</title>
</head>

<body>
  <h1>窗口标题</h1>
  <button id="btn">打开第一个新窗口</button>
  <button id="btn2">打开第二个新窗口</button>
  <!-- 这里必须要使用外联,不能直接在下面写,不然会报错,大概意思就是不能使用内联脚本 -->
  <script src="./index.js"></script>
</body>
</html>

渲染进程index.js代码

// 这里强调一下main.js为主进程,窗口里面页面点击创建的js里面重新创建的窗口为渲染进程
// require直接使用会报错,因为electron是不被允许直接require的,不给这个权限,需要我们自行放开
// 权限需要在窗口的配置定义webPreferences对象,值为 {nodeIntegration: true,contextIsolation: false},这样就可以正常使用require了

// 创建窗口这里使用的是electron自带的ipcRenderer属性,它是向主进程发送创建窗体请求,参数一为事件名,参数二为窗体配置
const { ipcRenderer } = require("electron")
const path = require("path")
window.addEventListener("DOMContentLoaded", () => {
  // 点击按钮打开新窗口
  // 获取btn
  const btn = document.getElementById("btn")
  // 按钮点击打开新窗口
  btn.addEventListener("click", () => {
    // 创建新窗口(向主进程发起请求,创建窗体,并显示pageFileName指定的页面)
    ipcRenderer.send('open-window', {
      width: 600,
      height: 400,
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false
      },
      pageFileName: path.join(__dirname, "list.html") // 确保传递了正确的页面文件名,list.html需要显示的页面
    })
  })


  // 打开第二个窗口
  // 获取btn
  const btn2 = document.getElementById("btn2")
  // 按钮点击打开新窗口
  btn2.addEventListener("click", () => {
    // 创建新窗口(向主进程发起请求,创建窗体,并显示pageFileName指定的页面)
    ipcRenderer.send('open-window', {
      width: 200,
      height: 200,
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false
      },
      pageFileName: path.join(__dirname, "list2.html") // 确保传递了正确的页面文件名,list2.html需要显示的页面
    })
  })
})

项目结构

在这里插入图片描述

效果图

在这里插入图片描述

自定义窗口的实现(以及阻止窗口关闭)

项目结构

在这里插入图片描述

安装

npm install --save @electron/remote

main.js代码如下

const { app, BrowserWindow } = require("electron")
const createWindow = () => {
  // 创建窗口
  let win = new BrowserWindow({
    x: 100,
    y: 50, //窗体坐标 
    frame: false, // 只保留主体部分,然后后面的样式全部都是由html去模拟
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,  //加入这两行代码就可以正常使用require了,不会报错了
      enableRemoteModule: true
    }
  })
  require('@electron/remote/main').initialize()
  require("@electron/remote/main").enable(win.webContents)

  //当前窗口显示的页面
  win.loadFile("index.html")
  // 窗口关闭
  win.on('close', () => {
    console.log('close')
    win = null
  })
}


// 窗口加载和关闭
app.on('ready', createWindow)
app.on("window-all-closed", () => {
  console.log("window-all-closed")
  app.quit()
})

index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <!-- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
  <title>萧寂</title>
  <link rel="stylesheet" href="./index.css">
</head>

<body>
  <main>
    <h1 class="box">主题内容</h1>
    <div>
      <span></span>
      <span></span>
      <span>X</span>
    </div>
  </main>
  <h2>上面的三个模拟最小化,最大化和关闭</h2>

  <div class="isShow">
    <h3>是否关闭当前应用</h3>
    <p>系统可能不会保存您的所有更改</p>
    <p><span class="s1"></span><span class="s1"></span></p>
  </div>
  <script src="./index.js"></script>
</body>

</html>

index.css

main {
  display: flex;
  justify-content: space-evenly;
  align-items: center;
}
.box {
  color: red;
}
span {
  padding: 10px 5px;
  cursor: pointer;
}
/* 将提示语隐藏 */
.isShow {
  display: none;
}
.s1 {
  cursor: pointer;
}

index.js

const remote = require("@electron/remote")

window.addEventListener('DOMContentLoaded', () => {
//利用remote获取当前窗口对象
  let mainwin = remote.getCurrentWindow()
  
  // 这里代码是当窗口关闭,去进行一下阻止,并弹出提示框,用户确定关闭再进行关闭
  // 监听close事件,close事件触发会执行这个事件onbeforeunload
  window.onbeforeunload = function () {
    // 获取到弹框dom元素,并设置样式
    document.querySelector('.isShow').style.display = 'block'
    // 将主题内容隐藏
    document.querySelector('h2').style.display = 'none'

    // 获取弹窗的按钮(确认和取消)
    let btn = document.querySelectorAll('.s1')

    // 点击确认关闭按钮
    btn[0].addEventListener('click', () => {
      // 这里不再使用close事件,不然会一直触发window.onbeforeunload事件,进入死循环了
      mainwin.destroy() //窗口销毁
    })

    // 点击取消按钮
    btn[1].addEventListener('click', () => {
      // 将窗口隐藏就好了
      // 获取到弹框dom元素,并设置样式
      document.querySelector('.isShow').style.display = 'none'
      // 将主题内容显示
      document.querySelector('h2').style.display = 'block'
    })
    return false
  }

  const spans = document.querySelectorAll('span')

  // 最小化
  spans[0].addEventListener("click", () => {
    mainwin.minimize() //窗口最小化
  })
  // 放大
  spans[1].addEventListener("click", () => {
    // 最大化操作
    console.log('mainwin.isMaximized()', mainwin.isMaximized())  //false,返回布尔值,代表当前界面是否是最大化了
    if (!mainwin.isMaximized()) {
      mainwin.maximize() //如果没有最大化的话,给个最大化
    } else {
      mainwin.restore() //如果是最大化了,给它恢复到初始状态
    }
  })
  // 关闭窗口
  spans[2].addEventListener("click", () => {
    mainwin.close()  //关闭窗口
  })
})

效果图

在这里插入图片描述

当点击关闭按钮,会弹出提示框,点击是就关闭,点击否会将提示框进行隐藏
在这里插入图片描述

父子及模态窗口

模态窗口定义:定义完以后不能对主窗口或者别的窗口进行操作,除非模态窗口
其余代码和上面一样,只修改了index.js代码,代码如下

const remote = require('@electron/remote')

window.addEventListener('DOMContentLoaded', () => {
  let btn = document.querySelector('#btn')
  btn.addEventListener('click', () => {
    let subWin = new remote.BrowserWindow({
      width: 200,
      height: 200,
      parent: remote.getCurrentWindow(), //这个属性指向父级,实现了父子关联
      modal: true,  //定义模态窗口(默认为false,定义完以后不能对主窗口或者别的窗口进行操作,除非关闭模态窗口)
    })
    subWin.loadFile('sub.html')
    subWin.on('close', () => {
      subWin = null
    })
  })
})

自定义菜单

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1063450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云服务器e系列是共享型?什么意思?

阿里云服务器经济型e实例是共享型云服务器&#xff0c;共享型实例采用非绑定CPU调度模式。每个vCPU会被随机分配到任何空闲CPU超线程上&#xff0c;不同实例vCPU会争抢物理CPU资源&#xff0c;并导致高负载时计算性能波动不稳定&#xff0c;有可用性SLA保证&#xff0c;但无性能…

【网络安全 --- 工具安装】VMware 16.0 详细安装过程(提供资源)

一&#xff0c;VMware下载地址&#xff1a; 百度网盘链接链接&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https:/…

【开发篇】十六、SpringBoot整合JavaMail实现发邮件

文章目录 0、相关协议1、SpringBoot整合JavaMail2、发送简单邮件3、发送复杂邮件 0、相关协议 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;&#xff1a;简单邮件传输协议&#xff0c;用于发送电子邮件的传输协议POP3&#xff08;Post Office Protocol - Versi…

IPv6协议报文头

IPv6协议概述 IPv6&#xff08;Internet Protocol Version 6&#xff09;是网络层协议的第二代标准协议&#xff0c;也被成为IPng&#xff08;IP Next Generation&#xff09;。它是Internet工程任务组IETF&#xff08;Internet Engineering Task Force&#xff09;设计的一套…

ros2移植Apollo和autoware规控算法可跑工程

工程详细介绍请看&#xff1a; 自动驾驶路径规划控制ros移植Apollo和autoware规控算法可跑工程&#xff08;适合入门学习&#xff0c;科研和实战&#xff09; ros2的工程版本说明 之所以增加ros2版本&#xff0c;是因为想增加代码的工程应用性&#xff0c;其实对于科研来说并…

计算机专业毕业设计项目推荐11-博客项目(Go+Vue+Mysql)

博客项目&#xff08;GoVueMysql&#xff09; **介绍****系统总体开发情况-功能模块****各部分模块实现** 介绍 本系列(后期可能博主会统一为专栏)博文献给即将毕业的计算机专业同学们,因为博主自身本科和硕士也是科班出生,所以也比较了解计算机专业的毕业设计流程以及模式&am…

正点原子嵌入式linux驱动开发——U-boot启动流程详解

在上一篇笔记中详细分析了uboot的顶层Makefile&#xff0c;理清了uboot的编译流程。本章来详细的分析一下uboot的启动流程&#xff0c;理清uboot是如何启动的。通过对uboot启动流程的梳理&#xff0c;可以掌握一些外设是在哪里被初始化的&#xff0c;这样当需要修改这些外设驱动…

14885-2010 固定资产分类与代码 思维导图

声明 本文是学习GB-T 14885-2010 固定资产分类与代码…pdf而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了固定资产的分类、代码及计量单位。 本标准适用于固定资产管理、清查、登记、统计等工作。 2 术语和定义 下列术语和定义…

由[哈希/散列]模拟实现[unordered_map/unordered_set] (手撕迭代器)

文章目录 1.迭代器分析2.细节处理3.完整代码3.1HashTable.h3.2unordered_set.h3.3unordered_map.h3.4Test.cpp 1.迭代器分析 2.细节处理 以下两篇文章均为笔者的呕心沥血想要搞懂本篇文章的uu请自行查阅 哈希/散列的细节实现 哈希/散列–哈希表[思想到结构][修订版] 手撕迭代器…

硬件知识:U盘相关知识介绍,值得收藏

目录 什么是U盘&#xff1f; U盘根据结构有哪几种&#xff1f; 根据U盘的存储介质、外形、功能分类有哪几种&#xff1f; 什么是U盘&#xff1f; U盘&#xff0c;全称为USB闪存盘&#xff0c;是一种以闪存芯片作为数据存储介质的移动存储设备。U盘的历史可以追溯到1998年&am…

vue3 中使用 echarts 图表——准备篇

我们常常在项目中使用图表来表示数据&#xff0c;而目前最常用的图标就是echarts&#xff0c;接下来我们就开始学习在vue中使用echarts图标。 一、准备一个vue项目&#xff08;一般通过vite来构建&#xff0c;而不是vue-cli&#xff09; 1.找到打开vite官网 2. 运行创建命令 …

MySQL进阶-存储引擎

目录 1.MySQL体系结构 体系结构图 各层的作用 2.存储引擎简介 2.1查看当前表的存储引擎 2.2 查询mysql支持的存储引擎 2.3 InnoDB简介 2.4 MyISAM简介 2.5 Memory简介 3.存储引擎的选择 1.MySQL体系结构 mysql体系结构主要有四层结构&#xff0c;从上到下依次是&#…

Spring Cloud zuul扩展能力设计和心得

前言 实际上Spring Cloud已经废弃zuul了&#xff0c;改用gateway&#xff0c;但是webflux的技术并没在实际项目大规模普及&#xff0c;还有很多servlet NIO的应用&#xff0c;所以zuul还是很有必要改造的&#xff0c;实测zuul调优&#xff08;调节转发的连接池&#xff09;跟g…

【算法挨揍日记】day11——852. 山脉数组的峰顶索引、162. 寻找峰值

852. 山脉数组的峰顶索引 852. 山脉数组的峰顶索引 题目描述&#xff1a; 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3存在 i&#xff08;0 < i < arr.length - 1&#xff09;使得&#xff1a; arr[0] < arr[1] < ... arr[i-1] < …

数据结构:二叉树(超详解析)

目录​​​​​​​ 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 1.3.1孩子兄弟表示法&#xff1a; 1.3.2双亲表示法&#xff1a;只存储双亲的下标或指针 两节点不在同一树上&#xff1a; 2.二叉树概念及结构 2.1.概念 2.2.特殊的二叉树&#xff1a; 2…

掌握交易时机!

“您是否知道您选择购买和出售加密货币的时间会产生很大的影响&#xff1f;当然&#xff0c;大多数交易者都知道高价卖出和低价买入的基本知识。然而&#xff0c;在选择交易加密货币的最佳时机时&#xff0c;还需要考虑许多其他小细节。加密货币市场分析表明&#xff0c;一天中…

【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能

刚才的案例中都是以id为条件的简单CRUD&#xff0c;一些复杂条件的SQL语句就要用到一些更高级的功能了。 1.条件构造器 除了新增以外&#xff0c;修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外&#xff0c;还支持…

ES 关于 remote_cluster 的一记小坑

最近有小伙伴找到我们说 Kibana 上添加不了 Remote Cluster&#xff0c;填完信息点 Save 直接跳回原界面了。具体页面&#xff0c;就和没添加前一样。 我们和小伙伴虽然隔着网线但还是进行了深入、详细的交流&#xff0c;梳理出来了如下信息&#xff1a; 两个集群&#xff1a;…

Java常见API---split()

package daysreplace;public class SplitTest {public static void main(String[] args) {String str"武汉市|孝感市|长沙市|北京市|上海市";String[] array str.split("\\|");System.out.println(array[0]);System.out.println(array[1]);System.out.pri…

[黑马程序员TypeScript笔记]------一篇就够了

目录&#xff1a; TypeScript 介绍 TypeScript 是什么&#xff1f;TypeScript 为什么要为 JS 添加类型支持&#xff1f;TypeScript 相比 JS 的优势TypeScript 初体验 安装编译 TS 的工具包 编译并运行 TS 代码 简化运行 TS 的步骤 TypeScript 常用类型 概述类型注解常用基础…