过去 3 年的所有 JavaScript 和 TypeScript 特性
作者:互联网
概述
- JavaScript / ECMAScript(最早的在前)
- 打字稿(最旧的在前)
ECMA脚本
过去(仍然相关的旧介绍)
- 标记的模板文字:通过在模板文字前面添加一个函数名,该函数将传递模板文字的部分和模板值。这有一些有趣的用途。
// 假设我们要编写一种方法来记录包含数字的任意字符串,但要格式化数字。 // 我们可以为此使用标记模板。 function formatNumbers ( strings: TemplateStringsArray, number : number ): string { return strings[ 0 ] + number . toFixed ( 2 ) + 字符串[ 1 ]; } 控制台。log (formatNumbers `这是值:${ 0 },这很重要。` ); // 这是值:0.00,很重要。 // 或者如果我们想“翻译”(在这里更改为小写)字符串中的翻译键。 function translateKey ( key: string ): string { 返回键。toLocaleLowerCase (); } function translate ( strings: TemplateStringsArray, ...expressions: string [] ): string { 返回字符串。reduce ( ( accumulator, currentValue, index ) => accumulator + currentValue + translateKey (expressions[index] ?? '' ), ''); } 控制台。日志(将`你好,这是${ 'NAME' }翻译成${ 'MESSAGE' }。`);// 你好,这是消息的名称。
- 符号:对象的唯一键:
Symbol("foo") === Symbol("foo"); // false
。内部使用。
const obj : { [ index : string ]: string } = {}; const symbolA =符号( 'a' ); 常量符号 B =符号。对于('b'); 控制台。日志(symbolA.description );// "a" obj[symbolA] = 'a' ; obj[symbolB] = 'b' ; obj[ 'c' ] = 'c' ; 对象。d = 'd' ; 控制台。(obj[symbolA]); //“一个” 控制台。日志(obj[symbolB]);// "b" // 不能使用任何其他符号或没有符号访问密钥。 控制台。log (obj[ Symbol ( 'a' )]); // 未定义的 控制台。日志(obj[ 'a' ]);// undefined // 当使用 for ... in. for ( const i in obj) { console . 日志(一);// "c", "d" }
ES2020
- 可选链接
?
:要访问可能未定义的对象的值(通过索引),可以通过在父对象名称之后使用可选链接来使用。这也可以用于索引 ([...]
) 或函数调用。
// PREVIOUSLY: // 如果我们有一个对象变量(或任何其他结构),我们不确定是否已定义, // 我们无法轻松访问该属性。 常量 对象:{名称:字符串} | 未定义=数学。随机() > 0.5 ? 未定义:{名称:'测试' }; 常量值 =对象。姓名;// 类型错误:'object' 可能是 'undefined'。 // 我们可以先检查它是否已定义,但这会损害可读性并且对于嵌套对象来说会变得复杂。 常数 objectOld : { name : string } | 未定义=数学。随机() > 0.5 ? 未定义:{名称:'测试' }; const valueOld = objectOld ?对象旧。名称:未定义; // 新: // 相反,我们可以使用可选链接。 const objectNew : {名称:字符串} | 未定义=数学。随机() > 0.5 ? 不明确的:{名称:'测试' }; const valueNew = objectNew?。姓名; // 这也可以用于索引和函数。 常量 数组:字符串[] | 未定义=数学。随机() > 0.5 ? 未定义:[ '测试' ]; const item = array?.[ 0 ]; 常量 函数:(()=> 字符串)| 未定义=数学。随机() > 0.5 ?未定义: () => '测试' ; const result = func?.();
- Nullish coalescing operator (??) :可以使用
||
new运算符代替有条件赋值的运算符。??
它不是应用于所有虚假值,而是仅应用于undefined
和null
。
常 量值:字符串| 未定义=数学。随机() > 0.5 ? 未定义:“测试”; // PREVIOUSLY: // 当我们想在值未定义或 null 时有条件地分配其他内容时,我们可以使用“||” 操作员。 const anotherValue = 值 || '你好' ; 控制台。日志(另一个值);// "test" 或 "hello" // 这在使用真值时工作正常,但如果我们要与 0 或空字符串进行比较,它也适用。 const incorrectValue = ''|| “不正确”; 控制台。日志(不正确的值);// 总是“不正确” const anotherIncorrectValue = 0 || “不正确”; 控制台。日志(另一个不正确的值);// 总是“不正确” // 新: // 相反,我们可以使用新的无效合并运算符。它仅适用于未定义和空值。 const newValue = 值 ?? '你好' ; 控制台。log (newValue) // 始终为“hello” // 现在不替换虚假值。 ??“不正确”; 控制台。日志(正确值);// 总是 "" const anotherCorrectValue = 0 ?? “不正确”; 控制台。日志(另一个正确值);// 总是 0
import()
:动态导入,就像import ... from ...
,但在运行时和使用变量。
让导入模块; if (shouldImport) { importModule = await import ( './module.mjs' ); }
String.matchAll
:在不使用循环的情况下获取正则表达式的多个匹配项,包括它们的捕获组。
const stringVar = 'testhello,testagain,' ; // PREVIOUSLY: // 只获取匹配项,但不获取它们的捕获组。 控制台。log (stringVar.match ( /test([\w]+?),/g ) ); // ["testhello,", "testagain,"] // 只得到一个匹配项,包括它的捕获组。const singleMatch = 字符串变量。匹配( /test([\w]+?),/ ); 如果(单一匹配){控制台。日志(单匹配[ 0 ]);// “testhello”,控制台。日志(单匹配[ 1 ]); // "hello" } // 得到相同的结果,但是非常不直观(exec 方法保存最后一个索引)。 // 需要定义在循环外(保存状态)并且是全局的(/g), // 否则会产生死循环。 const regex = /test([\w]+?),/g ; 让执行匹配; while ((execMatch = regex.exec ( stringVar)) !== null ) { console . 日志(execMatch[ 0 ]);// "testhello,", "testagain," 控制台。日志(execMatch[ 1 ]);// “再一次问好” } // 新: // 正则表达式需要是全局的 (/g),否则也没有任何意义。 const matchesIterator = stringVar. matchAll ( /test([\w]+?),/g ); // 需要迭代或转换为数组(Array.from()),不能直接索引。 for ( const match of matchesIterator) { console . 日志(匹配[ 0 ]);// "testhello,", "testagain," 控制台。日志(匹配[ 1 ]);// “你好”,“再次” }
Promise.allSettled()
:喜欢Promise.all()
,但等待所有承诺完成并且不会在第一次拒绝/抛出时返回。它使处理所有错误变得更加容易。
async function success1 ( ) { return 'a' } async function success2 ( ) { return 'b' } async function fail1 ( ) { throw 'fail 1' } async function fail2 ( ) { throw 'fail 2' } // 上一个: 控制台。log ( await Promise .all ([ success1 (), success2 ( ) ])); // ["a", "b"] // 但是: 尝试{ 等待 承诺。所有([成功1(),成功2(),失败1(),失败2()]); } catch (e) { 控制台. 记录(e); // "fail 1" } // 注意:我们只捕获一个错误,无法访问成功值。 // PREVIOUS FIX(真的不是最理想的): 控制台。log ( await Promise .all ([ // ["a", "b", undefined, undefined] success1 ( ). catch ( e => {控制台。记录(e); }), 成功2()。catch ( e => { console.log (e); }), fail1 ( ) . catch ( e => { console .log (e); }), // “失败 1” fail2 () 。catch ( e => { console.log (e); } )])) ; // "fail 2" // 新:const results = await Promise . 全部解决([ success1 (), success2 (), fail1 (), fail2 ()]); const成功结果 = 结果 。过滤器(结果=>结果。状态=== '已完成') 。map ( result => (result as PromiseFulfilledResult < string >). value ); 控制台。日志(成功结果);// ["a", "b"] 结果。过滤器(结果=>结果。状态=== '拒绝')。forEach ( error => { console . log ((error as PromiseRejectedResult ). reason ); // "fail 1", "fail 2" }); // 或: for ( const result of results) { if (result.status === ' fulfilled' ) { console . 日志(结果。值);// "a", "b" } else if ( result.status ==='拒绝' ) { 控制台. 日志(结果。原因);// “失败 1”,“失败 2” } }
BigInt
:新的BigInt
数据类型允许准确地存储和操作大(整数)数字,这可以防止 JavaScript 将数字存储为浮点数而产生的错误。它们可以使用BigInt()
构造函数构造(最好使用字符串以防止不准确)或通过附加n
在数字末尾来构造。
// 以前: // JavaScript 将数字存储为浮点数,因此总会有一些不准确 // 但更重要的是,在某个数字之后的整数运算开始出现不准确。 const maxSafeInteger = 9007199254740991 ; 控制台。日志(maxSafeInteger === Number。MAX_SAFE_INTEGER );// true // 如果我们比较比它大的数字,可能会不准确。控制台。日志(数字。MAX_SAFE_INTEGER + 1 ===数字。MAX_SAFE_INTEGER + 2 _ _ ); // 新: // 使用新的 BigInt 数据类型,理论上我们可以存储和操作无限大(整数)的数字。 // 通过使用 BigInt 构造函数或在数字末尾附加“n”来使用它。 const maxSafeIntegerPreviously = 9007199254740991n ; 控制台。日志(maxSafeIntegerPreviously);// 9007199254740991 const anotherWay = BigInt ( 9007199254740991 ); 控制台。日志(另一种方式);// 9007199254740991 // 如果我们使用数字构造函数,我们不能安全地传递大于 MAX_SAFE_INTEGER 的整数。 常数不正确 = BigInt ( 9007199254740992 ); 控制台。日志(不正确);// 9007199254740992 const incorrectAgain = BigInt ( 9007199254740993 ); 控制台。日志(再次不正确);// 9007199254740992 // 糟糕,它们转换为相同的值。 // 而是使用字符串或更好的其他语法。 const correct = BigInt ( '9007199254740993' ); 控制台。日志(正确);// 9007199254740993 const correctAgain =9007199254740993n ; 控制台。日志(再次正确);// 9007199254740993 // 十六进制、八进制和二进制数也可以作为字符串传递。 const hex = BigInt ( '0x1ffffffffffffff' ); 控制台。日志(十六进制);// 9007199254740991 const octal = BigInt ( '0o377777777777777777' ); 控制台。日志(八进制);// 9007199254740991 const binary = BigInt ( '0b1111111111111111111111111111111111111111111111111111' ); 安慰. 日志(二进制);// 9007199254740991 // 大多数算术运算的工作方式与您预期的一样, // 尽管另一个运算符也需要是 BigInt。所有操作也返回 BigInts。 常量加法 = maxSafeIntegerPreviously + 2n ; 控制台。日志(添加);// 9007199254740993 const multiplication = maxSafeIntegerPreviously * 2n ; 控制台。日志(乘法);// 18014398509481982 const减法 = 乘法 - 10n ; 控制台。日志(减法);// 18014398509481972 const模 = 乘法 % 10n ; 控制台。日志(模数);// 2 const求幂 = 2n ** 54n ; 控制台。日志(求幂);// 18014398509481984 const exponentiationAgain = 2n ^ 54n ; 控制台。日志(再次取幂);// 18014398509481984 const negative = exponentiation * - 1n ; 控制台。日志(消极的); // -18014398509481984 // 除法的工作方式有点不同,因为 BigInt 只能存储整数。 const除法 = 乘法 / 2n ; 控制台。日志(除法);// 9007199254740991 // 对于可整除的整数,这很好用。 // 但对于不可整除的数字,这将类似于整数除法(向下舍入)。 const divisionAgain = 5n / 2n ; 控制台。日志(再次划分);// 2 // 非 BigInt 数字没有严格相等(但松散相等)。 控制台。日志( 0n === 0 ); // 假 控制台。日志(0n == 0);// true // 但是比较按预期工作。 控制台。对数( 1n < 2 ); // 真正的 控制台。对数( 2n > 1 ); // 真正的 控制台。对数( 2 > 2 ); // 假 控制台。对数( 2n > 2); // 假 控制台。日志( 2n >= 2 ); // true // 它们属于“bigint”类型 console。日志(类型为 1n);// "bigint" // 它们可以转换回常规数字(有符号和无符号(无负数))。 // 虽然这当然会牺牲准确性。可以指定有效位数。 控制台。日志(BigInt.asIntN(0 , -2n ));// 0 控制台。记录(大整数。asIntN ( 1 , - 2n )); // 0 控制台。日志(BigInt.asIntN(2,-2n ));// -2 // 通常你会使用更多的位数。// 负数在转换为无符号数时会被转换为2的补码。控制台。日志(BigInt.asUintN(8 , -2n ));// 254
globalThis
:在全局上下文中访问变量,无论环境如何(浏览器、Node.js 等)。仍然被认为是不好的做法,但有时是必要的。类似于this
浏览器的顶层。
控制台。日志(globalThis。数学);// 数学对象
import.meta
: 使用 ES-modules 时,获取当前模块 URLimport.meta.url
。
控制台。日志(导入。元。url );// “文件://...”
- export * as … from …:轻松将默认值重新导出为子模块。
从“ 另一个模块”导出*
从 “模块”导入{am}
ES2021
String.replaceAll()
:替换字符串中子字符串的所有实例,而不是始终使用带有全局标志 (/g) 的正则表达式。
const testString = 'hello/greeting everybody/everybody' ; // PREVIOUSLY: // 仅替换第一个实例 控制台。日志(testString。替换('/','|')); // 'hello|greetings everyone/everybody' // 相反,需要使用正则表达式,这对性能来说更糟,需要转义。 // 不是全局标志 (/g)。 控制台。日志(testString.replace ( / \//g , '|' )); // '你好|大家好|大家' // 新: // 使用 replaceAll 这会更清晰、更快速。 控制台。日志(testString.replaceAll ('/' , ' |')); // '你好|大家好|大家'
Promise.any
:当只需要一个承诺列表的一个结果时,它返回第一个结果,它只在所有承诺都拒绝时拒绝并返回一个AggregateError
, 而不是Promise.race
,它会立即拒绝。
async function success1 ( ) { return 'a' } async function success2 ( ) { return 'b' } async function fail1 ( ) { throw 'fail 1' } async function fail2 ( ) { throw 'fail 2' } // 上一个: 控制台。log ( await Promise . race ([ success1 (), success2 ()])); // "a" // 但是: 尝试{ 等待 承诺。race ([ fail1 (), fail2 (), success1 (), success2 ()]); } catch (e) { 控制台. 记录(e); // "fail 1" } // 注意:我们只捕获一个错误,无法访问成功值。 // PREVIOUS FIX(真的不是最理想的): 控制台。log ( await Promise . race ([ // "a" fail1 (). catch ( e => {控制台. 记录(e); }), // “失败 1” fail2 (). catch ( e => { console .log (e); }), // “fail 2” success1 (). catch ( e => { console.log (e); }), success2 ( ) . catch ( e => { console.log (e); } )])) ; // 新:控制台。日志(等待承诺。任何([失败1 (), fail2 (), success1 (), success2 ()])); // "a" // 并且它仅在所有承诺都拒绝时拒绝并返回包含所有错误的 AggregateError。 尝试{ 等待 承诺。任何([ fail1 (), fail2 ()]); } catch (e) { 控制台. 记录(e); // [AggregateError: 所有承诺都被拒绝] console . 日志(例如错误);// ["失败 1", "失败 2"] }
- Nullish coalescing assignment (??=):仅在之前为“nullish”(null 或 undefined)时才分配一个值。
让x1 =未定义; 让x2 = 'a' ; const getNewValue = ( ) => 'b' ; // 将新值赋给 x1,因为 undefined 是无效的。 x1 ??= 'b' ; 控制台。log (x1) // "b" // 不给 x2 赋新值,因为字符串不是空值。 // 另请注意:getNewValue() 永远不会执行。 x2 ??= getNewValue (); 控制台。日志(x1)//“a”
- 逻辑与赋值(&&=):只有在之前为“真”时才赋值(真或转换为真的值)。
让x1 =未定义; 让x2 = 'a' ; const getNewValue = ( ) => 'b' ; // 不给 x1 赋新值,因为 undefined 不是 truthy。 // 另请注意:getNewValue() 永远不会执行。 x1 &&= getNewValue (); 控制台。log (x1) // undefined // 为 x2 分配一个新值,因为字符串是真实的。 x2 &&= 'b' ; 控制台。日志(x1)//“b”
- 逻辑或赋值(||=):只有在之前为“falsy”时才赋值(false 或转换为false)。
让x1 =未定义; 让x2 = 'a' ; const getNewValue = ( ) => 'b' ; // 将新值赋给 x1,因为 undefined 是假的。 x1 ||= 'b' ; 控制台。log (x1) // "b" // 不给 x2 赋新值,因为字符串不是假的。 // 另请注意:getNewValue() 永远不会执行。 x2 ||= getNewValue (); 控制台。日志(x1)//“a”
WeakRef
:保持对对象的“弱”引用,而不阻止对象被垃圾收集。
const ref = new WeakRef (元素); // 如果对象/元素仍然存在并且没有被垃圾回收,则获取值。 常量值 = 参考。取消引用; 控制台。日志(值);// undefined // 看起来这个对象已经不存在了。
- 数字文字分隔符 (_):分隔数字
_
以提高可读性。这不会影响功能。
常数int = 1_000_000_000 ; const float = 1_000_000_000.999_999_999 ; const max = 9_223_372_036_854_775_807n ; 常量二进制 = 0b1011_0101_0101 ; 常量八进制 = 0o1234_5670 ; 常量十六进制 = 0xD0_E0_F0 ;
ES2022
- 顶层 await:
await
关键字现在可以在 ES 模块的顶层使用,消除了对包装函数的需要并改进了错误处理。
异步 函数 asyncFuncSuccess ( ) { return 'test' ; } async function asyncFuncFail ( ) { throw new Error ( 'Test' ); } // PREVIOUSLY: // 每当我们想要等待一个承诺时,这只能在异步函数中实现。 // 等待 asyncFuncSuccess(); // SyntaxError: await 仅在异步函数中有效 // 所以我们不得不将它包装在一个函数中,从而失去了错误处理和顶级并发性。 尝试{ (异步() => { 控制台。日志(等待 asyncFuncSuccess ()); // “测试” 尝试{ await asyncFuncFail (); } catch (e) { // 这是必需的,否则错误永远不会被捕获(或者在没有适当跟踪的情况下为时已晚)。 控制台。错误(e);// 错误:“测试” 抛出e; } })(); } catch (e) { // 这永远不会被触发(或者太晚没有适当的跟踪)因为函数是异步的。 控制台。错误(e); } // 这是在 promise 结果之前记录的,因为没有等待异步函数(因为它不能)。 控制台。日志('嘿'); // "Hey" // NEW: // 如果文件是一个 ES 模块(在 package.json 中设置,有导出,名为“.mts”),我们可以在顶层等待。 控制台。日志(等待 asyncFuncSuccess());// “测试” 尝试{ await asyncFuncFail (); } catch (e) { 控制台. 错误(e);// 错误:“测试” } // 这是在承诺结果之后记录的,因为等待所有异步调用。 控制台。日志('你好'); // “你好”
#private
: 通过以#
. 然后只能从类本身访问这些。它们不能被删除或动态分配。任何不正确的行为都会导致 JavaScript(而非 TypeScript)语法错误。对于 TypeScript 项目不推荐这样做,而是使用 existingprivate
关键字。
类 ClassWithPrivateField { #privateField; #anotherPrivateField = 4 ; 构造函数( ) { 这个.#privateField = 42 ; // 有效 . #privateField; // 语法错误 this .#undeclaredField = 444 ; // 语法错误 控制台. 日志(这个。#anotherPrivateField);// 4 } } 常量实例 = new ClassWithPrivateField (); 实例.#privateField === 42 ;// 语法错误
- 静态类成员:将任何类字段(属性和方法)标记为静态。
类 Logger { static id = 'Logger1' ; 静态 类型= 'GenericLogger' ; 静态 日志(消息:字符串|错误){ 控制台。日志(消息); } } class ErrorLogger extends Logger { static type = 'ErrorLogger' ; 静态限定类型; 静态 日志(e:错误){ 返回 超级。log (e.toString ( )); } } 控制台。日志(记录器。类型);//“GenericLogger” 记录器。日志('测试'); // "Test" // static-only 类的实例化是无用的,只是为了演示目的而在这里完成。 const log = new Logger (); 错误记录器。日志(新 错误(“测试”));// 错误:“测试”(不受父实例化的影响) 控制台。日志(错误记录器。类型);// “ErrorLogger” 控制台. 日志(ErrorLogger.qualifiedType); _ // 未定义的 控制台。日志(ErrorLogger.id );_ // "Logger1" // 这会抛出异常,因为 log() 不是实例方法而是静态方法。 控制台。log (log.log ( )); // log.log 不是函数
- 类中的静态初始化块:初始化类时运行的块,基本上是静态成员的“构造函数”。
类 测试{ static staticProperty1 = '属性 1' ; 静态静态属性2; 静态{ 这个。staticProperty2 = '属性 2' ; } } 控制台。日志(测试。staticProperty1 );//“属性 1” 控制台。日志(测试。staticProperty2 );// "属性 2"
- 导入断言(非标准,在 V8 中实现):断言导入使用的是哪种类型
import ... from ... assert { type: 'json' }
。可用于直接导入 JSON 而无需解析它。
从 './foo.json'导入json assert { type : 'json' }; 控制台。日志(json.answer );// 42
- RegExp match indices :获取正则表达式匹配和捕获组的开始和结束索引。这适用
RegExp.exec()
于String.match()
和String.matchAll()
。
const matchObj = /(test+)(hello+)/ d. exec ( '开始-testesthello-停止' ); // 以前: 控制台。日志(匹配对象?索引); // 新: if (matchObj) { // 整个匹配的开始和结束索引(之前我们只有开始)。 控制台。log (matchObj.indices [ 0 ] ); // [9, 18] // 捕获组的开始和结束索引。 控制台。log (matchObj.indices [ 1 ] ); // [9, 13] 控制台. log (matchObj.indices [ 2 ] ); // [13, 18] }
- 负索引(.at(-1)):索引数组或字符串时,
at()
可用于从末尾开始索引。它相当于arr[arr.length — 1])
获取一个值(但不是设置)。
控制台。log ([ 4 , 5 ].at ( - 1 )) // 5 常量数组 = [ 4 , 5 ]; 大批。在(- 1 ) = 3 处;// 语法错误:分配给右值
hasOwn
:推荐的新方法来找出对象具有哪些属性而不是使用obj.hasOwnProperty()
。它在某些边缘情况下效果更好。
const obj = {名称:'测试' }; 控制台。log ( Object . hasOwn (obj, 'name' )); // 真正的 控制台。log ( Object . hasOwn (obj, '性别' )); // 错误的
- 错误原因:现在可以为错误指定可选原因,允许在重新抛出时指定原始错误。
尝试{ 尝试{ connectToDatabase (); } catch (err) { throw new Error ( '连接到数据库失败。' , { cause : err }); } } catch (err) { 控制台. 日志(错误。原因);// ReferenceError: connectToDatabase 未定义 }
Future(已经可以与 TypeScript 4.9 一起使用)
- 自动访问器:自动将属性设为私有并为其创建获取/设置访问器。
类 Person { 访问者名称:字符串; 构造函数(名称:字符串){ 这个。名字=名字; 控制台。log ( this.name ) // 'test' } } const person = new Person ( ' test' ) ;
打字稿
基础知识(进一步介绍的上下文)
- 泛型:将类型传递给其他类型。这允许类型被泛化但仍然是类型安全的。总是喜欢这个而不是使用
any
orunknown
。
// WITHOUT: function getFirstUnsafe ( list: any [] ): any { return list[ 0 ]; } const firstUnsafe = getFirstUnsafe ([ 'test' ]); // 键入任何 // WITH: function getFirst< Type >( list : Type []): Type { return list[ 0 ]; } const first = getFirst< string >([ 'test' ]); // 输入为字符串 // 在这种情况下,参数甚至可以被删除,因为它是从参数中推断出来的。 const firstInferred = getFirst ([ 'test' ]); // typed as string // 接受为泛型的类型也可以使用 `extends` 进行限制。Type 通常也缩写为 T。class List < T extends string | number > { 私有 列表:T[] = []; 得到(键:数字):T { 返回 这个。列表[键]; } 推(价值:T):无效{ 这个。列表。推(值); } } const list = new List <字符串>(); 列表。推( 9 ); // 类型错误:“数字”类型的参数不可分配给“字符串”类型的参数。 const booleanList = new List < boolean >(); // 类型错误:类型 'boolean' 不满足约束 'string | 数字'。
过去(仍然相关的旧介绍)
- 实用程序类型:TypeScript 包含许多实用程序类型,这里解释了一些最有用的类型。
接口 测试{ 名称:字符串; 年龄:人数; } // Partial 实用程序类型使所有属性都是可选的。 type TestPartial = Partial <测试>; // 输入为 { name?: string | 不明确的; 年龄?:数字 | 不明确的; } // Required 实用程序类型做相反的事情。 类型 TestRequired = Required < TestPartial >; // 输入为 { name: string; 年龄:数字;} // Readonly 实用程序类型使所有属性只读。 类型 TestReadonly =只读<测试>;// 类型为 { readonly name: string; readonly age: string } // Record 实用程序类型允许简单定义对象/地图/字典。最好尽可能索引签名。 const config : Record < string , boolean > = { option : false , anotherOption : true }; // Pick 实用程序类型仅获取指定的属性。 输入 TestLess = Pick < Test , 'name' >; // 输入为 { name: string; } 输入 TestBoth = Pick < Test , 'name' | '年龄' >; // 输入为 { name: string; 年龄:字符串;} // Omit 实用程序类型忽略指定的 properties.type type TestFewer = Omit < Test , 'name' >; // 输入为 { age: string; } type TestNone = Omit < Test , '名称' | '年龄' >; // typed as {} // Parameters 实用程序类型获取函数类型的参数。 value: string , anotherValue: number ): string { return 'test' ; } type Params = Parameters < typeof doSmth>; // typed as [value: string, anotherValue: number] // ReturnType 实用程序类型获取函数类型的返回类型。 type Return = ReturnType < typeof doSmth>; // typed as string // 还有很多,下面会介绍其中的一些。
- 条件类型:有条件地根据某种类型是否匹配/扩展另一种类型来设置类型。它们可以像 JavaScript 中的条件(三元)运算符一样阅读。
// 如果是数组,则只提取数组类型,否则返回相同类型。 输入 Flatten <T> = T extends any [] ? T[数] : T; // 提取出元素类型。 类型 Str = Flatten < string []>; // 键入为字符串 // 单独保留类型。 type Num = Flatten < number >; // 输入为数字
- 用条件类型推断:不是所有的泛型类型都需要消费者指定,有些也可以从代码中推断出来。要具有基于推断类型的条件逻辑,
infer
需要关键字。它以某种方式定义了临时推断类型变量。
// 从前面的例子开始,这样可以写得更干净。 type FlattenOld <T> = T extends any [] ? T[数] : T; // 除了索引数组之外,我们还可以从数组中推断出 Item 类型。 输入 Flatten <T> = T extends (infer Item )[] ? 项目: T; // 如果我们想写一个类型来获取函数的返回类型,否则是未定义的,我们也可以推断。 类型 GetReturnType < Type > = Type extends (... args : any[]) => 推断返回?返回:未定义; 输入 Num = GetReturnType < () => number >; // 输入为数字 type Str = GetReturnType < ( x: string ) => string >; // 类型为字符串 类型 Bools = GetReturnType < ( a: boolean , b: boolean ) => void >; // 类型为未定义
- 元组可选元素和 Rest:声明元组中的可选元素 using
?
,其余基于另一种类型 using...
。
// 如果我们还不知道一个元组有多长,但它至少是一个,我们可以使用 `?` 指定可选类型。 const list : [ number , number ?, boolean ?] = []; list[ 0 ] // 输入为数字 list[ 1 ] // 输入为数字 | undefined list[ 2 ] // 类型为布尔值 | undefined list[ 3 ] // 类型错误:长度为“3”的元组类型“[number, (number | undefined)?, (boolean | undefined)?]”在索引“3”处没有元素。 // 我们也可以将元组基于现有类型。 // 如果我们想在开始时填充一个数组,我们可以使用剩余运算符 `...` 来做到这一点。 函数padStart<T extends any []>( arr : T, pad : string ): [ string , ...T] { return [pad, ...arr]; } const padded = padStart ([ 1 , 2 ], 'test' ); // 输入为 [string, number, number]
- 抽象类和方法:可以声明类和其中的方法以
abstract
防止它们被实例化。
抽象 类 Animal { abstract makeSound (): void ; 移动():无效{ 控制台。日志('漫游地球......'); } } // 扩展时需要实现抽象方法。 class Cat extends Animal {} // 编译错误:非抽象类“Cat”未实现从类“Animal”继承的抽象成员“makeSound”。 类 狗 扩展 动物{ makeSound(){ 控制台。日志( 'woof' ); } } // 抽象类不能被实例化(如接口),抽象方法也不能被调用。 新 动物();// 编译错误:无法创建抽象类的实例。 const dog = new Dog ()。制作声音();//“汪”
- 构造函数签名:在类声明之外定义构造函数的类型。在大多数情况下不应使用,可以使用抽象类代替。
接口 MyInterface { 名称:字符串; } interface ConstructsMyInterface { new ( name : string ): MyInterface ; } 类 测试 实现 MyInterface { 名称:字符串; 构造函数(名称:字符串){ 这个。名字=名字; } } class AnotherTest { age : number ; } function makeObj ( n: ConstructsMyInterface ) { return new n ( 'hello!' ); } const obj = makeObj (测试); // 类型为 Test const anotherObj = makeObj ( AnotherTest ); // 类型错误:“typeof AnotherTest”类型的参数不可分配给“ConstructsMyInterface”类型的参数。
- ConstructorParameters 实用程序类型:TypeScript 辅助函数,它从构造函数类型(但不是类)获取构造函数参数。
// 如果我们想获取 makeObj 函数的构造函数参数怎么办。 接口 MyInterface { 名称:字符串; } interface ConstructsMyInterface { new ( name : string ): MyInterface ; } 类 测试 实现 MyInterface { 名称:字符串; 构造函数(名称:字符串){ 这个。名字=名字; } } 函数 makeObj (测试: ConstructsMyInterface, ...args: ConstructorParameters<ConstructsMyInterface> ) { 返回 新 测试(...args); } makeObj (测试); // 类型错误:需要 2 个参数,但得到 1 个 。const obj = makeObj ( Test , 'test' ); // 类型为测试
打字稿 4.0
- 可变元组类型:元组中的其余元素现在可以是通用的。现在也允许使用多个 rest 元素。
// 如果我们有一个组合两个未定义长度和类型的元组的函数怎么办?我们如何定义返回类型? // PREVIOUSLY: // 我们可以写一些重载。 声明 函数 concat ( arr1: [], arr2: [] ): []; 声明 函数concat<A>( arr1 : [A], arr2 : []): [A]; 声明 函数concat<A, B>( arr1 : [A], arr2 : [B]): [A, B]; 声明 函数concat<A, B, C>( arr1 : [A], arr2 : [B, C]): [A, B, C]; 声明 函数concat<A, B, C, D>(arr1 : [A], arr2 : [B, C, D]): [A, B, C, D]; 声明 函数concat<A, B>( arr1 : [A, B], arr2 : []): [A, B]; 声明 函数concat<A, B, C>( arr1 : [A, B], arr2 : [C]): [A, B, C]; 声明 函数concat<A, B, C, D>( arr1 : [A, B], arr2 : [C, D]): [A, B, C, D]; 声明 函数concat<A, B, C, D, E>( arr1 : [A, B], arr2 : [C, D, E]): [A, B, C, D, E]; 声明 函数concat<A, B, C>( arr1: [A, B, C], arr2 : []): [A, B, C]; 声明 函数concat<A, B, C, D>( arr1 : [A, B, C], arr2 : [D]): [A, B, C, D]; 声明 函数concat<A, B, C, D, E>( arr1 : [A, B, C], arr2 : [D, E]): [A, B, C, D, E]; 声明 函数concat<A, B, C, D, E, F>( arr1 : [A, B, C], arr2 : [D, E, F]): [A, B, C, D, E, F ]; // 即使每个只有三个项目,这也不是最理想的。 // 相反,我们可以组合类型。 声明 函数concatBetter<T, U>( arr1 : T[], arr2: U[]): (T | U)[]; // 但这种类型为 (T | U)[] // 新: // 使用可变元组类型,我们可以轻松定义它并保留有关长度的信息。 声明 函数concatNew<T extends Arr , U extends Arr >( arr1 : T, arr2 : U): [...T, ...U]; const tuple = concatNew ([ 23 , 'hey' , false ] as [ number , string , boolean ], [ 5 , 99 , 20 ]作为[数字,数字,数字]); 控制台。日志(元组[ 0 ]);// 23 const 元素: number = tuple[ 1 ]; // 类型错误:类型“string”不可分配给类型“number”。 控制台。日志(元组[ 6 ]);// 类型错误:长度为 '6' 的元组类型 '[23, "hey", false, 5, 99, 20]' 在索引 '6' 处没有元素。
- 标记的元组元素:元组元素现在可以命名为
[start: number, end: number]
。如果其中一个元素被命名,则所有这些元素都必须被命名。
输入 Foo = [ first : number , second?: string , ... rest : any []]; // 这允许在这里正确命名参数,它也会显示在编辑器中。 声明 函数 someFunc ( ...args: Foo );
- 从构造函数推断类属性:在构造函数中设置属性时,现在可以推断类型,不再需要手动设置。
class Animal { // 在构造函数中赋值时不需要设置类型。 姓名; 构造函数(名称:字符串){ 这个。名字=名字; 控制台。日志(这个。名字);// 类型为字符串 } }
- JSDoc @deprecated 支持:JSDoc/TSDoc
@deprecated
标签现在可以被 TypeScript 识别。
/** @deprecated消息 */ type Test = string ; 常量 测试:测试= 'dfadsf';// 类型错误:“测试”已弃用。
打字稿 4.1
- 模板字面量类型:定义字面量类型时,可以通过模板来指定类型,例如
${Type}
. 这允许构造复杂的字符串类型,例如在组合多个字符串文字时。
type VerticalDirection = '顶部' | '底部' ; 类型 HorizontalDirection = '左' | '对' ; 输入 Direction = ` ${VerticalDirection} ${HorizontalDirection} ` ; const dir1 : Direction = '左上角' ; const dir2 : Direction = 'left' ; // 类型错误:类型“left”不可分配给类型“top left” | “右上角” | “左下角” | “ Direction = '左上角' ; // 类型错误:类型 '"left top"' 不可分配给类型 '"top left" | “右上角” | “左下角” | “右下”。 // 这也可以与泛型和新的实用程序类型结合使用。 declare function makeId<T extends string , U extends string >( first : T, second : U): ` ${Capitalize<T>} - ${Lowercase<U>} ` ;
- 映射类型中的键重映射:重新键入映射类型,同时仍然使用它们的值,如
[K in keyof T as NewKeyType]: T[K]
.
// 假设我们想重新格式化一个对象,但在其 ID 前加上下划线。 const obj = { value1 : 0 , value2 : 1 , value3 : 3 }; const newObj : { [ keyof typeof obj中的属性 as `_ ${Property} ` ]: number }; // 输入为 { _value1: number; _value2:数字;值 3:数字;}
- 递归条件类型:在其定义本身内部使用条件类型。这允许有条件地解包无限嵌套值的类型。
输入 Awaited <T> = T extends PromiseLike <infer U> ? 等待<U> : T; 输入 P1 = Awaited <字符串>; // 输入为字符串 类型 P2 = Awaited < Promise < string >>; // 输入为字符串 类型 P3 = Awaited < Promise < Promise < string >>>; // 输入为字符串
- JSDOC @see 标签的编辑器支持
@see variable/type/link
:编辑器现在支持JSDoc/TSDoc标签。
const原始值 = 1 ; /** * 另一个值的副本 * @see originalValue */ const value = originalValue;
- tsc --
explainFiles
:该--explainFiles
选项可用于 TypeScript CLI 以解释哪些文件是编译的一部分以及原因。这对于调试很有用。警告:对于大型项目或复杂的设置,这将生成大量输出,而不是使用tsc --explainFiles | less
或类似的东西。
tsc --explainFiles << output ../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es5.d.ts 通过文件中的“es5”引用的库'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts' 通过文件 '../.es5' 引用的库。 ./.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts' ../../.asdf/installs/nodejs/16.13.1/。 npm/lib/node_modules/typescript/lib/lib.es2015.d.ts 库通过'es2015'从文件'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/ typescript/lib/lib.es2016.d.ts' 通过文件 '../../.asdf/installs/nodejs/16.13.1/ 中的 'es2015' 引用的库。npm/lib/node_modules/typescript/lib/lib.es2016.d.ts' ../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts 库通过'es2016'从文件'../../ .asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts' 通过'es2016'从文件'../../.asdf/installs/引用的库nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts' ... 输出
- Destructured Variables Can Be Explicitly Marked as Unused:解构时,可以使用下划线将变量标记为未使用。这可以防止 TypeScript 抛出“未使用的变量”错误。
const [_first, second] = [ 3 , 5 ]; 控制台。日志(第二); // 或者更短 const [_, value] = [ 3 , 5 ]; 控制台。日志(值);
打字稿 4.3
- 属性上的单独写入类型:定义设置/获取访问器时,写入/设置类型现在可以不同于读取/获取类型。这允许设置器接受相同值的多种格式。
类 测试{ private _value : number ; 获取 值():数字{ 返回 这个。_值; } 设置 值(值:数字|字符串){ 如果(类型值 === '数字'){ 这。_value = 值; 返回; } 这个。_value = parseInt(值,10); } }
- override:将继承的类方法显式标记为 overrides using
override
,这样当父类发生变化时,TypeScript 可以通知您父方法不再存在。这允许更安全的复杂继承模式。
class Parent { getName (): string { return 'name' ; } } class NewParent { getFirstName (): string { return 'name' ; } } class Test extends Parent { override getName (): string { return 'test' ; } } class NewTest extends NewParent { override getName ():字符串{ // 类型错误:该成员不能有 'override' 修饰符,因为它未在基类 'NewParent' 中声明。 返回 “测试”; } }
- 静态索引签名:在类上使用静态属性时,现在也可以使用
static [propName: string]: string
.
// 以前: 类 测试{} 测试。测试= '' ; // 类型错误:类型“typeof Test”上不存在属性“test”。 // 新: 类 NewTest { static [ key : string ]: string ; } 新测试。测试= '' ;
- JSDOC @link 标签的编辑器支持
{@link variable/type/link}
:现在支持JSDoc/TSDoc内联标签,并将在编辑器中显示和解析。
const原始值 = 1 ; /** * { @link originalValue} 的副本 */ const value = originalValue;
打字稿 4.4
- 精确可选属性类型 (--exactOptionalPropertyTypes):使用编译器标志
--exactOptionalPropertyTypes
(或 intsconfig.json
)赋值undefined
不再允许隐式允许的属性undefined
(例如property?: string
)。相反undefined
需要明确允许像property: string | undefined
.
类 测试{ 名称?:字符串; 年龄:号码| 未定义; } const测试 =新 测试(); 测试。名称=未定义;// 类型错误:类型“undefined”不可分配给“exactOptionalPropertyTypes: true”的类型“string”。考虑将“未定义”添加到目标类型。 测试。年龄=未定义; 控制台。日志(测试。年龄);// 不明确的
打字稿 4.5
- Awaited 类型和 Promise 改进:新的
Awaited<>
实用程序类型从无限嵌套的 Promises 中提取值类型(就像await
对值所做的那样)。这也改进了Promise.all()
.
// 假设我们想要一个通用的等待值。 // 我们可以为此使用 Awaited 实用程序类型(它的源代码是前面示例的一部分), // 因此无限嵌套的 Promises 都解析为它们的值。 输入 P1 = Awaited <字符串>; // 输入为字符串 类型 P2 = Awaited < Promise < string >>; // 输入为字符串 类型 P3 = Awaited < Promise < Promise < string >>>; // 输入为字符串
- 导入名称上的类型修饰符:在普通(非
import type
)导入语句中,type
关键字可用于表示该值只应为类型编译导入(并且可以删除)。
// PREVIOUSLY: // 导入类型的最佳方法是使用 `import type` 关键字来防止它们在编译后实际被导入。从'./file' 导入{ something} ;从'./file'导入类型{ SomeType } ;// 对于同一个文件,这需要两个导入语句。// 新:// 现在这可以组合成一个语句。import { something, type SomeType } from './file' ;
- const Assertions:定义常量时
as const
可用于将它们准确地键入为文字类型。这有很多用例,可以更轻松地进行准确的输入。它还生成 objects 和 arraysreadonly
,以防止常量对象发生突变。
// PREVIOUSLY: const obj = { name : 'foo' , value : 9 , toggle : false }; // 输入为 { name: string; 值:数字;切换:布尔值;} // 任何值都可以赋值,因为它们通常是有类型的。 对象。名称= '酒吧' ; const tuple = [ 'name' , 4 , true ]; // typed as (string | number | boolean)[] // 无法从类型中确定长度和确切类型。可以在任何地方分配任何值。 元组[ 0 ] =0 ; 元组[ 3 ] = 0 ; // 新: const objNew = { name : 'foo' , value : 9 , toggle : false } as const ; // 输入为 { readonly name: "foo"; 只读值:9;只读切换:假;} // 不能赋值(因为它被定义为“foo”(而且也是只读的))。 对象新建。名称= '酒吧' ; // 类型错误:无法分配给“名称”,因为它是只读属性。 const tupleNew = [ '名字' ,4、真]作为 常量;// typed as readonly ["name", 4, true] // 长度和确切类型现在已定义,不能分配任何内容(因为它被定义为文字(并且也是只读的))。 tupleNew[ 0 ] = 0 ; // 类型错误:无法分配给“0”,因为它是只读属性。 tupleNew[ 3 ] = 0 ; // 类型错误:'readonly ["name", 4, true]' 类型的索引签名只允许读取。
- 类中方法的片段完成:当一个类继承方法类型时,它们现在被建议为编辑器中的片段。
打字稿 4.6
- 索引访问推理改进当使用键直接索引类型时,当类型位于同一对象上时,该类型现在会更加准确。此外,这只是一个很好的例子,展示了现代 TypeScript 的可能性。
接口 AllowedTypes { 'number' : number ; '字符串':字符串; '布尔值':布尔值; } // Record 从允许的类型中指定种类和值类型。 type UnionRecord < AllowedKeys extends keyof AllowedTypes > = { [ Key in AllowedKeys ]: { kind : Key ; 值:AllowedTypes [键]; 日志值:(值:AllowedTypes[Key] ) => void ; } }[允许键]; // 函数 logValue 只接受 Record 的值。 function processRecord< Key extends keyof AllowedTypes >( record : UnionRecord < Key >) { 记录。logValue ( record.value); } processRecord ({ kind : 'string' , value : 'hello!' , // 用于隐式具有类型 string | number | boolean 的值, // 但现在被正确地推断为字符串。 日志值:值=> { 控制台。log (value.toUpperCase ( )); } });
- TypeScript Trace Analyzer (--generateTrace):该
--generateTrace <Output folder>
选项可用于 TypeScript CLI 生成包含有关类型检查和编译过程的详细信息的文件。这有助于优化复杂类型。
tsc --generateTrace trace cat trace/trace.json <<输出 [ {"name":"process_name","args":{"name":"tsc"},"cat":"__metadata","ph": "M","ts":...,"pid":1,"tid":1}, {"name":"thread_name","args":{"name":"Main"},"cat ":"__metadata","ph":"M","ts":...,"pid":1,"tid":1}, {"name":"TracingStartedInBrowser","cat":" 禁用-by-default-devtools.timeline","ph":"M","ts":...,"pid":1,"tid":1}, {"pid":1,"tid" : 1,“酸碱度”:"B","cat":"program","ts":...,"name":"createProgram","args":{"configFilePath":"/...","rootDir":" /..."}}, {"pid":1,"tid":1,"ph":"B","cat":"parse","ts":...,"name":" createSourceFile","args":{"path":"/..."}}, {"pid":1,"tid":1,"ph":"E","cat":"parse", "ts":...,"name":"createSourceFile","args":{"path":"/..."}},"rootDir":"/..."}}, {"pid":1,"tid":1,"ph":"B","cat":"parse","ts":..., "name":"createSourceFile","args":{"path":"/..."}}, {"pid":1,"tid":1,"ph":"E","cat" :"解析","ts":...,"名称":"createSourceFile","args":{"路径":"/..."}},"rootDir":"/..."}}, {"pid":1,"tid":1,"ph":"B","cat":"parse","ts":..., "name":"createSourceFile","args":{"path":"/..."}}, {"pid":1,"tid":1,"ph":"E","cat" :"解析","ts":...,"名称":"createSourceFile","args":{"路径":"/..."}},cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}}, {“pid”:1,“tid”:1,“ph”:“X”,“cat”:“程序”,“ts”:...,“name”:“resolveModuleNamesWorker”,“dur”:。 ..,"args":{"containingFileName":"/..."}}, ... 输出 cat trace/types.json <<输出 [{"id":1,"intrinsicName":"any", "recursionId":0,"flags":["..."]}, {"id":2,"intrinsicName":"any","recursionId":1,"flags":["..." ]}, {"id":3,"intrinsicName":"any","recursionId":2,"flags":["..."]}, {"id":4,"intrinsicName":" error ","recursionId":3,"flags":["... "]}, {"id":5,"intrinsicName":"unresolved","recursionId":4,"flags":["..." ]}, {"id":6,"intrinsicName":"any","recursionId":5,"flags":["..."]}, {"id":7,"intrinsicName":" 内在","recursionId":6,"flags":["..."]},intrinsicName":"intrinsic","recursionId":6,"flags":["..."]},intrinsicName":"intrinsic","recursionId":6,"flags":["..."]}, {"id":8,"intrinsicName":"unknown","recursionId":7,"flags":["..."]}, {"id":9,"intrinsicName":"unknown", " recursionId":8,"flags":["..."]}, {"id":10,"intrinsicName":"undefined","recursionId":9,"flags":["..."] }, {"id":11,"intrinsicName":"undefined","recursionId":10,"flags":["..."]}, {"id":12,"intrinsicName":"null " ,"recursionId":11,"flags":["..."]}, {"id":13,"intrinsicName":"string","recursionId":12,"flags":["... “]}, ... 输出
打字稿 4.7
- Node.js 中的 ECMAScript 模块支持:当使用 ES 模块而不是 CommonJS 时,TypeScript 现在支持指定默认值。在
tsconfig.json
.
... “compilerOptions” : [ ... “模块” : “es2020” ] ...
- type in package.json
type
: in 的字段package.json
可以设置为"module"
,这是使用 node.js 和 ES Modules 所需要的。在大多数情况下,这对 TypeScript 来说已经足够了,不需要上面的编译器选项。
... “类型” : “模块” ...
- 实例化表达式:实例化表达式允许在引用值时指定类型参数。这允许在不创建包装器的情况下缩小泛型类型。
类 列表<T> { 私有 列表:T[] = []; 得到(键:数字):T { 返回 这个。列表[键]; } 推(价值:T):无效{ 这个。列表。推(值); } } function makeList<T>( items : T[]): List <T> { const list = new List <T>(); } 项目。forEach (项目=>列表。推(项目)); 返回列表; } // 假设我们想要一个创建列表但只允许特定值的函数。 // PREVIOUSLY: // 我们需要手动定义一个包装函数并传递参数。 function makeStringList ( text: string [] ) { return makeList (text); } } // 新: // 使用实例化表达式,这要容易得多。 const makeNumberList = makeList<数字>;
- extends Constraints on infer Type Variables:在条件类型中推断类型变量时,现在可以使用
extends
.
// 假设我们想要输入一个类型,如果它是一个字符串,它只获取数组的第一个元素。 // 我们可以为此使用条件类型。 // 以前: type FirstIfStringOld <T> = T extends [infer S, ... unknown []] ? S扩展 字符串? S:从不 :从不; // 但这需要两个嵌套的条件类型。我们也可以合二为一。 type FirstIfString <T> = T extends [ string , ... unknown []] // 从 `T` 中获取第一个类型 ?T[ 0] :从不; // 这仍然不是最优的,因为我们需要为正确的类型索引数组。 // 新: // 在推断类型变量上使用扩展约束,这可以更容易地声明。 输入 FirstIfStringNew <T> = T extends [infer S extends string , ... unknown []] ? S :从不; // 请注意,输入的工作方式与之前相同,这只是一种更简洁的语法。 输入A = FirstIfStringNew <[字符串,数字,数字]>;// 类型为字符串 type B = FirstIfStringNew <[ "hello" , number , number ]>; // 输入为“hello” type C = FirstIfStringNew <[ “hello” | “世界”,布尔值]>; // 输入为“你好” | “世界” 类型D = FirstIfStringNew <[ boolean , number , string ]>; // 输入为从不
- 类型参数的可选方差注释:泛型在检查它们是否“匹配”时可以有不同的行为,例如,对于 getter 和 setter,允许继承是相反的。为了清楚起见,现在可以选择指定它。
// 假设我们有一个接口/类扩展了另一个。 界面 动物{ animalStuff : any ; } interface Dog extends Animal { dogStuff : any ; } // 我们有一些通用的“getter”和“setter”。 输入 Getter <T> = () => T; 类型 Setter <T> = ( value: T ) => void ; // 如果我们想知道 Getter<T1> 是否匹配 Getter<T2> 或 Setter<T1> 是否匹配 Setter<T2>,这取决于协方差。 useAnimalGetter ( getter: Getter<Animal> ) { getter (); } // 现在我们可以将 Getter 传递给函数。 useAnimalGetter (( () => ({ animalStuff : 0 })作为 动物)); // 这显然有效。 // 但是如果我们想使用返回 Dog 的 Getter 怎么办? useAnimalGetter (( () => ({ animalStuff : 0 , dogStuff : 0 }) as Dog )); // 这也适用,因为狗也是动物。 函数 useDogGetter ( getter: Getter<Dog> ) { getter (); } // 如果我们对 useDogGetter 函数尝试相同的操作,我们将不会得到相同的行为。 useDogGetter (( () => ({ animalStuff : 0 })作为 动物)); // 类型错误:属性“dogStuff”在“Animal”类型中缺失,但在“Dog”类型中是必需的。 // 这是行不通的,因为需要一只狗,而不仅仅是一只动物。 useDogGetter (( () => ({ animalStuff : 0 , dogStuff : 0 })作为 狗)); // 但是,这是有效的。 // 直觉上,我们可能希望 Setter 的行为相同,但事实并非如此。 函数 setAnimalSetter ( setter: Setter<Animal>, value: Animal ) { setter (value); } // 如果我们传递一个相同类型的 Setter,它仍然有效。 setAnimalSetter ( ( value: Animal ) => {}, { animalStuff : 0 }); 函数 setDogSetter ( setter: Setter<Dog>, value: Dog ) { setter (value); } // 这里也一样。 设置狗设置器((价值:狗)=> {},{ animalStuff:0,dogStuff:0 }); // 但如果我们将 Dog Setter 传递给 setAnimalSetter 函数,则行为与 Getters 相反。 setAnimalSetter ( ( value: Dog ) => {}, { animalStuff : 0 , dogStuff : 0 }); // 类型错误:'(value: Dog) => void' 类型的参数不可分配给'Setter<Animal>' 类型的参数。 // 这次它以相反的方式工作。 setDogSetter((值:动物)=>{}, { animalStuff : 0 , dogStuff : 0 }); // 新: // 要向 TypeScript 发出信号(不需要但有助于提高可读性),请使用新的类型参数可选方差注释。 输入 GetterNew <out T> = () => T; 输入 SetterNew < in T> = ( value: T ) => void ;
- 分辨率自定义
moduleSuffixes
:当使用具有自定义文件后缀的环境(例如.ios
本地应用程序构建)时,现在可以为 TypeScript 指定这些后缀以正确解析导入。在tsconfig.json
.
... "compilerOptions" : [ ... "moduleSuffixes" : [ ".ios" , ".native" , "" ] ] ...
从'./foo'导入*作为foo ;// 这首先检查 ./foo.ios.ts、./foo.native.ts,最后检查 ./foo.ts。
- 在编辑器中转到源定义:在编辑器中,新的“转到源定义”菜单选项可用。它类似于“转到定义”,但更喜欢
.ts
和.js
文件而不是类型定义(.d.ts
)。
打字稿 4.9
- 满足运算符:该
satisfies
运算符允许检查与类型的兼容性,而无需实际分配该类型。这允许在保持兼容性的同时保持更准确的推断类型。
// PREVIOUSLY: // 假设我们有一个存储各种项目及其颜色的对象/地图/字典。 const obj = { fireTruck : [ 255 , 0 , 0 ], bush : '#00ff00' , ocean : [ 0 , 0 , 255 ] } // 输入为 { fireTruck: number[]; 布什:字符串;海洋:数字[];} // 这会隐式键入属性,以便我们可以对数组和字符串进行操作。 const rgb1 = 对象。救火车[ 0 ];// 类型为数字 const十六进制=对象。灌木丛;// 类型为字符串 // 假设我们只想允许某些对象。 // 我们可以使用 Record 类型。 const oldObj : Record <字符串, [数字,数字,数字] | string > = { fireTruck : [ 255 , 0 , 0 ], bush : '#00ff00' , ocean : [ 0 , 0 , 255 ] }// 输入为 Record<string, [number, number, number] | string> // 但现在我们丢失了属性的类型。 常量oldRgb1 = oldObj。救火车[ 0 ];// 类型为字符串 | 数字 常量oldHex = oldObj。灌木丛;// 类型为字符串 | number // 新: // 使用 satisfies 关键字,我们可以检查与类型的兼容性而无需实际分配它。 const newObj = { fireTruck : [ 255 , 0 , 0 ], bush : '#00ff00' , ocean : [ 0 , 0, 255 ] } 满足Record < string , [ number , number , number ] | string > // 输入为 { fireTruck: [number, number, number]; 布什:字符串;海洋:[数字,数字,数字];} // 而且我们仍然有属性的类型,数组甚至通过成为元组变得更加准确。 const newRgb1 = newObj。救火车[ 0 ];// 输入为数字 const newRgb4 = newObj. 消防车[ 3 ];// 类型错误:长度为“3”的元组类型“[number, number, number]”在索引“3”处没有元素。 const newHex = newObj。灌木丛;// 输入为字符串
- 编辑器的“删除未使用的导入”和“排序导入”命令:在编辑器中,新命令(和自动修复)“删除未使用的导入”和“排序导入”使管理导入更加容易。
标签:JavaScript, 编程,Web开发 来源: