IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> tsconfig 配置文件 -> 正文阅读

[JavaScript知识库]tsconfig 配置文件

前言

tsconfig.json 文件必须在 TypeScript 项目的根目录下。

tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。


一、TSC 命令的使用

在使用 tsc 命令时,只有其后不加任何参数,才会使用 tsconfig.json 配置进行编译。

tsc

此时编译器会从当前目录开始查找 tsconfig.json 文件,如果当前目录没有发现该文件,则逐级向父级目录搜索。如果一直没有检索到该文件,编译器会给出使用提示。

若要直接编译指定的文件,则需要在 tsc 后指定文件名:

tsc <文件名>

TSC 的可选项:

  • –project(简写 -p):编译器直接在该目录下查找 tsconfig.json 文件,如果没找到则报错。
  • –build(简写 -b):编译器会进行增量构建。先找到所有引用的工程,然后检查它们是否为最新版本,最后按顺序构建非最新版本的工程。
  • –verbose(简写 -v):打印详细的日志(可以与其它标记一起使用)。
  • –dry: 显示将要执行的操作但是并不真正进行这些操作。
  • –clean: 删除指定工程的输出(可以与–dry一起使用)。
  • –force: 把所有工程当作非最新版本对待。
  • –watch(简写 -w):观察模式。watch 模式监控当前项目 ts 文件变化立即进行编译。
  • 其他 TSC 的可选项请参见:TS 中文官网配置文件之编译选项

二、tsconfig 配置文件的属性

1、tsconfig 配置文件的顶层属性

(1)、compilerOptions

compilerOptions 是编译选项,可以被忽略,这时编译器会使用默认值。

compilerOptions 的配置详见:TS 中文官网配置文件之编译选项

(2)、file

file 数组类型。它表示由 ts 管理的 文件 的具体路径,可以是相对或绝对路径。

这些文件内部有依赖的模块(或者引入了哪些模块),编译器也会搜索到依赖模块进行编译。

如果某些模块并没有在项目中引入,虽然在项目目录中也不会被编译。

如果不指定 files ,项目目录下的所有文件都会被编译器编译。

【注意】files 中不支持 glob 匹配模式的路径。

(3)、 include 和 exclude

include 和 exclude 都是数组类型。

include 用于表示 ts 管理的文件。

exclude 用于表示 ts 排除的文件(即不被编译的文件)。

文件列表可以使用 glob 匹配模式列表,支持的 glob 通配符有:

  • *:匹配0或多个字符(不包括目录分隔符)。
  • ?:匹配一个任意字符(不包括目录分隔符)。
  • **/:递归匹配任意子目录。

【注意】

  • files、exclude 和 include 这三者的优先级是这样的:files > exclude > include
  • exclude 默认情况下会排除 node_modulesbower_componentsjspm_packagesoutDir 目录。

【拓展】
glob:是一个古老的 UNIX 程序,它用来匹配路径文件名(pathname模式匹配),现在在 Linux Shell 使用和编程方面,glob 还在被广泛使用。glob 的模式匹配跟正则表达式不太一样,它比正则表达式要简单一些。glob 的模式匹配有时也叫做通配符匹配(wildcard matching)。具体的规则可参考:glob 模式匹配简明教程

(4)、compileOnSave

布尔类型,可以让 IDE 在保存文件的时候根据 tsconfig.json 重新生成编译后的文件。

(5)、extends

字符串类型,该值是一个路径,指定另一个配置文件用于继承 tsconfig.json 中的配置。在原文件里的配置最先被加载,原文件里的配置被继承文件里的同名配置所重写。 如果发现循环引用,则会报错。

(6)、typeAcquisition

typeAcquisition 配置项在平时的开发中并不常用,了解即可。

对象类型,设置自动引入库类型定义文件。acquisition 翻译过来是 “获得物、获得” 的意思。在整个项目中,如果存在用JavaScript写的库,ts 会自动去 compilerOptions.typeRoots 指定的目录中寻找对应的类型声明文件。这个行为被称为 typeAcquisition (类型获得)。这个行为可以通过enable来开启或关闭,且以库级别来指定应用的范围。但我在实践中,通过指定 enable 的值去控制这个行为并未有明显的感官,即使使用 vscode 修改配置后重启也并未生效。

(7)、watchOptions

对象类型,用来配置使用哪种监听策略来跟踪文件和目录。由于 tsc 的监听文件机制依赖于 node 的 fs.watch / fs.watchFile。这两种方法的实现并不相同,前者是采用文件系统的事件做到通知,而后者使用轮询的机制。更多可以查阅 node 官方文档里的:fs.watchfs.watchFile

  • watchFile:字符串类型,配置单个文件的监听策略,必须为一下几个值:

    • useFsEvents(默认):采用系统的文件系统的原生事件机制监听文件更改
    • useFsEventsOnParentDirectory:采用系统的文件系统的原生事件机制监听修改文件所在的目录,这样修改一个文件实际上监听的是此文件所在的目录都被监听了,如此整个项目的文件监听器将显著减少,但可能导致监听并不准确。
    • dynamicPriorityPolling:创建一个动态队列去监听文件,修改频率较低的文件将被减少轮询监听的频率。
    • fixedPollingInterval:固定间隔的检查每个文件是否发生变化。
    • priorityPollingInterval:固定间隔的检查每个文件是否发生变化,但使用启发式监听的文件的检查频率要低于非启发式监听的文件。
  • watchDirectory:字符串类型,配置监听目录的策略,必须为以下几个值(和 watchFile 里的对应值的功能一样):

    • useFsEvents(默认)
    • dynamicPriorityPolling
    • fixedPollingInterval
  • fallbackPolling:当采用系统的文件系统中原生事件机制监听文件时,此选项指定本机的文件监听器被耗尽或者不支持本机文件监听器是编译器采用的轮询策略,可以设置为以下几个值(前三个和 watchFile 里的对应值的功能一样):

    • fixedPollingInterval
    • dynamicPriorityPolling
    • priorityPollingInterval
    • synchronousWatchDirectory:禁用对目录的延迟监听。如果有大量的文件更改,比如在 npm install 时 node_modules 目录发生的变化,延迟监听是非常有用的。但总有些不常见的场景需要禁用延迟监听。
  • synchronousWatchDirectory:布尔类型,是否对目录延迟监听。如果配置为 true ,当文件发生修改时同步的调用回调并更新目录监听器。

  • excludeFiles:字符串数组,用于指定不需要被监听变化的文件

  • excludeDirectories:字符串数组,用于指定不需要被监听变化的目录

(8)、reference

reference 是项目引用,它支持将 TypeScript 程序的结构分割成更小的组成部分。

refrence 的作用是将两个项目关联起来作为一个项目开发,当某个项目代码修改后还能单独编译相应的项目而不是整个项目,实现了关联项目间的懒编译。

下面举个应用场景:

假设:我们要开发一个类似于 lodash 的工具库,并在项目中使用,而且后期很有可能还要在业界推广。为了保证这个工具的顺利开发及推广,我们必须要做相应的单元测试。那这个工具库可以看做一个项目,对其中的每个功能的测试也可作为一个独立的项目。但整个过程中,工具库的开发和测试应该是属于同一个项目下 “分项目” 的。那这种情况下 reference 就很棒了。首先我们搭一个目录出来:

|---- src/
	|---- index.ts    // 整个工具库的入口
	|---- copyDeep.ts // 其中定义了copyDeep方法
|---- test/
	|---- copyDeep.test.ts // copyDeep的单元测试
|---- package.json
|---- tsconfig.json

在 copyDeep.test.ts 中肯定要引用 src/copyDeep,也就是说 test 的项目是依赖于 src 的。如果 src 中的代码发生了变化,整个工具库项目应该重新编译,而 test 项目不应该再被编译,这本来就是合理的。如果 test 项目中的代码发生了变化,那 test 项目应该被重新编译,而 src 项目不应该再被编译。如何在一个项目中配置而做到分别编译相应的子项目呢?首先最先想到的应该是在 tsconfig.json 文件中引入 include 字段配置,我们先尝试一下下面的配置:

{
    "files": [
        "./src/index.ts"
    ],
    "include": [
        "./test/**/*.test.ts"
    ],
    "compilerOptions": {
        "outDir": "./dist/"
    }
}

我们来分析这样配置的会有哪些问题:

  • 首先,从整个项目层面,确实做到了修改任意文件重新编译的功能。但注意,编译的是全量的 ts 文件。
  • 随着日后项目的增大,在 *.test.ts 文件中引入也将逐渐变大。
  • 修改了 src///*.ts 的内容,test//*.ts 也将作为输出,这是我们不希望看到的。

此时,reference 将解决上述的每一个问题,我们修改项目结构如下:

|---- src/
	|---- index.ts    	// 整个工具库的入口
	|---- copyDeep.ts 	// 其中定义了copyDeep方法
	|---- tsconfig.json // 工具库的编译配置文件
|---- test/
	|---- copyDeep.test.ts 	// copyDeep的单元测试
	|---- tsconfig.json 	// 测试的编译配置文件
|---- package.json
|---- tsconfig.json

并修改为以下配置:

// 根目录下的 /tsconfig.json
{
  	"compilerOptions": {
    	"declaration": true, // 为子项目生成.d.ts声明文件
    	"outDir": "./dist",
  	}
}

// src目录下的 /src/tsconfig.json
{
	"extends": "../tsconfig",
	"compilerOptions": {
		"composite": true // 必须设置为true,表明该文件夹为一个子项目
	}
}

// test目录下的 /src/tsconfig.json
{
	"extends": "../tsconfig",
	"references": [
		{ "path": "../src" } // 表示引用了工具库项目
	]
}

这样配置后,如果 src 项目已经编译完成并且输出了编译后的文件, 那在 test 项目中,实际加载的是 src 项目声明的 .d.ts 文件,而且这个声明文件是对 test 项目可见的。另外,如果开启了 watch 模式,修改了内容只会编译相应的项目而不会全量编译。这会显著的加速类型检查和编译,减少编辑器的内存占用。而且在代码结构层命有了一个很清晰的规划。

更多 reference 的配置详见:TS 中文官网配置文件之项目引用

2、tsconfig 具体配置与解析汇总

{
   // 指定需要编译文件 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件
   "include": ["./test.ts"],
   // 指定需要编译文件 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件
   "files": ["./src/**/*"],
   // 不编译某些文件
   "exclude": ["test.ts"],
   "compilerOptions": {
       // 只编译修改过的文件,这个时候会生成tsconfig.tsbuildinfo,下次编译的时候会进行对比只编译修改过的文件 
       "incremental": true,
       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
       "target": "es5",
       // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
       "module": "commonjs",
       /* 注意:如果未指定--lib,则会注入默认的librares列表。注入的默认库为:
       对于 --target ES5: DOM,ES5,ScriptHost
       对于 --target ES6: DOM,ES6,DOM.Iterable,ScriptHost
       TS 绝不会在您的代码中注入polyfill,所以需要你自己制定编译lib */
       "lib": ["es5", "dom", "ScriptHost", "es2015.promise"],
       // 允许编译JS
       "allowJs": true,
       /* 是否检测JS的语法,例如下面的语法编辑器会报错
       let name = 'paul';
       console.log(name.a.b) */
       "checkJs": true,
       // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
       "jsx": preserve,
       /* 如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件,
       declaration和allowJs不能同时设为true */
       "declaration": true
       // 值为true或false,指定是否为声明文件.d.ts生成map文件
       "declarationMap": true
       // 用来指定编译时是否生成.map文件
       "sourceMap": true,
       // 当module设置为 'amd' and 'system'的时候可以使用此命令,这样可以将ts文件打包到一个目录下
       "outFile":"./",
       //  outDir 编译后的文件存到到哪个目录下,默认是每一个ts文件的当前目录,,下面的配置就是把ts编译到build目录下
       "outDir": './build',
       // 下面单独介绍
       "rootDir": "./src",
       // 是否编译构建引用项目,很复杂后面介绍
       "composite": true,
       // 指定文件用来存储增量编译信息,默认是tsconfig.tsbuildinfo
       "tsBuildInfoFile": "./",
       // 编译的时候删除注释
       "removeComments": true,
       // 不生成编译文件,这个一般比较少用,这个build目录下将没有任何文件,但是会进行编译,有错误会抛出
       "noEmit": true,
       // 是否引入npm包tslib中的辅助函数,__extends等 
       "importHelpers": true,
       // 当target为'ES5' or 'ES3'时,为'for-of', spread, and destructuring'中的迭代器提供完全支持
       "downlevelIteration": true,
       // isolatedModules的值为true或false,指定是否将每个文件作为单独的模块,默认为true,它不可以和declaration同时设定
       // 不是很理解,将每一个文件作为单独模块
       "isolatedModules": true,
       /* Strict Type-Checking Options */
       // 严格模式将会打开下面的几个选项
       "strict": false, 
       /* 不允许变量或函数参数具有隐式any类型,例如
       function(name) {
           return name;
       } */
       "noImplicitAny": true,
       // null类型检测,const teacher: string = null;会报错
       "strictNullChecks": true,
       // 对函数参数进行严格逆变比较
       "strictFunctionTypes": true,
       // 严格检查bind call apply
       "strictBindCallApply": true,
       // 此规则将验证构造函数内部初始化前后已定义的属性。
       "strictPropertyInitialization": true,
       // 检测this是否隐式指定
       "noImplicitThis": true,
       // 使用js的严格模式,在每一个文件上部声明 use strict
       "alwaysStrict": true,
       /* Additional Checks */
       // 默认false,是否检测定义了但是没使用的变量
       "noUnusedLocals": true,
       // 用于检查是否有在函数体中没有使用的参数
       "noUnusedParameters": true,
       // 用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示
       "noImplicitReturns": true,
       // 用于检查switch中是否有case没有使用break跳出switch
       "noFallthroughCasesInSwitch": true,
       /* Module Resolution Options */
       // 用于选择模块解析策略,有'node'和'classic'两种类型
       "moduleResolution": "node",
       // 复杂的很 下面单独介绍这三个模块
       "baseUrl": './'
       "paths": {},                   
       "rootDirs": [],
       /* typeRoots用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载 */
       typeRoots: [],
       // types用来指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来
       types:[],
       // 用来指定允许从没有默认导出的模块中默认导入 
       "allowSyntheticDefaultImports": true, 
       // 通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性
       "esModuleInterop": true ,
       // 不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识
       "preserveSymlinks": true,
       "allowUmdGlobalAccess": true,
       
       // sourceRoot用于指定调试器应该找到TypeScript文件而不是源文件的位置,这个值会被写进.map文件里
       "sourceRoot": '',
       // mapRoot用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径,该选项会影响.map文件中的sources属性
       "mapRoot",
       // inlineSourceMap指定是否将map文件内容和js文件编译在一个同一个js文件中,如果设为true,则map的内容会以//#soureMappingURL=开头,然后接base64字符串的形式插入在js文件底部
       "inlineSourceMap": true,
       // inlineSources用于指定是否进一步将ts文件的内容也包含到输出文件中
       "inlineSources": true,
       
       // experimentalDecorators用于指定是否启用实验性的装饰器特性
       "experimentalDecorators": true,
       
       // emitDecoratorMetadata用于指定是否为装上去提供元数据支持,关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据,如果需要使用Reflect的一些方法,需要引用ES2015.Reflect这个库
       "emitDecoratorMetadata": true,
       // compileOnSave的值是true或false,如果设为true,在我们编辑了项目中的文件保存的时候,编辑器会根据tsconfig.json中的配置重新生成文件,不过这个要编辑器支持
       "compileOnSave": true,
       // 很复杂 下面介绍
       "references":[]",
   }
}

3、重点属性的配置详解

(1)、strict 模式详解

当 Typescript 严格模式设置为 on 时,它将使用 strict 族下的 严格类型规则 对项目中的所有文件进行代码验证。

strict 的严格类型规则包含以下属性:

规则名称解释
noImplicitAny不允许变量或函数参数具有隐式any类型。
noImplicitThis不允许this上下文隐式定义。
strictNullChecks不允许出现null或undefined的可能性。
strictPropertyInitialization验证构造函数内部初始化前后已定义的属性。
strictBindCallApply对 bind, call, apply 更严格的类型检测。
strictFunctionTypes对函数参数进行严格逆变比较。

1??、noImplicitAny

不允许变量或函数参数具有隐式 any 类型。

例如:

// Typescript严格模式
function getName (name) {
    return name;
}
// 报错:Parameter 'name' implicitly has an 'any' type. ts(7006)

如果确定为name为any, 也必须显式的制定:

// Typescript严格模式
function getName1 (name: any) {
    return name;
}

2??、noImplicitThis

不允许 this 上下文隐式定义。

例如:

// Typescript严格模式
function uppercaseLabel () {
    return this.label.toUpperCase()
}

const config = {
    label: 'foo-config',
    uppercaseLabel
}
config.uppercaseLabel();
// 报错:'this' implicitly has type 'any', because it does not have a type annotation. ts(2683)

你可以这样修改:

// Typescript严格模式
interface MyConfig {
    label: string
    uppercaseLabel: (params: void) => string
}

const config: MyConfig = {
    label: 'foo-config',
    uppercaseLabel () {
      return this.label.toUpperCase()
    }
}

3??、strictNullChecks

不允许出现null或undefined的可能性。

例如:

// 此规则不允许出现 null 或 undefined 的可能性。请看以下示例:
// Typescript 非严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
}
// Typescript 非严格模式下,这样写不会有任何问题。但是,严格模式下,若发现没有匹配到任何值时就会报错
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
  //  报错:Object is possibly 'undefined'. ts(2532)
}
// 于是你会将改成以下模样:
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  if (typeof article === 'undefined') {
    throw new Error(`Could not find an article with id: ${id}.`)
  }

  return article.meta
}

4??、strictPropertyInitialization

验证构造函数内部初始化前后已定义的属性。

例如:

// 此规则将验证构造函数内部初始化前后已定义的属性。
// 必须要确保每个实例的属性都有初始值,可以在构造函数里或者属性定义时赋值
// Property 'username' has no initializer and is not definitely assigned in the constructor.
class User {
    private username: string;
}
  
const user = new User();
  
const username = user.username.toLowerCase();

// 第一种修改方式为username指定类型为string或者undefined
class User {
    private username: string | undefined;
}
  
const user = new User();
// 第二种方式是创建实例的时候初始化值
class User {
   constructor (public username: string) {}
}
// 第三种方式是使用断言明确告诉TS我知道自己在干嘛
const user = new User('paul');
    class User {
    username! : string;
}
const user = new User();
const username = user.username.toLowerCase();

5??、strictBindCallApply

对 bind, call, apply 更严格的类型检测。

例如:

/* 此函数在TS中会报错,Argument of type '[number, number, number]' is not
assignable to parameter of type '[number, number]'.
Types of property 'length' are incompatible */
function sum (num1: number, num2: number) {
    return num1 + num2
}
sum.apply(null, [1, 2, 3]);
// 可以使用...运算符和reduce函数修改
function sum(...args: number[]) {
    return args.reduce<number>((total, num) => total + num, 0)
}
  
sum.apply(null, [1, 2, 3])

6??、strictFunctionTypes

对函数参数进行严格逆变比较。

例如:

// 该规则将检查并限制函数类型参数是逆变而非双变,因为对于逆变类型的接口,TS是允许双变的
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;  // 启用 --strictFunctionTypes 时错误   
f2 = f1;  // 正确
f2 = f3;  // 错误

interface Animal {
    Eat(): void
}

interface Dog extends Animal{
    Bark():void
}

interface Cat extends Animal{
    Meow():void
}

interface Comparer<T> {
    compare: (a: T, b: T) => number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 启用 --strictFunctionTypes 时错误
dogComparer = animalComparer;  // 正确

(2)、模块解析baseUrl

// 假设我们路径如下,此时我们在test.ts中引用 import test2 from '../test2',
-- src
version1
    test.ts
version2
    demo.ts
test2.ts
// 如果我们设置"baseUrl": "./src" 那么我们在ts中引入test2可以写为
import test2 from 'test2',需要注意的是只有我们引用的是绝对路径的时候才会使用baseUrl去解析文件

(3)、路径映射(path)

// 如果我们tsconfig使用如下配置
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "version2/*"
      ]
    }
}
// 我们的项目目录如下,此时我们在test.ts中引用 import test2 from 'test2',
// 首先匹配 baseUrl/test2如果找到则停止否则开始寻找 baseUrl/version2/test2
-- src
version1
    test.ts
version2
    demo.ts
test2.ts

(4)、rootDir

假设我的目录结构如下:

-- src
    version1
        test.ts
    version2
        demo.ts

如果我们设置 "rootDir": "./src",那么我们的编译后的文件结构如下:

--build
    version1
        test.ts
    version2
        demo.ts

【注意】项目中除了 src 目录之外的其他地方不能有 ts 文件。

(5)、虚拟目录 rootDirs

// 如果我们tsconfig使用如下配置, 这个时候我们生成了一个虚拟的根目录,这个根目录下存放了version2,version3目录下文件
{
  "compilerOptions": {
    "rootDirs": [
      "src/version2",
      "src/version3",
    ],
}

// 我们的项目目录如下,此时我们在test.ts中引用demo就可以这样使用了 import demo from './demo',
-- src
version1
    test.ts
version2
    demo.ts
test2.ts

三、配置 tsconfig 文件时常见的错误信息列表

参见:TS 中文官网配置文件之错误信息列表




【参考文献】
TypeScript 中文官网的项目配置
typeScript tsconfig配置详解
tsconfig.json配置详解
Typescript 严格模式有多严格?
tsconfig.json文件各字段吐血整理

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:32:12  更:2022-04-18 17:32:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:42:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码