'any' is cheap, show me the 'type'(Ⅰ)

263 阅读8分钟

之前写了一篇文章《Angular/TypeScript中那些值得了解的知识——依赖注入》,有这样一条留言:你是想说js/ts是世界上最好的语言? 今天想在这里聊聊这个话题,介绍下我眼中TS中的一些最佳实践

JavaScript —— 野蛮生长,瑕瑜互见

要说TS的好,就先说说JS的不足。众所周知,JS诞生之初是浏览器端的脚本,协助完成一些简单的人机交互,因此JS设计之初最大的优点就是“灵活”。这个灵活体现在语法上,也体现在设计上,比如它是动态的,是弱类型的,可以面向函数、也可以面向对象,它写起来很难出错,代码又十分简练。举个最简单的例子,我想使用“栈”,对JS来说就是:

const stack = [];

然后调用push、pop方法即可,简简单单的[]就能表示数种数据结构。而在有了Node.js的加持后,JS不止可以用来操作DOM,还具备了操作数据库、操作文件等更丰富的能力。易上手、语法灵活等优势加之多端可用,这也造就了JS在更广泛的领域被使用,npm各类工具库琳琅满目,开发者生态如烈火烹油、鲜花着锦,一图胜千言

trend.png

不过在JS野蛮生长的过程中,它也成为了被吐槽最多,常年处于鄙视链最低端的开发语言,同样是一图胜千言:

thanks-for-inventing-javascript.jpg

你开发时如果不重视编码规范,运行时可能就会造成性能问题或者运行出错。

TypeScript —— 尺步绳趋,进退有据

JS的优点是“灵活”,缺点则也是“灵活”。举个最简单的例子,JS里没有“重载”、没有对象的约束,因此JS代码运行中经常遇到xxx is not defined这类报错,但编写中时无法发现这类问题,究其根因,就是没有对这类参数类型做限定,过于“灵活”的JS在编写代码时埋下的雷,Facebook早先使用的Flow语言,还有TypeScript,都是在试图控制过于灵活的JS,那么,究竟什么是TypeScript?简单来说就是

TypeScript = Type + JavaScript

它在JS的基础上加上类型的约束,保证了你的很多隐藏的xxx is not defined问题提前暴露。有个很经典的比喻,如果说长期的JS开发使你的编码习惯变成了积重难返的脱缰野马,那TS就像是御马的缰绳,既能悬崖勒马,亦可策马奔腾。

TS 难在哪?

其实TS的难,正是因为它太简单。

因为TS就是JS+Type,你可以很轻松的把一段JS代码转换成TS代码,比如这样的JS代码:

const hello = "Hello World!";

加个类型就是TS:

const hello : string = "Hello World!"

所以TS看起来真的不用学,不过这样的想法使用TS可能会有两个误区:

  1. 几乎全用any作为类型;因为把JS代码改为TS后,可能会报错,最简单的方法往往是在某些变量后加as any,但这样的作法相当于没有使用TS,应该尽量避免使用any;
  2. 在所有变量,所有方法中都加入类型声明,比如,我上面举的例子,实际上,按上面例子去写代码是没有意义的。甚至在Angular项目中,不修改angular.json的默认情况下,还会报错:

bad_practice.png

这个错误的意思就是:“ 'Hello World' 这个字符串是什么类型还要再专门声明吗?”

我认为,TS的核心就是在JS的基础上做一些类型的声明,像上述代码中的string类型,就是不需要声明的,同样的例子还有将某些没有返回值的方法的声明为返回类型是void,我觉得也是没必要,类似这样。

// bad
onFilterChange($event: xxx): void {
    this.filterChange.emit($event);
 }

那么,什么是需要声明的?我想结合UI组件库开发,介绍下我觉得好的一些实践。

我对于UI组件库的一点理解

因为我接下来的介绍都是基于React UI组件库的,这里说点React开发UI组件库的基础知识。

首先UI组件库实际上是对常用组件做了样式统一包装再对外呈现,举个例子:

menu.png

常见的菜单组件,不管横向纵向,我们都可以把它看作是ul、li标签,只是样式上设置了flex-direction: row;,对使用者而言,只需要了解文档学习使用API即可,无需关心内部实现。

而React,思想就是接收状态参数,对应展现相应的UI,状态参数一旦变了,UI也就变了,有点像修图:

UI = f(state)

所以它更推荐写成function component,因此React开发UI组件库代码基本都是这样的结构;

// 以menu为例
import React from 'react'export const Menu: React.FC = (props) => {
    // 拿到参数用户传入的参数
  const { xxx, children } = props
  // 省略一些逻辑处理
  // ...
  // 渲染时使用ul、children中包含li
  return (
    <ul className={xxx} >
        {children} 
    </ul>
  )
}
​
// 导出Menu供用户使用
export default Menu

对于用户,这样使用即可:

menu_with_code.png

有意识地去“面向对象”

上面举了两个例子,例子1:

const hello : string = "Hello World!"

不需要声明类型,是因为它的类型不言自明,但Menu那个例子,props它的类型并不知道,所以就很有必要声明。你可以借鉴一些面向对象的习惯,把props看成是一种类型的对象,比如:

// 使用字符串字面量实现简单的枚举
type menuMode = 'horizontal' | 'vertical'export interface MenuProps {
  /**默认 active 的菜单项的索引值 */
  defaultIndex: number
  className?: string
  /**菜单类型 横向或者纵向 */
  mode?: menuMode
  /** 适配react 18, 需要自行添加children属性 */
  children?: ReactNode
}
​
// 这里通过泛型定义props的类型是MenuPorps,类似props: MenuPorps的意思
export const Menu: React.FC<MenuProps> = (props) => {
    const { className, mode, defaultIndex, children } = props
       //  ...
}

这样写代码的好处就是,你指定了props的类型,使用时就会有关于这个类型的代码提示,比如:

autoComplete.gif

所以,你可以试试在写纯函数时,对你的输入多做类型的定义,我觉得这就是TS的最佳实践!(输出为何不做类型定义,我们下一篇来讲)

当然,我相信你也一定注意到了,上述代码中还有一句type menuMode = 'horizontal' | 'vertical',它的作用是这样的:

以菜单组件的编写为例,假定菜单只有横向和纵向,我们一般的思路是会使用枚举来限定这个类型。

当然这里使用枚举没有错,不过有点重——枚举本身往往具备这样的能力:比如我们网购时常用的退货功能,退货原因有“七天无理由退货”、“商品与描述不符”、“其他”等很多,这些冗长的原因可能只需要在前台页面显示,而后端数据库中保存时,可能只用一个数字标识即可,也就是说,显示的可枚举内容可用更简单、唯一的数值代表时,就是使用枚举的场景,而用一些IDE编码是,大家会发现,定义一个枚举对象,IDE就会显示其默认的枚举值,为变量赋这些枚举内容时,就相当于赋了值。

enum.png

如果你觉得你不需要这种映射关系,只是想简单的定义一种类型,表示指定的字符串,就可以直接用字符串字面量表示,前面加上type,表示你在对某种类型取别名。它使用起来效果是这样的:

string.gif

你会发现这样写代码,上面例子的mode字段的值就被限定成你为它定义的类型别名,你几乎不可能在任何需要使用到它时拼写错误,且假如因为需求变更你修改了这个类型别名的内容,你的代码会在使用这个别名处报错,提示你同步修改使用之处。

既然都有了“面向对象”,那“继承”在哪里?

如果按我上文中所说,TS的最佳实践就是在对一个JS文件进行分析后,在文件头部多加一个或多个interface表示一种类型的对象,再通过type定义若干类型别名,一起来“限制”你的变量、参数。 这种思维去写代码还有什么好处?以及既然说到了TS需要面向对象的思想,那是否也需要继承的思想呢?当然需要!以一个简单的组件库问题为了,比如我们开发的Button组件,一定是希望它时可以包含button组件的全部方法的,比如onClick/onFocus等,怎么做呢?当然可以把常用的方法作为属性全部实现一遍,类似这样:

wrong.png

又或者,使用继承的方式实现,具体怎么做,以及在此基础上还有什么好的TS编码实践,请看下集。当然,如果你对这些TS编码的技巧也有兴趣,欢迎你对我们的文章一键三联,以及关注我们接下来的开源项目————TinyUI。欢迎微信搜索我们的小助手: opentiny-official,拉你进群,了解它最新的动态。