大量摘抄於各種文章 ———— 站在巨人的肩膀上搬磚
TypeScript官方文檔
(中文版)
是最好的學習材料html
TypeScript = Type + Script(標準JS)。咱們從TS的官方網站上就能看到定義:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript。TypeScript是一個編譯到純JS的有類型定義的JS超集。react
如何更好的利用JS的動態性和TS的靜態特質,咱們須要結合項目的實際狀況來進行綜合判斷。一些建議:webpack
至於到底用不用TS,仍是要看實際項目規模、項目生命週期、團隊規模、團隊成員狀況等實際狀況綜合考慮。git
低級錯誤、非空判斷、類型推斷,這類問題是ESLint等工具檢測不出來的。
let isDone: boolean = false; let decimal: number = 6; let color: string = "blue"; // 數組,有兩種寫法 let list: number[] = [1, 2, 3]; let list: Array<number> = [1, 2, 3]; // 元組(Tuple) let x: [string, number] = ["hello", 10]; // 枚舉 enum Color {Red = 1, Green = 2, Blue = 4} let c: Color = Color.Green; // 不肯定的能夠先聲明爲any let notSure: any = 4; // 聲明沒有返回值 function warnUser(): void { alert("This is my warning message"); } let u: undefined = undefined; let n: null = null; // 類型永遠沒返回 function error(message: string): never { throw new Error(message); } // 類型主張,就是知道的比編譯器多,主動告訴編譯器更多信息,有兩種寫法 let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length; let strLength: number = (someValue as string).length;
信息隱藏有助於更好的管理系統的複雜度,這在軟件工程中顯得尤其重要。github
class Person { protected name: string; public age: number; constructor(name: string) { this.name = name; } } class Employee extends Person { static someAttr = 1; private department: string; constructor(name: string, department: string) { super(name); this.department = department; } } let howard = new Employee("Howard", "Sales"); console.log(howard.name); // 報錯:Person中name屬性是protected類型,只能在本身類中或者子類中使用
Robot類能夠繼承Base類,並實現Machine和Human接口,
這種能夠組合繼承類和實現接口的方式使面向對象編程更爲靈活、可擴展性更好。web
interface Machine { move(): void } interface Human { run(): void } class Base { } class Robot extends Base implements Machine, Human { run() { console.log('run'); } move() { console.log('move'); } }
定義了一個模板類型T,實例化GenericNumber類時能夠傳入內置類型或者自定義類型。泛型(模板)在傳統面向對象編程語言中是很常見的概念了,在代碼邏輯是通用模式化的,參數能夠是動態類型的場景下比較有用。typescript
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
定義了一個系統配置類型SystemConfig和一個模塊類型ModuleType,咱們在使用這些類型時就不能隨便修改config和mod的數據了,這對於多人協做的團隊項目很是有幫助。npm
interface SystemConfig { attr1: number; attr2: string; func1(): string; } interface ModuleType { data: { attr1?: string, attr2?: number }, visible: boolean } const config: SystemConfig = { attr1: 1, attr2: 'str', func1: () => '' }; const mod: ModuleType = { data: { attr1: '1' }, visible: true };
TS除了支持ES6的模塊系統以外,還支持命名空間。這在管理複雜模塊的內部時比較有用。編程
namespace N { export namespace NN { export function a() { console.log('N.a'); } } } N.NN.a();
面向對象相關概念詳見json
對於老項目,因爲TS兼容ES規範,因此能夠比較方便的升級現有的JS(這裏指ES6及以上)代碼,逐漸的加類型註解,漸進式加強代碼健壯性。遷移過程:
"target":"es5"
:編譯後代碼的ES版本,還有es3,es2105等選項。"module":"commonjs"
:編譯後代碼的模塊化組織方式,還有amd,umd,es2015等選項。"strict":true
:嚴格校驗,包含不能有沒意義的any,null校驗等選項。tsconfig.json
無需修改,增長"allowJs": true
選項。loaders: [ // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. { test: /\.tsx?$/, loader: "awesome-typescript-loader" } ]
對於新項目,微軟提供了很是棒的一些Starter項目,詳細介紹瞭如何用TS和其餘框架、庫配合使用。若是是React項目,能夠參考這個Starter:TypeScript-React-Starter
React、及其餘各類著名框架、庫都有TS類型聲明,咱們能夠在項目中經過npm install @types/react方式安裝,能夠在這個網站搜索你想要安裝的庫聲明包。安裝後,寫和那些框架、庫相關的代碼將會是一種很是爽的體驗,函數的定義和註釋將會自動提示出來,開發效率將會獲得提高。
type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
type Person = Huaren & Bairen & Heiren;
function getLength(something: string | number): number { return something.length;❌ } // index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'. // Property 'length' does not exist on type 'number'. function getString(something: string | number): string { return something.toString();✅ }
type EventNames = 'click' | 'scroll' | 'mousemove'; function handleEvent(ele: Element, event: EventNames) { // do something } handleEvent(document.getElementById('hello'), 'scroll'); // 沒問題 handleEvent(document.getElementById('world'), 'dblclick'); // 報錯,event 不能爲 'dblclick' // index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.
interface Cat { name: string; run(): void; } interface Fish { name: string; swim(): void; } function isFish(animal: Cat | Fish) { if (typeof animal.swim === 'function'❌) { return true; } return false; } // index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'. // Property 'swim' does not exist on type 'Cat'. function isFish(animal: Cat | Fish) { if (typeof (animal as Fish✅).swim === 'function') { return true; } return false; }
類型謂詞守衛自定義類型
function isFish(animal: Fish | Bird): animal is Fish { return (animal as Fish).swim !== undefined; }
typeof類型守衛
typeof v === "typename"
和typeof v !== "typename"
兩種形式能被識別"number"
, "string"
, "boolean"
, "symbol"
instanceof類型守衛
instanceof
用於守護類function getRandomPadder() { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(" "); } // 類型爲SpaceRepeatingPadder | StringPadder let padder: Padder = getRandomPadder(); if (padder instanceof SpaceRepeatingPadder) { padder; // 類型細化爲'SpaceRepeatingPadder' } if (padder instanceof StringPadder) { padder; // 類型細化爲'StringPadder' }
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }
舉個例子:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key] } interface IObj { name: string; age: number; male: boolean; } const obj:IObj = { name: 'zhangsan', age: 18, male: true } let x1 = getProperty(obj, 'name') // 容許,x1的類型爲string let x2 = getProperty(obj, 'age') // 容許,x2的類型爲number let x3 = getProperty(obj, 'male') // 容許,x3的類型爲boolean let x4 = getProperty(obj, 'sex') // 報錯:Argument of type '"sex"' is not // assignable to parameter of type '"name" | "age" | "male"'.
getProperty
函數,來獲取指定對象的指定屬性keyof
關鍵字,得到泛型T
上已知的公共屬性名聯合的字符串字面量類型'name' | 'age' | 'male'
K extends keyof T
限制K
只能是'name' | 'age' | 'male'
中的一個值T[K]
則表明對象裏對應key的元素的類型更好的理解索引類型
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] { return names.map(n => o[n]); } // T[K][]也能夠寫成 Array<T[K]> interface Person { name: string; age: number; sex: string; } let person: Person = { name: 'Jarid', age: 35, sex: '男', }; let strings: string[] = pluck(person, ['name', 'sex']); // ok, string[], [ 'Jarid', '男' ] let numbers: number[] = pluck(person, ['age']); // ok, number[], [ 35 ] let persons: (string | number)[] = pluck(person, ['name', 'sex', 'age']); // [ 'Jarid', '男', 35 ]
type
`keyof咱們就能夠獲取跟隨
interface Person`變化的字符串字面量類型interface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location" type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... type K3 = keyof { [x: string]: Person }; // string
interface Person { name: string; age: number; } type Partial<T> = { [P in keyof T]?: T[P]; } type PersonPartial = Partial<Person>; --------------------------------------- type Readonly<T> = { readonly [P in keyof T]: T[P]; } type ReadonlyPerson = Readonly<Person>; // 至關於 type ReadonlyPerson = { readonly name: string; readonly age: number; }
Omit<T, K> TypeScript 3.5 //讓咱們能夠從一個對象類型中剔除某些屬性,並建立一個新的對象類型 Partial<T>,TypeScript 2.1 // 將構造類型T全部的屬性設置爲可選的 Readonly<T>,TypeScript 2.1 // 將構造類型T全部的屬性設置爲只讀的 Record<K,T>,TypeScript 2.1 // 可用來將某個類型的屬性映射到另外一個類型上 Pick<T,K>,TypeScript 2.1 // 從類型T中挑選部分屬性K來構造類型 Exclude<T,U>,TypeScript 2.8 // 從類型T中剔除全部能夠賦值給U的屬性,而後構造一個類型 Extract<T,U>,TypeScript 2.8 // 從類型T中提取全部能夠賦值給U的類型,而後構造一個類型 NonNullable<T>,TypeScript 2.8 // 從類型T中剔除null和undefined,而後構造一個類型 ReturnType<T>,TypeScript 2.8 // 由函數類型T的返回值類型構造一個類型 InstanceType<T>,TypeScript 2.8 // 由構造函數類型T的實例類型構造一個類型 Required<T>,TypeScript 2.8 // 構造一個類型,使類型T的全部屬性爲required必選 ThisType<T>,TypeScript 2.8 // 這個工具不會返回一個轉換後的類型。它作爲上下文的this類型的一個標記。注意,若想使用此類型,必須啓用--noImplicitThis
jsx
語法的文件都須要以tsx
後綴命名Component<P, S>
泛型參數聲明,來代替PropTypes
!window
對象屬性,統一在項目根下的global.d.ts
中進行聲明定義types/
目錄下定義好其結構化類型聲明class App extends Component<IProps, IState> { static defaultProps = { // ... } readonly state = { // ... }; // 小技巧:若是state很複雜不想一個個都初始化, // 能夠結合類型斷言初始化state爲空對象或者只包含少數必須的值的對象: // readonly state = {} as IState; }
須要特別強調的是,若是用到了state
,除了在聲明組件時經過泛型參數傳遞其state
結構,還須要在初始化state
時聲明爲 readonly
這是由於咱們使用 class properties 語法對state作初始化時,會覆蓋掉Component<P, S>
中對state
的readonly
標識。
// SFC: stateless function components // v16.7起,因爲hooks的加入,函數式組件也可使用state,因此這個命名不許確。 // 新的react聲明文件裏,也定義了React.FC類型 const List: React.SFC<IProps> = props => null
// 能夠推斷 age 是 number類型 const [age, setAge] = useState(20); // 初始化值爲 null 或者 undefined時,須要顯示指定 name 的類型 const [name, setName] = useState<string>(); // 初始化值爲一個對象時 interface People { name: string; age: number; country?: string; } const [owner, setOwner] = useState<People>({name: 'rrd_fe', age: 5}); // 初始化值是一個數組時 const [members, setMembers] = useState<People[]([]);
// bad one class App extends Component { state = { a: 1, b: 2 } componentDidMount() { this.state.a // ok: 1 // 假如經過setState設置並不存在的c,TS沒法檢查到。 this.setState({ c: 3 }); this.setState(true); // ??? } // ... } // React Component class Component<P, S> { constructor(props: Readonly<P>); setState<K extends keyof S>( state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null), callback?: () => void ): void; forceUpdate(callBack?: () => void): void; render(): ReactNode; readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>; state: Readonly<S>; context: any; refs: { [key: string]: ReactInstance }; } // interface IState{ // a: number, // b: number // } // good one class App extends Component<{}, { a: number, b: number }> { readonly state = { a: 1, b: 2 } //readonly state = {} as IState,斷言所有爲一個值 componentDidMount() { this.state.a // ok: 1 //正確的使用了 ts 泛型指示了 state 之後就會有正確的提示 // error: '{ c: number }' is not assignable to parameter of type '{ a: number, b: number }' this.setState({ c: 3 }); } // ... }
什麼是 react 高階組件? 裝飾器?
第一,是否還能使用裝飾器語法調用高階組件?
import { RouteComponentProps } from 'react-router-dom'; const App = withRouter(class extends Component<RouteComponentProps> { // ... }); // 如下調用是ok的 <App />
如上例子,咱們在聲明組件時,註解了組件的props是路由的RouteComponentProps結構類型,可是咱們在調用App組件時,並不須要告知RouteComponentProps裏具備的location、history等值,這是由於withRouter這個函數自身對其作了正確的類型聲明。
第二,使用裝飾器語法或者沒有函數類型簽名的高階組件怎麼辦?
就是將高階組件注入的屬性都聲明可選(經過Partial這個映射類型),或者將其聲明到額外的injected組件實例屬性上。
import { RouteComponentProps } from 'react-router-dom'; // 方法一 @withRouter class App extends Component<Partial<RouteComponentProps>> { public componentDidMount() { // 這裏就須要使用非空類型斷言了 this.props.history!.push('/'); } // ... }); // 方法二 @withRouter class App extends Component<{}> { get injected() { return this.props as RouteComponentProps } public componentDidMount() { this.injected.history.push('/'); } // ...
interface IVisible { visible: boolean; } //排除 IVisible function withVisible<Self>(WrappedComponent: React.ComponentType<Self & IVisible>): React.ComponentType<Omit<Self, 'visible'>> { return class extends Component<Self> { render() { return <WrappedComponent {...this.props} visible={true} /> } } }
參考文章
TypeScript 入門教程
使用 TypeScript 裝飾器裝飾你的代碼
優雅的在 react 中使用 TypeScript
TypeScript 中使用React Hook
TypeScript體系調研報告