欢迎转载或分享,转载请注明做者及出处!javascript
star
,后面会持续更新:github地址最近刚入职新公司,正好部门每一个新人都须要作技术分享,这里借这个机会把本身的知识体系梳理一遍。css
本来题目设定为是 Typescript
高级用法介绍,可是考虑掘金有不少关于 Typescript
高级用法 相关的分享,写的都很是好,这里就不作重复性工做了。因此把题目改为了 深刻理解 Typescript
高级用法,注重 理解 而非 使用。html
前言:这里的标题看起来是 "高级用法",很多同窗可能就表示被劝退了。其实
Typescript
做为一门强类型
编程语言,最具特点的就是他的类型表达能力,这是不少完备的后端语言都难以媲美的说的很对,但PHP是最好的语言,因此若是你搞懂了他的类型系统,对未来的平常开发必定是大有裨益的,但过于灵活的类型系统也注定了Typescript
没法成为一门纯粹的静态语言,不过每一行代码都有代码提示他不香嘛?前端
为了更好的阅读体验,建议在宽屏浏览器端阅读。vue
阅读本文须要具有的基础知识。java
本文的定位为理解高级用法,故不会涉及过多基础知识相关的讲解,须要读者本身去完善这方面的知识储备。node
Javascript
或其余语言编程经验。Typescript
实际使用经验,最好在正经项目中完整地使用过。Typescript
基础语法以及常见关键字地做用。Typescript
的 类型系统
架构有一个最基本的了解。初用 Typescript
开发的同窗必定有这样的困扰:react
Typescript
能够提升代码可维护性,结果却发现徒增了很多开发负担。as any
默默等待代码 review
时的公开处刑。Typescript
成了首要难题,思索片刻决定投靠的 Anyscript
,快速开发业务逻辑,待到春暖花开时再回来补充类型。双倍的工做量,双倍的快乐只有本身才懂。为了不以上悲剧的发生或者重演,咱们只有在对它有更加深入的理解以后,才能在开发时游刃有余、在撸码时纵横捭阖。jquery
思考题:有人说
Typescript
=Type
+Javascript
,那么抛开Javascript
不谈,这里的Type
是一门完备的编程语言吗?webpack
有过编程经验的同窗都知道,函数是一门编程语言中最基础的功能之一,函数是过程化、面向对象、函数式编程中程序封装的基本单元,其重要程度不言而喻。
函数能够帮助咱们作不少事,好比 :
Typescript
中类型系统中的的函数被称做 泛型操做符
,其定义的简单的方式就是使用 type
关键字:
// 这里咱们就定义了一个最简单的泛型操做符
type foo<T> = T;
复制代码
这里的代码如何理解呢,其实这里我把代码转换成你们最熟悉的 Javascript
代码其实就不难理解了:
// 把上面的类型代码转换成 `JavaScript` 代码
function foo(T) {
return T
}
复制代码
那么看到这里有同窗内心要犯嘀咕了,心想你这不是忽悠我嘛?这不就是 Typescript
中定义类型的方式嘛?这玩意儿我可太熟了,这玩意儿不就和 interface
同样的嘛,我还知道 Type
关键字和 interface
关键字有啥细微的区别呢!
嗯,同窗你说的太对了,不过你不要着急,接着听我说,其实类型系统中的函数还支持对入参的约束。
// 这里咱们就对入参 T 进行了类型约束
type foo<T extends string> = T;
复制代码
那么把这里的代码转换成咱们常见的 Typescript
是什么样子的呢?
function foo(T: string) {
return T
}
复制代码
固然啦咱们也能够给它设置默认值:
// 这里咱们就对入参 T 增长了默认值
type foo<T extends string = 'hello world'> = T;
复制代码
那么这里的代码转换成咱们常见的 Typescript
就是这样的:
function foo(T: string = 'hello world') {
return T
}
复制代码
看到这里确定有同窗火烧眉毛地想要提问了:那能不能像 JS 里的函数同样支持剩余参数呢?
很遗憾,目前暂时是不支持的,可是在咱们平常开发中必定是有这样的需求存在的。那就真的没有办法了嘛?其实也不必定,咱们能够经过一些骚操做来模拟这种场景,固然这个是后话了,这里就不做拓展了。
人生总会面临不少选择,编程也是同样。
——我瞎编的
条件判断也是编程语言中最基础的功能之一,也是咱们平常撸码过程成最经常使用的功能,不管是 if else
仍是 三元运算符
,相信你们都有使用过。
其实这在 Typescript
官方文档被称为 条件类型(Conditional Types)
,定义的方法也很是简单,就是使用 extends
关键字。
T extends U ? X : Y;
复制代码
这里相信聪明的你一眼就看出来了,这不就是 三元运算符
嘛!是的,并且这和三元运算符的也发也很是像,若是 T extends U
为 true
那么 返回 X
,不然返回 Y
。
结合以前刚刚讲过的 "函数",咱们就能够简单的拓展一下:
type num = 1;
type str = 'hello world';
type IsNumber<N> = N extends number ? 'yes, is a number' : 'no, not a number';
type result1 = IsNumber<num>; // "yes, is a number"
type result2 = IsNumber<str>; // "no, not a number"
复制代码
这里咱们就实现了一个简单的带判断逻辑的函数。
看到这里确定有同窗就笑了,这还不简单,就举例来讲,Typescript
中最多见数据类型就是 数组(Array)
或者 元组(tuple)
。
同窗你说的很对,那你知道如何对 元组类型
做 push
、pop
、shift
、unshift
这些行为操做吗?
其实这些操做都是能够被实现的:
// 这里定义一个工具类型,简化代码
type ReplaceValByOwnKey<T, S extends any> = { [P in keyof T]: S[P] };
// shift action
type ShiftAction<T extends any[]> = ((...args: T) => any) extends ((arg1: any, ...rest: infer R) => any) ? R : never; // unshift action type UnshiftAction<T extends any[], A> = ((args1: A, ...rest: T) => any) extends ((...args: infer R) => any) ? R : never; // pop action type PopAction<T extends any[]> = ReplaceValByOwnKey<ShiftAction<T>, T>; // push action type PushAction<T extends any[], E> = ReplaceValByOwnKey<UnshiftAction<T, any>, T & { [k: string]: E }>; // test ... type tuple = ['vue', 'react', 'angular']; type resultWithShiftAction = ShiftAction<tuple>; // ["react", "angular"] type resultWithUnshiftAction = UnshiftAction<tuple, 'jquery'>; // ["jquery", "vue", "react", "angular"] type resultWithPopAction = PopAction<tuple>; // ["vue", "react"] type resultWithPushAction = PushAction<tuple, 'jquery'>; // ["vue", "react", "angular", "jquery"] 复制代码
注意:这里的代码仅用于测试,操做某些复杂类型可能会报错,须要作进一步兼容处理,这里简化了相关代码,请勿用于生产环境!
相信读到这里,大部分同窗应该能够已经能够感觉到 Typescript
类型系统的强大之处了,其实这里仍是继续完善,为元组增长 concat
、map
等数组的经常使用的功能,这里不做详细探讨,留给同窗们本身课后尝试吧。
可是其实上面提到的 "数据类型" 并非我这里想讲解的 "数据类型",上述的数据类型本质上仍是服务于代码逻辑的数据类型,其实并非服务于 类型系统
自己的数据类型。
上面这句话的怎么理解呢?
无论是 数组
仍是 元组
,在广义的理解中,其实都是用来对 数据 做 批量操做,同理,服务于 类型系统
自己的数据结构,应该也能够对 类型 做 批量操做。
那么如何对 类型 做 批量操做 呢?或者说服务于 类型系统
中的 数组 是什么呢?
下面就引出了本小节真正的 "数组":联合类型(Union Types)
提及 联合类型(Union Types)
,相信使用过 Typescript
同窗的必定对它又爱又恨:
联合类型(Union Types)
会很是的方便,但想智能地推导出返回值的类型地时候却又犯了难。(...args: any[]) => void
这种毫无卵用的参数类型定义。联合类型(Union Types)
时,虽然有 类型守卫(Type guard)
,可是某些场景下依然不够好用。其实当你对它有足够的了解时,你就会发现 联合类型(Union Types)
比 交叉类型(Intersection Types)
不知道高到哪里去了,我和它谈笑风生。
既然目标是 批量操做类型,天然少不了类型的 遍历,和大多数编程语言方法同样,在 Typescript
类型系统中也是 in
关键字来遍历。
type key = 'vue' | 'react';
type MappedType = { [k in key]: string } // { vue: string; react: string; }
复制代码
你看,经过 in
关键字,咱们能够很容易地遍历 联合类型(Union Types)
,并对类型做一些变换操做。
但有时候并非全部全部 联合类型(Union Types)
都是咱们显式地定义出来的。
可使用 keyof
关键字动态地取出某个键值对类型的 key
interface Student {
name: string;
age: number;
}
type studentKey = keyof Student; // "name" | "age"
复制代码
一样的咱们也能够经过一些方法取出 元组类型
子类型
type framework = ['vue', 'react', 'angular'];
type frameworkVal1 = framework[number]; // "vue" | "react" | "angular"
type frameworkVal2 = framework[any]; // "vue" | "react" | "angular"
复制代码
看到这里,有的同窗可能要问了,你既然说 联合类型(Union Types)
能够批量操做类型,那我想把某一组类型批量映射成另外一种类型,该怎么操做呢?
方法其实有不少,这里提供一种思路,抛砖引玉一下,别的方法就留给同窗们自行研究吧。
其实分析一下上面那个需求,不难看出,这个需求其实和数组的 map
方法有点类似
// 这里的 placeholder 能够键入任何你所但愿映射成为的类型
type UnionTypesMap<T> = T extends any ? 'placeholder' : never;
复制代码
其实这里聪明的同窗已经看出来,咱们只是利用了 条件类型(Conditional Types)
,使其的判断条件老是为 true
,那么它就老是会返回左边的类型,咱们就能够拿到 泛型操做符
的入参并自定义咱们的操做。
让咱们趁热打铁,再举个具体的栗子:把 联合类型(Union Types) 的每一项映射成某个函数的 返回值。
type UnionTypesMap2Func<T> = T extends any ? () => T : never;
type myUnionTypes = "vue" | "react" | "angular";
type myUnionTypes2FuncResult = UnionTypesMap2Func<myUnionTypes>;
// (() => "vue") | (() => "react") | (() => "angular")
复制代码
相信有了上述内容的学习,咱们已经对 联合类型(Union Types)
有了一个相对全面的了解,后续在此基础之上在做一些高级的拓展,也如砍瓜切菜通常简单了。
固然除了数组,还存在其余的数据类型,例如能够用 type
或 interface
模拟 Javascript
中的 字面量对象,其特征之一就是可使用 myType['propKey']
这样的方式取出子类型。这里抛砖引玉一下,有兴趣的同窗能够自行研究。
就像常见的编程语言同样,在 Typescript
的类型系统中,也是支持 全局做用域 的。换句话说,你能够在没有 导入 的前提下,在 任意文件任意位置 直接获取到而且使用它。
一般使用 declare
关键字来修饰,例如咱们常见的 图片资源
的类型定义:
declare module '*.png';
declare module '*.svg';
declare module '*.jpg';
复制代码
固然咱们也能够在 全局做用域 内声明一个类型:
declare type str = string;
declare interface Foo {
propA: string;
propB: number;
}
复制代码
须要注意的是,如何你的模块使用了 export
关键字导出了内容,上述的声明方式可能会失效,若是你依然想要将类型声明到全局,那么你就须要显式地声明到全局:
declare global {
const ModuleGlobalFoo: string;
}
复制代码
就像 nodejs
中的模块同样,每一个文件都是一个模块,每一个模块都是独立的模块做用域。这里模块做用域触发的条件之一就是使用 export
关键字导出内容。
每个模块中定义的内容是没法直接在其余模块中直接获取到的,若是有须要的话,可使用 import
关键字按需导入。
泛型操做符是存在做用域的,还记得这一章的第一节为了方便你们理解,我把泛型操做符类比为函数吗?既然能够类比为函数,那么函数所具有的性质,泛型操做符天然也能够具有,因此存在泛型操做符做用域天然也就很好理解了。
这里定义的两个同名的 T
并不会相互影响:
type TypeOperator<T> = T;
type TypeOperator2<T> = T;
复制代码
上述是关于泛型操做符做用域的描述,下面咱们聊一聊真正的函数做用域:
类型也能够支持闭包:
function Foo<T> () {
return function(param: T) {
return param;
}
}
const myFooStr = Foo<string>();
// const myFooStr: (param: string) => string
// 这里触发了闭包,类型依然能够被保留
const myFooNum = Foo<number>();
// const myFooNum: (param: number) => number
// 这里触发了闭包,类型也会保持相互独立,互不干涉
复制代码
Typescript
中的类型也是能够支持递归的,递归相关的问题比较抽象,这里仍是举例来说解,同时为了方便你们的理解,我也会像第一节同样,把类型递归的逻辑用 Javascript
语法描述一遍。
首先来让咱们举个栗子:
这里解决的方法其实很是很是多,解决的思路也很是很是多,因为这一小节讲的是 递归,因此咱们使用递归的方式来解决。废话不罗嗦,先上代码:
// shift action
type ShiftAction<T extends any[]> = ((...args: T) => any) extends ((arg1: any, ...rest: infer R) => any) ? R : never; type combineTupleTypeWithTecursion<T extends any[], E = {}> = { 1: E, 0: combineTupleTypeWithTecursion<ShiftAction<T>, E & T[0]> }[T extends [] ? 1 : 0] type test = [{ a: string }, { b: number }]; type testResult = combineTupleTypeWithTecursion<test>; // { a: string; } & { b: number; } 复制代码
看到上面的代码是否是一脸懵逼?不要紧,接下来咱们用普通的 Typescript
代码来 "翻译" 一下上述的代码。
function combineTupleTypeWithTecursion(T: object[], E: object = {}): object {
return T.length ? combineTupleTypeWithTecursion(T.slice(1), { ...E, ...T[0] }) : E
}
const testData = [{ a: 'hello world' }, { b: 100 }];
// 此时函数的返回值为 { a: 'hello world', b: 100 }
combineTupleTypeWithTecursion(testData);
复制代码
看到这儿,相信聪明的同窗一会儿就懂了,原来类型的递归与普通函数的递归本质上是同样的。若是触发结束条件,就直接返回,不然就一直地递归调用下去,所传递的第二个参数用来保存上一次递归的计算结果。
固然熟悉递归的同窗都知道,常见的编程语言中,递归行为很是消耗计算机资源的,一旦超出了最大限制那么程序就会崩溃。同理类型中的递归也是同样的,若是递归地过深,类型系统同样会崩溃,因此这里的代码你们理解就好,尽可能不要在生产环境使用哈。
还记得一开始提出的思考题吗?其实经过上述的学习,咱们彻底能够自信地说出,Typescript
的 Type
自己也是一套完备的编程语言,甚至能够说是完备的图灵语言。所以类型自己也是能够用来编程的,你彻底能够用它来编写一些有趣的东西,更别说是搞定平常开发中遇到的简单的业务场景了。
其实所谓 "高级用法",不过是用来解决某些特定的场景而产生的特定的约定俗称的写法或者语法糖。那高级用法重要吗?重要,也不重要。怎理解呢,根据编程中的 "二八原则",20%的知识储备已经能够解决80%的需求问题,可是这剩余的20%,就是入门与熟练的分水岭。
其实只要当咱们仔细翻阅一遍官方提供的 handbook,就已经能够应付平常开发了。可是就像本文一开头说的那样,你是否以为:
Typescript
在某些场景下用起来很费劲,远不及 Javascript
灵活度的十分之一。Javascript
中了某些 骚操做 用极简短的代码解决了某个复杂的代码而沾沾自喜,但却为不正确的 返回类型 挠秃了头。as xxx
会让你的代码看起来很挫,但却无能为力,含恨而终。同窗,当你使用某种办法解决了上述的这些问题,那么这种用法就能够被称做 "高级用法"。
举个栗子:在 Redux
中有一个叫做 combineReducers
的函数,由于某些场景,咱们须要增长一个 combineReducersParamFactory
的函数,该函数支持传入多个函数,传入函数的返回值为做为combineReducers
的入参,咱们须要整合多个入参数函数的返回值,并生成最终的对象供 combineReducers
函数使用。
思考一下逻辑,发现其实并不复杂,用 Javascript
能够很容易地实现出来:
/** * 合并多个参数的返回数值并返回 * @param { Function[] } reducerCreators * @returns { Object } */
function combineReducersParamFactory(...reducerCreators) {
return reducerCreators.reduce((acc, creator) => ({ ...acc, ...creator() }), {})
}
// test ...
function todosReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const ret = combineReducersParamFactory(
() => ({ todosReducer }),
() => ({ counterReducer })
);
// { todosReducer: [Function: todosReducer], counterReducer: [Function: counterReducer] }
复制代码
但若是用须要配备对应的类型,应该如何编写呢?
type Combine<T> = (T extends any ? (args: T) => any : never) extends (args: infer A) => any ? A : never;
/**
* 合并多个参数的返回数值并返回
* @param { Function[] } reducerCreators
* @returns { Object }
*/
function combineReducersParamFactory<T extends ((...args) => object)[]>(...reducerCreators: T): Combine<ReturnType<T[number]>> {
return reducerCreators.reduce<any>((acc, creator) => ({ ...acc, ...creator() }), {});
}
// test ...
function todosReducer(state: object[], action: { [x: string]: string}) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
function counterReducer(state: number, action: { [x: string]: string}) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 这里不须要显示传入类型,这里就能够获得正确的代码提示
const ret = combineReducersParamFactory(
() => ({ todosReducer }),
() => ({ counterReducer })
);
// { todosReducer: [Function: todosReducer], counterReducer: [Function: counterReducer] }
复制代码
你看,类型通过精心编排以后,就是可让调用者不增长任何负担的前提下,享受到代码提示的快乐。
通过这一章节的学习,咱们能够明确了解到,通过咱们精心编排的类型,能够变得很是的智能,可让调用者几乎零成本地享受到代码提示的快乐。或许在编排类型时所耗费的时间成本比较大,可是一旦咱们编排完成,就能够极大地减小调用者的脑力负担,让调用者享受到编程的快乐。
熟悉 函数式编程 的同窗必定对 数据流动 的概念有较为深入的理解。当你在 "上游" 改变了一个值以后,"下游" 相关的会跟着自动更新。有 响应式编程 经验的同窗这是时候应该火烧眉毛地想举手了,同窗把手放下,这里咱们并不想深刻地讨论 流式编程思想,之因此引出这些概念,是想类比出本小节的重点: 流动的类型。
是的,编写类型系统的思路是能够借鉴 函数式编程 的思想的。所以某一个类型发生变化时,其余相关的类型也会自动更新,而且当代码的臃肿到不可维护的时候,你会获得一个友好的提示,整个类型系统就好像一个被精心设计过的约束系统。
聊完了类型系统的编写思路,我们再来聊一聊代码哲学。其实之因此如今 Typescript
愈来愈火,撇开哪些聊烂了的优点不谈,其实最大的优点在于强大的类型表现能力,以及编辑器(VSCode)完备的代码提示能力。
那么在这些优点的基础上,我我的拓展了一些编码哲学(习惯),这里见仁见智,大佬轻喷~:
any
或 as any
,注意这里并非说不能用,而是你判断出目前状况下使用 any
是最优解。any
做为类型,优先考虑一下是否可使用 unknown
类型替代,毕竟 any
会破坏类型的流动。as xxx
,若是大量使用这种方式纠正类型,那么大几率你对 类型流动 理解的还不够透彻。前面咱们说到,类型是具有流动性的,结合 响应式编程 的概念其实很容易理解。这一小节咱们将列举几个常见的例子,来和你们具体讲解一下。
有编程经验的同窗都知道,数据是能够被传递的,同理,类型也能够。
你可用 type
建立一个类型指针,指向对应的类型,那么就能够实现类型的传递,固然你也能够理解为指定起一个别名,或者说是拷贝,这里见仁见智,可是经过上述方法能够实现类型的传递,这是显而易见的。
type RawType = { a: string, b: number };
// 这里就拿到了上述类型的引用
type InferType = RawType; // { a: string, b: number };
复制代码
一样,类型也能够随着数据的传递而传递:
var num: number = 100;
var num2 = num;
type Num2Type = typeof num2; // number
复制代码
也正是依赖这一点,Typescript
才得以实现 类型检查、定义跳转 等功能。
到这里熟悉 流式编程 的同窗就要举手了:你光说了类型的 传递,输入 与 输出,那我若是但愿在类型 传递 的过程当中对它进行操做,该怎么作呢?同窗你不要急,这正是我下面所想要讲的内容。
在上一小节中,咱们反复地扯到了 函数式编程、响应式编程、流式编程 这些抽象的概念,其实并非跑题,而是者二者的思想(理念)实在太类似了,在本小节后续的讲解中,我还会一直延用这些概念帮助你们理解。翻看一下经常使用 函数式编程 的库,无论是 Ramda
、RXJS
仍是咱们耳熟能详的 lodash
、underscore
,里面必定有一个操做符叫做 filter
,也就是对数据流的过滤。
这个操做符的使用频率必定远超其余操做符,那么这么重要的功能,咱们在类型系统中该如何实现呢?
要解决这个问题,这里咱们先要了解一个在各大 技术社区/平台 搜索频率很是高的一个问题:
TypeScript中 的 never 类型具体有什么用?
既然这个问题搜索频率很是之高,这里我也就不重复做答,有兴趣的同窗能够看一下尤大大的回答: TypeScript中的never类型具体有什么用? - 尤雨溪的回答 - 知乎。
这里咱们简单总结一下:
never
表明空集。never
常被用来做 "类型兜底"。固然上面的总结并不完整,但已经足够帮助理解本小节内容,感兴趣的同窗能够自行查阅相关资料。
上面提到了 "类型收窄",这与咱们的目标已经十分接近了,固然咱们还须要了解 never
参与类型运算的相关表现:
type NeverTest = string | never // stirng
type NeverTest2 = string & never // never
复制代码
重要的知识出现了:T | never
,结果为 T
。
看到这里,相信聪明的同窗们已经有思路了,咱们能够用 never
来过滤掉 联合类型(Union Types)
中不和指望的类型,其实这个 泛型操做符 早在 Typescript 2.8 就已经被加入到了官方文档中了。
/** * Exclude from T those types that are assignable to U */
type Exclude<T, U> = T extends U ? never : T;
复制代码
相信通过这么长时间的学习,看到这里你必定很容易就能这种写法的思路。
好了,讲完了 过滤,咱们再来说讲 分流。类型 分流 的概念其实也不难理解,这个概念经常与逻辑判断一同出现,毕竟从逻辑层面来说,联合类型(Union Types)
本质上仍是用来描述 或 的关系。一样的概念若是引入到 流式编程 中,就天然而然地会引出 分流。换成打白话来说,就是不一样数据应被分该发到不一样的 管道 中,同理,类型也须要。
那么这么经常使用的功能,在 Typescript
中如何处理呢?其实这种常见的问题,官方也很是贴心地为咱们考虑到了,那就是:类型守卫(Type guard)
。网上对 类型守卫(Type guard)
有讲解的文章很是的多,这里也不做赘述,有兴趣的同窗能够自行搜索学习。咱们这里用一个简单的栗子简单地演示一下用法:
function foo(x: A | B) {
if (x instanceof A) {
// x is A
} else {
// x is B
}
}
复制代码
能够触发类型守卫的常见方式有:typeof
、instanceof
、in
、==
、 ===
、 !=
、 !==
等等。
固然在有些场景中,单单经过以上的方式不能知足咱们的需求,该怎么办呢?其实这种问题,官方也早已经帮我考虑到了:使用 is
关键字自定义 类型守卫(Type guard)
。
// 注意这里须要返回 boolean 类型
function isA(x): x is A {
return true;
}
// 注意这里须要返回 boolean 类型
function isB(x): x is B {
return x instanceof B;
}
function foo2(x: unknown) {
if (isA(x)) {
// x is A
} else {
// x is B
}
}
复制代码
这一章节中,咱们经过类比 响应式编程
、流式编程
的概念方式,帮助你们更好地理解了 类型推导 的实现逻辑与思路,相信通过了这一章节的学习,咱们对 Typescript
中的类型推导又有了更加深刻的理解。不过这一章引入的抽象的概念比较多,也比较杂,基础不是太好的同窗须要多花点时间翻看一下相关资料。
提及 Typescript
的编译手段大部分同窗应该都不会陌生,不管是在 webpack
中使用 ts-loader
或 babel-loader
,仍是在 gulp
中使用 gulp-typescript
,亦或是直接使用 Typescript
自带的命令行工具,相信大部分同窗也都已经得心应手了,这里不作赘述。
这里咱们把目光聚焦到撸码体验上,相信有使用过 Typescritp
开发前端项目的同窗必定有过各类各样的困扰,这里列举几个常见的问题:
declare module '*.module.css'
这种毫无卵用的类型定义。In TypeScript 2.2 and later, developers can enable language service plugins to augment the TypeScript code editing experience.
其实官方文档已经写的很清楚了,这玩意儿旨在优化 Typescript
代码的 编写体验。因此想利用这玩意儿改变编译结果或是想自创新语法的仍是省省吧 嗯,我在说我本身呢!
那么 Typescript Service Plugins
的能够用来作哪些事呢?
官方也有明确的回答:
plugins are for augmenting the editing experience. Some examples of things plugins might do:
- Provide errors from a linter inline in the editor
- Filter the completion list to remove certain properties from
window
- Redirect "Go to definition" to go to a different location for certain identifiers
- Enable new errors or completions in string literals for a custom templating language
一样官方也给出了不推荐使用 Typescript Service Plugins
的场景:
Examples of things language plugins cannot do:
- Add new custom syntax to TypeScript
- Change how the compiler emits JavaScript
- Customize the type system to change what is or isn't an error when running
tsc
好了,相信读到这里你们必定对 Typescript Service Plugins
有了一个大体的了解,下面我会介绍一下 Typescript Service Plugins
的安装与使用。
# 就像安装普通的 `npm` 包同样
npm install --save-dev your_plugin_name
复制代码
{
"compilerOptions": {
/** compilerOptions Configuration ... */
"noImplicitAny": true,
"plugins": [
{
/** 配置插件名称,也能够填写本地路径 */
"name": "sample-ts-plugin"
/** 这里能够给插件传参 ... */
}
/** 支持同时引入多个插件 ... */
]
}
}
复制代码
VSCode
开发,记得务必 using the workspace version of typescript,不然可能致使插件不生效。Typescript Service Plugins
产生的告警或者报错不会影响编译结果。具体使用细节请用编辑器打开我提供的 demo,自行体验。
npm install --save-dev typescript-styled-plugin typescript
复制代码
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-styled-plugin"
/** 具体配置参数请查看官方文档 */
}
]
}
}
复制代码
此插件能够用来缓解在使用 CSS Module
时没有代码提示的困境,主要思路就是经过读取对应的 CSS Module 文件并解析成对应的 AST
,并生成对应的类型文件从而支持对应的代码提示。可是根据反馈来看,彷佛某些场景下表现并不尽人意,是否值得大规模使用有待商榷。
相似实现思路的还有 typings-for-css-modules-loader,功能来讲确定是 webpack loader
更增强大,可是 Typescript Plugin
更加轻量、入侵度也越低,取舍与否,见仁见智吧
npm install --save-dev eslint typescript-eslint-language-service
复制代码
在 .eslintrc.*
文件中,添加对应的 eslint
配置
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-eslint-language-service"
/** 默认会读取 `.eslintrc.*` 文件 */
/** 具体配置参数请查看官方文档 */
}
]
}
}
复制代码
此插件可让 Typescript
原生支持 eslint
检查及告警,编辑器不须要安装任何插件便可自持,可是报错并不影响编译结果。
npm install --save-dev typescript-styled-plugin typescript
复制代码
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-styled-plugin"
/** 具体配置参数请查看官方文档 */
}
]
}
}
复制代码
此插件能够为 styled-components 的样式字符串模板提供 属性/属性值
作语法检查。 同时也推荐安装 VSCode
插件 vscode-styled-components,为你的样式字符串模板提供代码提示以及语法高亮。
未完待续...
未完待续...
答:不能够,全部可使用 Typescript Plugin
的场景必定都是编码阶段的,并且官方对 plugins 的定位局限在了 只改善编写体验
这方面,你并不能自定义语法或者自定义规则来改变编译结果,不过你能够考虑使用自定义 compiler
,固然这是另外一个话题了。
如下引用自官方文档:
TypeScript Language Service Plugins ("plugins") are for changing the editing experience only. The core TypeScript language remains the same. Plugins can't add new language features such as new syntax or different typechecking behavior, and plugins aren't loaded during normal commandline typechecking or emitting.