李沐冉 發表於 2019-12-30 10:01:00

Typescript 最佳实践

<p>文章列表:&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>《一》大话 TypeScript&nbsp;基本类型</p>
</li>
<li>
<p>《二》大话 Typescript 枚举</p>
</li>
<li>
<p>《三》大话&nbsp;Typescript 接口</p>
</li>
<li>
<p>《四》大话 Typescript 泛型</p>
</li>
<li>
<p>《五》大话 Typescript 函数与类</p>
</li>
<li>
<p>《六》Typescript 最佳实践</p>
</li>
</ul>
<p>&nbsp;</p>
<p>为了更好的阅读体验,&nbsp; 可以看.&nbsp;</p>
<p>&nbsp;</p>
<p>一年前刚接触&nbsp;Typescript&nbsp;的时候, 觉得它加大了代码工作量. 写一大堆东西.为了找某个类型东奔西跑, 引入第三库还经常报错.&nbsp;</p>
<p>然而现在的我想说: 真香.&nbsp;</p>
<p>我们经常吐槽别人代码可维护性特别低, 总是希望别人能够主动的写注释, 可是写注释却没有任何方式可以进行约束. 这下好了, 类型就是最好的注释, 用&nbsp;Typescript, 可以大大提高代码的可维护性.&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>一.&nbsp;如何处理第三方库类型相关问题</p>
<p>Typescipt 所提供的第三方库类型定义不仅约束我们的输入调用, 还能为我们提供文档. 现在, NPM 上的第三方类型定义种类繁多,很难保证类型定义是正确的. 也很难保证所有使用的第三方库都有类型定义.&nbsp;</p>
<p>那么, 在这个充满未知的过程中,如何才能正确使用TypeScript中的第三方库呢?</p>
<p>&nbsp;</p>
<p>下面列举了四种常见的无法正常工作的场景以及对应的解决方法:</p>
<ul class="list-paddingleft-2">
<li>
<p>库本身没有自带类型定义</p>
</li>
<li>
<p>库本身没有类型定义, 也没有相关的@type</p>
</li>
<li>
<p>类型声明库有误</p>
</li>
<li>
<p>类型声明报错</p>
</li>
</ul>
<p>&nbsp;</p>
<p>1. 库本身没有自带类型定义</p>
<p>查找不到相关的库类型. 举个栗子&nbsp;</p>
<p><img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" data-type="png" data-w="1046"></p>
<p>在初次将 react 改造支持 typescript 时, 想必很多人都会遇到 module.hot 报错. 此时只需要安装对应的类型库即可.&nbsp;</p>
<p><strong>安装 @types/webpack-env</strong></p>
<p>&nbsp;</p>
<p>2. 库本身没有类型定义, 也没有相关的@type</p>
<p>那只能自己声明一个了. 随便举个栗子.&nbsp;</p>
<p>declare module "lodash"</p>
<p>&nbsp;</p>
<p>3. 类型声明库有误</p>
<ul class="list-paddingleft-2">
<li>
<p>推动解决官方类型定义的问题, 提issue, pr&nbsp;</p>
</li>
<li>
<p>Import 后通过 extends 或者 merge 能力对原类型进行扩展</p>
</li>
<li>
<p>忍受类型的丢失或不可靠性</p>
</li>
<li>
<p>使用 // @ts-ignore&nbsp; 忽略</p>
</li>
</ul>
<p>&nbsp;</p>
<p>4. 类型声明报错</p>
<ul class="list-paddingleft-2">
<li>
<p>在 compilerOptions 的添加"skipLibCheck": true, 曲线救国</p>
</li>
</ul>
<p>&nbsp;</p>
<p>二.&nbsp;巧用类型收缩解决报错下面列举了几种常见的解决方法:</p>
<ul class="list-paddingleft-2">
<li>
<p>类型断言</p>
</li>
<li>
<p>类型守卫 typeof in instanceof 字面量类型保护</p>
</li>
<li>
<p>双重断言</p>
</li>
</ul>
<p>&nbsp;</p>
<p>1、 类型断言</p>
<p>类型断言可以明确的告诉 TypeScript 值的详细类型,</p>
<p>在某些场景, 我们非常确认它的类型, 即使与 typescript 推断出来的类型不一致. 那我们可以使用类型断言.&nbsp;</p>
<p>语法如下:&nbsp;</p>
<p>&lt;类型&gt;值</p>
<p>&nbsp;</p>
<p>值 as 类型&nbsp;// 推荐使用这种语法. 因为&lt;&gt;容易跟泛型, react 中的语法起冲突</p>
<p>&nbsp;</p>
<p>举个例子, 如下代码,&nbsp; padding 值可以是 string , 也可以是 number, 虽然在代码里面写了 Array(), 我们明确的知道, padding 会被parseint 转换成 number 类型, 但类型定义依然会报错.&nbsp;</p>
<p>&nbsp;</p>
<p>function padLeft(value: string, padding: string | number) {</p>
<p>&nbsp; &nbsp;// 报错: Operator '+' cannot be applied to</p>
<p>&nbsp; &nbsp;// types 'string | number' and 'number'</p>
<p>&nbsp; &nbsp;return Array(padding + 1).join(" ") + value;</p>
<p>}</p>
<p>&nbsp;</p>
<p>解决方法, 使用类型断言. 告诉 typescript 这里我确认它是 number 类型, 忽略报错.&nbsp;</p>
<p>function padLeft(value: string, padding: string | number) {</p>
<p>&nbsp; &nbsp;// 正常</p>
<p>&nbsp; &nbsp;return Array(padding as number + 1).join(" ") + value;</p>
<p>}</p>
<p>&nbsp;</p>
<p>但是如果有下面这种情况, 我们要写很多个 as 么?&nbsp;</p>
<p>function padLeft(value: string, padding: string | number) {</p>
<p>&nbsp; &nbsp;console.log((padding as number) + 3);</p>
<p>&nbsp; &nbsp;console.log((padding as number) + 2);</p>
<p>&nbsp; &nbsp;console.log((padding as number) + 5);</p>
<p>&nbsp; &nbsp;return Array((padding as number) + 1).join(' ') + value;</p>
<p>}</p>
<p>&nbsp;</p>
<p>2、 类型守卫</p>
<p>类型守卫有以下几种方式, 简单的概括以下</p>
<ul class="list-paddingleft-2">
<li>
<p>typeof:&nbsp; 用于判断 "number","string","boolean"或 "symbol" 四种类型.&nbsp;</p>
</li>
<li>
<p>instanceof : 用于判断一个实例是否属于某个类</p>
</li>
<li>
<p>in: 用于判断一个属性/方法是否属于某个对象</p>
</li>
<li>
<p>字面量类型保护</p>
</li>
</ul>
<p>&nbsp;</p>
<p>上面的例子中, 是 string | number 类型, 因此使用 typeof 来进行类型守卫. 例子如下:&nbsp;</p>
<p>function padLeft(value: string,padding: string | number) {</p>
<p>&nbsp; &nbsp;if (typeof padding === 'number') {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(padding + 3); //正常</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(padding + 2); //正常</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(padding + 5); //正常</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//正常</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;return Array(padding + 1).join(' ') value;</p>
<p>&nbsp; &nbsp;}</p>
<p>&nbsp; &nbsp;if (typeof padding === 'string') {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;return padding + value;</p>
<p>&nbsp; &nbsp;}</p>
<p>}</p>
<p>&nbsp;</p>
<p>相比较 类型断言 as , 省去了大量代码. 除了 typeof , 我们还有几种方式, 下面一一举例子.&nbsp;</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>instanceof :用于判断一个实例是否属于某个类</p>
</li>
</ul>
<p>class Man {</p>
<p>&nbsp; &nbsp;handsome = 'handsome';</p>
<p>}</p>
<p>&nbsp;</p>
<p>class Woman {</p>
<p>&nbsp; &nbsp;beautiful = 'beautiful';</p>
<p>}</p>
<p>&nbsp;</p>
<p>function Human(arg: Man | Woman) {</p>
<p>&nbsp; &nbsp;if (arg instanceof Man) {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.handsome);</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.beautiful); // error</p>
<p>&nbsp; &nbsp;} else {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;// 这一块中一定是 Woman</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.beautiful);</p>
<p>&nbsp; &nbsp;}</p>
<p>}</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>in : 用于判断一个属性/方法是否属于某个对象</p>
</li>
</ul>
<p>interface B {</p>
<p>&nbsp; &nbsp;b: string;</p>
<p>}</p>
<p>&nbsp;</p>
<p>interface A {</p>
<p>&nbsp; &nbsp;a: string;</p>
<p>}</p>
<p>&nbsp;</p>
<p>function foo(x: A | B) {</p>
<p>&nbsp; &nbsp;if ('a' in x) {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;return x.a;</p>
<p>&nbsp; &nbsp;}</p>
<p>&nbsp; &nbsp;return x.b;</p>
<p>}</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>字面量类型保护</p>
</li>
</ul>
<p>有些场景, 使用 in, instanceof, typeof 太过麻烦. 这时候可以自己构造一个字面量类型.&nbsp;</p>
<p>type Man = {</p>
<p>&nbsp; &nbsp;handsome: 'handsome';</p>
<p>&nbsp; &nbsp;type: 'man';</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>type Woman = {</p>
<p>&nbsp; &nbsp;beautiful: 'beautiful';</p>
<p>&nbsp; &nbsp;type: 'woman';</p>
<p>};</p>
<p>&nbsp;</p>
<p>function Human(arg: Man | Woman) {</p>
<p>&nbsp; &nbsp;if (arg.type === 'man') {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.handsome);</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.beautiful); // error</p>
<p>&nbsp; &nbsp;} else {</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;// 这一块中一定是 Woman</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;console.log(arg.beautiful);</p>
<p>&nbsp; &nbsp;}</p>
<p>}</p>
<p>&nbsp;</p>
<p>3、双重断言</p>
<p>有些时候使用 as 也会报错,因为 as 断言的时候也不是毫无条件的. 它只有当S类型是T类型的子集,或者T类型是S类型的子集时,S能被成功断言成T.&nbsp;</p>
<p>所以面对这种情况, 只想暴力解决问题的情况, 可以使用双重断言.&nbsp;</p>
<p>function handler(event: Event) {</p>
<p>&nbsp; &nbsp;const element = event as HTMLElement;</p>
<p>&nbsp; &nbsp;// Error: 'Event' 和 'HTMLElement'</p>
<p>&nbsp;&nbsp;&nbsp; 中的任何一个都不能赋值给另外一个</p>
<p>}</p>
<p>&nbsp;</p>
<p>如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的any</p>
<p>&nbsp;</p>
<p>function handler(event: Event) {</p>
<p>&nbsp; &nbsp;const element = (event as any) as HTMLElement;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;// 正常</p>
<p>}</p>
<p>&nbsp;</p>
<p>三.&nbsp;巧用 typescript 支持的 js 最新特性优化代码</p>
<p>1. 可选链 Optional Chaining&nbsp;</p>
<p>&nbsp;</p>
<p>let x = foo?.bar.baz();</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>typescript 中的实现如下:&nbsp;</p>
<p>var _a;</p>
<p>let x = (_a = foo) === null ||</p>
<p>_a === void 0 ? void 0 : _a.bar.baz();</p>
<p>&nbsp;</p>
<p>利用这个特性, 我们可以省去写很多恶心的 a &amp;&amp; a.b &amp;&amp; a.b.c 这样的代码</p>
<p>&nbsp;</p>
<p>2. 空值联合 Nullish Coalescing</p>
<p>&nbsp;</p>
<p>let x = foo ?? '22';</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>typescript 中的实现如下:&nbsp;</p>
<p>&nbsp;</p>
<p>let x = (foo !== null &amp;&amp; foo !== void 0 ?</p>
<p>foo : '22');</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>四.&nbsp;巧用高级类型灵活处理数据typescript 提供了一些很不错的工具函数. 如下图</p>
<p>&nbsp;<img alt="" data-src="https://img2018.cnblogs.com/blog/910706/201912/910706-20191230095745194-1303484589.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>类型索引</p>
</li>
</ul>
<p>为了实现上面的工具函数, 我们需要先了解以下几个语法:&nbsp;</p>
<p>keyof : 获取类型上的 key 值</p>
<p>extends : 泛型里面的约束</p>
<p>T : 获取对象 T 相应 K 的元素类型</p>
<p>&nbsp;</p>
<p>type Partial&lt;T&gt; = {</p>
<p>&nbsp; &nbsp;?: T</p>
<p>}</p>
<p>&nbsp;</p>
<p>在使用 props 的时候, 有时候全部属性都是可选的, 如果一个一个属性写 ? , 大量的重复动作. 这种时候可以直接使用 Partial&lt;State&gt;&nbsp;&nbsp;</p>
<p>&nbsp;</p>
<p>Record 作为一个特别灵活的工具. 第一个泛型传入对象的key值, 第二个传入 对象的属性值.&nbsp;</p>
<p>type Record&lt;K extends string, T&gt; = {</p>
<p>&nbsp; &nbsp;: T;</p>
<p>}</p>
<p>&nbsp;</p>
<p>我们看一下下面的这个对象, 你会怎么用 ts 声明它?&nbsp;</p>
<p>const AnimalMap = {</p>
<p>&nbsp; &nbsp;cat: { name: '猫', title: 'cat' },</p>
<p>&nbsp; &nbsp;dog: { name: '狗', title: 'dog' },</p>
<p>&nbsp; &nbsp;frog: { name: '蛙', title: 'wa' },</p>
<p>};</p>
<p>&nbsp;</p>
<p>此时用 Record 即可.&nbsp;</p>
<p>type AnimalType = 'cat' | 'dog' | 'frog';</p>
<p>&nbsp;</p>
<p>interface AnimalDescription {</p>
<p>name: string, title: string</p>
<p>}</p>
<p>&nbsp;</p>
<p>const AnimalMap:</p>
<p>Record&lt;AnimalType, AnimalDescription&gt; = {</p>
<p>&nbsp; &nbsp;cat: { name: '猫', title: 'cat' },</p>
<p>&nbsp; &nbsp;dog: { name: '狗', title: 'dog' },</p>
<p>&nbsp; &nbsp;frog: { name: '蛙', title: 'wa' },</p>
<p>};</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>never, 构造条件类型</p>
</li>
</ul>
<p>除了上面的几个语法. 我们还可以用 never , 构造条件类型来组合出更灵活的类型定义.&nbsp;</p>
<p>语法:&nbsp;</p>
<p>never: 从未出现的值的类型</p>
<p>&nbsp;</p>
<p>// 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y</p>
<p>构造条件类型 : T extends U ? X : Y</p>
<p>&nbsp;</p>
<p>type Exclude&lt;T, U&gt; = T extends U ? never : T;</p>
<p>&nbsp;</p>
<p>// 相当于: type A = 'a'</p>
<p>type A = Exclude&lt;'x' | 'a', 'x' | 'y' | 'z'&gt;</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>更简洁的修饰符: - 与 +&nbsp;</p>
</li>
</ul>
<p>可以直接去除 ? 将所有对象属性变成必传内容.&nbsp;</p>
<p>type Required&lt;T&gt; = { -?: T };</p>
<p>&nbsp;</p>
<p>// Remove readonly</p>
<p>type MutableRequired&lt;T&gt; = {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;-readonly : T</p>
<p>}; &nbsp;</p>
<p>&nbsp;</p>
<ul class="list-paddingleft-2">
<li>
<p>infer: 在 extends 条件语句中待推断的类型变量。&nbsp;</p>
</li>
</ul>
<p>// 需要获取到 Promise 类型里蕴含的值</p>
<p>type PromiseVal&lt;P&gt; =</p>
<p>P extends Promise&lt;infer INNER&gt; ? INNER : P;</p>
<p>&nbsp;</p>
<p>type PStr = Promise&lt;string&gt;;</p>
<p>&nbsp;</p>
<p>// Test === string</p>
<p>type Test = PromiseVal&lt;PStr&gt;;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>五.&nbsp;辨别 type &amp; interface&nbsp;</p>
<p>在各大类型库中, 会看到形形色色的 type 和 interface . 然而很多人在实际中却不知道它们的区别.&nbsp;</p>
<p>&nbsp;</p>
<p>官网的定义如下:&nbsp;</p>
<p>&nbsp;</p>
<p>An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.</p>
<p>&nbsp;</p>
<p>An interface can have multiple merged declarations, but a type alias for an object type literal cannot.</p>
<p>&nbsp;</p>
<p>从一张图看出它们两的区别:&nbsp;</p>
<p>&nbsp;</p>
<p><img src="https://img2018.cnblogs.com/blog/910706/201912/910706-20191230095802423-990880815.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>建议:&nbsp; 能用 interface 实现,就用 interface , 如果不能才用 type.&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>为了更好的阅读体验,&nbsp; 《typescrit 最佳实践》</p>
<p>-&nbsp;欢迎关注「前端加加」,认真学前端,做个有专业的技术人...</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/beidan/p/12118198.html
頁: [1]
查看完整版本: Typescript 最佳实践