TypeScript 實踐

螞蟻金服資料體驗技術發表於2018-03-04

作者簡介:aoto 螞蟻金服·資料體驗技術團隊

摘要:之前寫過一篇《TypeScript 體系調研報告》,經過半年多的螞蟻金服資料平臺大規模 JS 專案實戰,沉澱了一些程式設計實戰經驗和感悟。

前言

TypeScript 是有型別定義的 JS 的超集,包括 ES5、ES5+ 和其他一些諸如泛型、型別定義、名稱空間等特徵的集合,為了大規模 JS 應用而生。對於 TypeScript 本身,更多資訊請參考《TypeScript 體系調研報告》。本文只記錄 TypeScript 在我們實際專案中產生的一些實際有用的價值。我們的專案基於 React 體系,因此本文著重關注 React 體系和 TypeScript 結合使用的經驗。

React in TypeScript

在 TypeScript 開發環境下寫 React 元件,與 ES6 的區別主要就是 Props 和 State 的定義。如果是 ES6,大概是這樣:

import PropTypes from 'prop-types';
class App extends React.PureComponent {
  state = {
    aState: '',
    bState: '',
  };
}

App.propTypes = {
  aProps: PropTypes.string.isRequired,
  bProps: PropTypes.string.isRequired,
};
複製程式碼

如果用 TypeScript 來寫,大概是這樣(本文的 interface 定義預設以 I 開頭):

interface IProps {
  aProps: string;
  bProps: string;
}
interface IState {
  aState: string;
  bState: string;
}

class App extends React.PureComponent<IProps, IState> {
  state = {
    aState: '',
    bState: '',
  };
}
複製程式碼

TypeScript 自帶 JSX 解析器,因此為了充分利用它本身的是靜態檢查功能,所以用泛型來定義 Props 和 State 的型別。如上定義後,在成員方法中通過this.props.this.state.使用 Props 和 State 時可以智慧提示,而且會做型別檢查。看起來寫沒有變的簡潔多少,但 TypeScript 來開發 React 應用有一個很大的優勢,TS 環境中的 React 元件屬性是靜態的,也就是說可以做靜態檢查,並且在支援 TS 的 IDE 下寫的過程中可以自動自動提示(是否存在以及型別是否正確)/補全,**這對於保障大型 React 應用的程式碼質量和執行時質量很有幫助。**而 ES6 環境中的 prop-types 屬性是動態的,也就是執行期做檢查,也不能做自動提示/補全。由此看來除了 JS 程式碼本身, TypeScript 對 React 元件開發也是很有好處的。

Redux 是最流行的狀態管理庫,一般和 React 結合使用。在 TypeScript 和 ES6 環境下,Redux 程式碼寫法區別不大,有個問題就是,React 和 Redux 的繫結函式 connect 需用函式寫法,而不建議用裝飾器寫法。因為 TS 對 類裝飾器的靜態解析還不支援,用了裝飾器寫 connect 就不能利用 TS 的靜態解析的好處了。

const mapStateToProps = (state: {}, ownProps: IProps) => {};
const mapDispatchToProps = {};

export default connect(mapStateToProps, mapDispatchToProps)(App);
複製程式碼

Antd 是一個流行的 React 元件庫,提供了 TS 型別宣告,在寫元件的時候可以進行屬性的自動提示和檢查,不確定的可以進入型別宣告檔案檢視,減少了查閱線上 API 文件的次數和屬性拼寫錯誤的可能性。

React + Redux + TypeScript 的程式設計體驗:

react+typescript.gif | center | 780x624

物件導向設計

複雜軟體需要用複雜的設計,物件導向就是很好的一種設計方式,使用 TS 的一大好處就是 TS 提供了業界認可的類( ES5+ 也支援)、泛型、封裝、介面物件導向設計能力,以提升 JS 的物件導向設計能力。

泛型

泛型,簡單說就是泛化的型別。我們專案中的泛型實踐主要在 React 元件中,這也是借鑑了 React 的 Component 和 PureComponent 在 TS 中的定義。程式碼如下:

class Component<P, S> {}
class PureComponent<P = {}, S = {}> extends Component<P, S> {}
複製程式碼

P 和 S 就是泛型,這裡的 P 是 Props,S 是 State。從這裡可以看到如果我們要基於 React 元件進行基於繼承的設計,那泛型就可以發揮作用了。這裡舉一個例子,假如我們要設計一個複雜 UI 模組,有兩層繼承,BaseComponent 繼承 React.PureComponent,XComponent 繼承 BaseComponent,效果就是 XComponent extends BaseComponent extends React.PureComponent,示例程式碼如下:

export interface IBaseProps {}

export interface IXProps {}

export class BaseComponent<
  IProps extends IBaseProps,
  IState = {}
> extends React.PureComponent<IProps, IState> {}

export class XComponent<
  IProps extends IXProps,
  IState = {}
> extends BaseComponent<IProps, IState> {}
複製程式碼

BaseComponent 的屬性(props)定義是 IProps,繼承自 IBaseProps,這樣 BaseComponent 元件中就可以使用 IBaseProps 中定義的屬性了。

XComponent 的屬性定義是 IProps,繼承自 IXProps,同時 XComponent 又繼承自 BaseComponent,因為我們在 BaseComponent 類的屬性泛型定義中 IProps 是繼承 IBaseProps,這樣兩個條件共同作用於 XComponent,就有了 IProps extends IXProps extends IBaseProps 的效果了,就可以在 XComponent 中同時使用 IBaseProps 和 IXProps 的屬性了。

封裝

我們都知道,封裝在對於物件導向軟體設計非常有用,而封裝各個模組的實現細節,就可以在有效管理軟體的複雜度。 TS 對於程式碼封裝性的幫助主要體現在它提供了類似於 Java 的訪問控制符。有了 private/protected/public,我們可以自主的控制類需要對外暴露的介面。訪問許可權需儘可能嚴,也就是說無需對外暴露的用 private 或者 protected ,如無需被子類使用的就用 private,只有明確需要對外暴露的介面採用 public 來描述。在一些非 React 元件的公共類,封裝特性尤為必要。

介面

介面在物件導向設計裡面也是很重要的,介面也就是對外提供服務的埠。我們舉一個例子,一個公共模組類 A ,提供了幾個介面findByIdupdateDatadestroy。那麼我們要定義一個介面的定義,方便別的模組使用(這和傳統程式語言的介面有點不同,TS 的介面並不會自己執行):

export interface IClassA {
  findById(id: string): IModel;
  updateData(data: IModel): void;
  destroy(): void;
}
複製程式碼

呼叫方:

const a = new IClassA();
a.findById('1');
a.updateData({ id: '1', name: 'a' });
a.destroy();
複製程式碼

有了介面宣告,我們在使用該模組的時候可以清晰的看到它到底有哪些介面,方法的入參是什麼,返回值是什麼。同時也有程式碼的自動提示,提升開發效率,減少拼寫錯誤導致的低階 Bug。

程式碼質量

靜態檢查

現在流行的庫如 React 、Redux 、 Lodash 、 Antd 等等都有 TS 型別宣告,加上我們自己業務程式碼完善的型別定義,整個程式碼庫的健康度可以較好的保持住。這在傳統的大規模 JS 應用裡面是很難得的。以前總感覺 JS 一複雜,就感覺質量難以保證,線上執行也有點虛,說不定什麼時候就爆出一個 's' is undefined, 'b' is not a function 之類的錯誤。現在有了靜態檢查,心裡更有底了

增強設計階段

上面章節已經就 TS 對物件導向設計能力的增強做了描述。設計的增強,是可以提升程式碼質量的。良好的設計面對需求迭代的不斷衝擊,可以保持程式碼的可維護性和可擴充套件性,也就提升了程式碼的質量和健康度。

總結

現在的 Web 應用很多都是複雜的單頁應用,尤其是一些工具類的產品,複雜度慢慢接近甚至不亞於一些傳統的桌面軟體,如 Word、Excel、SQL 客戶端、資料整合分析工具等等,這些 Web 應用都可以說是大規模 JS 應用,需要一個團隊來協作迭代開發。那麼協作效率、程式碼可讀性、可維護性、健壯性等等都是我們應該重點專注的點。

協作效率本身比較難以衡量,但可以從閱讀、維護別人程式碼的難度和效率這一點來說明。如果有有完善的型別定義,再加上智慧的 TS IDE(如 VS Code),從我們專案的實踐經驗來看可以顯著的提升程式碼的可讀性、可維護性。很多時候都是看看原始碼就能較容易的發現一些問題和 Bug。隨著產品的不斷迭代,一來一回之間就提升了整個團隊的協作效率。

我們專案的 TS 校驗規則是很嚴格的,意味著程式碼中基本沒有 any ,都需要定義具體型別。對於一些公共的模組,我們也會嚴格定義 private、protected、public 等訪問控制符,這意味著程式碼即文件,程式碼就能說明哪些是對外的介面,哪些是內部使用的。

一些常用的第三方庫和框架都有完善的 TS 型別定義,同時整個專案業務程式碼都有完善的 TS 型別定義,靜態檢查出錯的都會在 IDE 中提示出來,甚至可以阻斷構建流程,這樣可以減少 Bug 的產生,程式碼質量更為可控,程式碼健康度更高,心裡也更有底了。

相關資源

對我們團隊感興趣的可以關注專欄或者傳送簡歷至'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相關文章