typescript修煉指南(四)

Alleyine發表於2019-12-31

typescript修煉指南(四)

大綱

本章主要是一些ts的型別工具以及模組化和相關實踐,涉及以下內容:

  • 型別程式設計
  • module和namespace
  • 小技巧
  • ts在react中的實踐

這篇稍微有點零碎,建議多查閱文件或者查閱相關的文章進行更多的瞭解,程式碼示例以告知 QAQ

往期推薦:


型別程式設計

  • 索引型別(假定我們要取出如下物件的name屬性對應的值)
const user = {
    name: 'lili',
    age: 20,
    sex: 'woman',
}

// 宣告型別
interface User {
    [key: string]: any
}

function choose(obj: User, key: string[]): any[] {
    return key.map(item => obj[item]) //  返回對應屬性的值 返回的是陣列
}

choose(user, ['name'])
複製程式碼
  • 索引型別查詢操作符
class Person1 {
    public name: string = 'lili'
    public age: number = 20
}

type getTypePerson = keyof Person1


interface Point {
    x: number;
    y: number;
}

// type keys = "x" | "y"
type keys = keyof Point;
複製程式碼
  • 索引訪問操作符 T[K][], 之前也提到過
function Person2<T, K extends keyof T>(obj: T, name: K[]): T[K][] {
        return name.map(item => obj[item])
    }

    const User2 = { name: 'lili', age: 20}

    Person2(User2, ['name', 'age'])

    // 以下是程式碼提示: (相對更加嚴謹)
    // (local function) Person2<{
    //     name: string;
    //     age: number;
    // }, "name" | "age">(obj: {
    //     name: string;
    //     age: number;
    // }, name: ("name" | "age")[]): (string | number)[]
複製程式碼
  • 對映型別 [K in keyof T]
interface User3interface {
    name: string,
    age: number,
}

// 新增每個都為可選型別
type User3<T> = {
    [K in keyof T]?: T[K]
}

type User3s = User3<User3interface>
// 程式碼提示:
// type User3s = {
//     name?: string | undefined;
//     age?: number | undefined;
// }
複製程式碼
  • 截獲函式返回值型別 ---- ReturnType
interface User4Interface {
    name: string,
    age: number,
}

type UserType = () => User4Interface
type User4 = ReturnType<UserType> // User4Interface
複製程式碼
  • 型別遞迴
interface Other {
    hobby: string,
    sex: string,
}
interface User5 {
    name: string,
    age: number,
    other: Other
}

// 為每個型別新增一個可選型別
// 1. 型別工具 Partial
type U1 = Partial<User5>
// 程式碼提示:
// type U1 = {
//     name?: string | undefined;
//     age?: number | undefined;
//     other?: Other | undefined;
// }

// 2. 手動實現
type Particals<T> = {
    [U in keyof T]?: T[U] extends object ? Particals<T[U]> : T[U]
}

type U2 = Particals<User5>

// type U2 = {
//     name?: string | undefined;
//     age?: number | undefined;
//     other?: Particals<Other> | undefined;
// }
複製程式碼
  • 工具型別 ’+‘號 ’-‘號 這兩個關鍵字用於對映型別中給屬性新增修飾符,比如-?就代表將可選屬性變為必選,-readonly代表將只讀屬性變為非只讀。比如TS就內建了一個型別工具Required,它的作用是將傳入的屬性變為必選項:
  1. Required 將傳入的屬性變為必選項
type Required<T> = { [P in keyof T]-?: T[P] };
複製程式碼
  1. Exclude 的作用是從 T 中排除出可分配給 U的元素. T extends U指T是否可分配給U
type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<1 | 2, 1 | 3> // -> 2
複製程式碼
  1. Omit
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

type Foo = Omit<{name: string, age: number}, 'name'> // -> { age: number }
複製程式碼
  1. Merge Merge<O1, O2>的作用是將兩個物件的屬性合併:
// type Merge<Obj1, obj2> = Compute<A> + Omit<U, T>
// type obj3 = Merge<obj1, obj2>

type obj1 = {
    name: string,
    age: number
}

type obj2 = {
    sex: string,
    hobby?: string,
}


type Compute<A extends any> =
    A extends Function
    ? A
    : { [K in keyof A]: A[K] }

type obj3 = Compute<obj1 & obj2> 

// type obj3 = {
//     name: string;
//     age: number;
//     sex: string;
//     hobby?: string | undefined;
// }
複製程式碼
  1. Intersection Intersection是Extract與Pick的結合,Intersection<T, U> = Extract<T, U> + Pick<T, U>
type obj4 = {
        name: string,
        age: number,
    }

type obj5 = {
    name: string,
    sex: string,
}

type Intersection<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>  // Extract 提取

type obj6 = Intersection<obj4, obj5>

// type obj6 = {
//     name: string;
// }
複製程式碼
  1. Overwrite<T, U>顧名思義,是用U的屬性覆蓋T的相同屬性.
type obj7 = {
        name: string,
        age: number,
    }

type obj8 = {
    name: number,
}

// import { Diff } from 'utility-types'  要安裝一下庫
type Overwrite<T extends object, U extends object,  I = Diff<T, U> & Intersection<U, T>> = Pick<I, keyof I>

type obj9 = Overwrite<obj7, obj8>

// type obj9 = {
//     name: number;  --- 被重寫了
//     age: number; 
// }


// Mutable 將 T 的所有屬性的 readonly 移除
type Mutable<T> = {
    -readonly [P in keyof T]: T[P]
}

type ob = {
    readonly name: string,
    age: number,
}

type obj10 = Mutable<ob>

// type obj10 = {
//     name: string;
//     age: number;
// }
複製程式碼

module和namespace

  • 全域性變數 比如在1.ts定義:const a = 1, 2.ts定義: const a = 2 這樣全域性中定義的變數會報錯 因為重複宣告瞭 避免全域性變數

  • 匯出型別

export type User = {
    name: string,
    age: number,
}
複製程式碼
  • 匯出多個型別
// 匯出多個型別
type User1 = {
    name: string,
    age: number,
}

interface User2 {
    name: string,
    age: number,
}

// "isolatedModules": false, tsconfig.json 要設定一下
export { User1, User2 }
複製程式碼
  • 重新命名
export { User1 as user1 }
複製程式碼
  • 引入
import { User2 as user2 } from './module';

// 整體匯入
import * as u from './module';

// 同樣的 --- 預設匯出
export default () => {

}
複製程式碼
  • namespace 其實一個名稱空間本質上一個物件,它的作用是將一系列相關的全域性變數組織到一個物件的屬性 如果一個名稱空間在一個單獨的 TypeScript 檔案中,則應使用三斜槓 /// 引用它,語法格式如下:
// index.ts
// 宣告全域性的明明空間
declare namespace USER {
    // 匯出介面
    export interface find { (name: string): string }
    export interface create { (name: string): string }
    export interface update { (name: string): string }
    export interface deleted { (name: string): string }
}

// 引入檔案
/// <reference path = "index.ts" />

// 使用名稱空間的模組
interface API {
    USER: { 
        update: USER.update 
        // .....
    },
}
複製程式碼

小技巧

  • 註釋,利於智慧提示快速定位程式碼
interface User {
    /**
     * 使用者
     */
    name: string,
    age: number,
}

const user: User = { name: 'lili', age: 20 }
// 以下是程式碼提示
// (property) User.name: string
// 使用者
複製程式碼
  • 型別推導
function getUser(name: string): string {
    return name
}

// typeof 獲取整體函式的型別
type getU = typeof getUser
// type getU = (name: string) => string


// h獲取返回值的型別
type returnT = ReturnType<typeof getUser>
// type returnT = string
複製程式碼
  • 巧用Omit 有時候我們需要複用一個型別,但是又不需要此型別內的全部屬性,因此需要剔除某些屬性,這個時候 Omit 就派上用場了。
interface User1 {
    name: string,
    age: number,
    sex: string,
}

type newUser1 = Omit<User1, 'sex'>
// type newUser1 = {
//     name: string;
//     age: number;
// }
複製程式碼
  • Record 高階型別 Record 允許從 Union 型別中建立新型別,Union 型別中的值用作新型別的屬性。有利於型別安全
type List = 'u1' | 'u2' | 'u3'

// 要求使用者表的屬性必須包含 { name: string, age: number } 型別欄位
type UserList = Record<List, { name: string, age: number }>

const userList: UserList = {
    u1: { name: 'lili', age: 20 },
    u2: { name: 'xiaoming', age: 21 },
    u3: { name: 'xiaohong', age: 22 },
}
複製程式碼

react實踐

  • 編寫一個無狀態的元件

寫法1:

// 定義一個介面
interface Rrop {
    /**
     * 這是一個react元件的屬性
     */
    name: string,
    age: number,
}

// 無狀態元件
export const comp: React.SFC<Rrop> = props => {
    const { name, age } = props

    return (
        <div>name: {name} age: {age}</div>
    )
}
複製程式碼

寫法2:

type Props1 = {
    click(e: React.MouseEvent<HTMLElement>): void
    children?: React.ReactNode
}

const handleClick = () => console.log('click')

const Events = ({ click: handleClick}: Props1) => {
    <div onClick={handleClick}></div>
}
複製程式碼
  • 編寫狀態元件 避免狀態被顯示改變 所以 需要賦 readonly
const initialState = {
    name: 'lili',
    age: 20
}

type States = Readonly<typeof initialState>

export class InitialComp extends React.Component {
    // 再次只讀
    public readonly state: States = initialState

    render() {
        return (
             <div>hello</div>
        )
    }
   
}
複製程式碼
  • ref
interface Props {
    name: string,
    age: number,
}

interface State {
    name: string,
}

export class StateComp extends React.Component<Props, State> {
    private inputRef = React.createRef<HTMLInputElement>() // 建立ref   div 元件的話那麼這個型別就是 HTMLDivElement。

    constructor(props: Props) {
        super(props)
        this.state = {     //  (property) React.Component<Props, State, any>.state: Readonly<State> 元件自動為我們分配的
            name: 'lili'
        }
    }

    private setStat(val: string) {
        this.setState({ name: val })
    }

    // onChange事件
    private onChangeInput(e: React.ChangeEvent<HTMLInputElement>) {
        this.setState({ name: e.target.value })
    }

    // form表單事件
    private handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault()
    }

    render() {
        return (
            <>
                <div>{this.state.name}</div>
                <input ref={this.inputRef} onChange={e => this.onChangeInput(e)}/> 
            </>
        )
    }
}
複製程式碼
  • 預設屬性傳遞
interface Other {
    hobby? : string
}


class ClassProp {
    public name: string = ''
    public submit = (name: string): string => name
    public other: Other = {
        hobby: ''
    }
}

// 例項化類傳遞 預設屬性
export class PropsClass extends React.Component<ClassProp, State> {
    // 1. 可以減少程式碼量
    // 2. 減少出錯率
    public static defaultProps = new ClassProp()

    public render() {
        const { name, other } = this.props
        // 如果遇到屬性找不到可以三木運算子判斷下或者是 other!.hobby
        return (
            <div>{ name } { other.hobby}</div>  
        )
    }
}
複製程式碼
  • promise型別問題
// promise型別問題

interface propP<T> {
    name: string,
    age: number,
    classes: T,
}

export class PromiseComp extends React.Component {

    // 返回promise物件型別
    public async getData(name: string): Promise<propP<string[]>> {  // 表示返回的res的引數型別
        return {
            name: 'lili',
            age: 20,
            classes: ['class1','clas2'],
        }
    }

    public request() {
        this.getData('lili').then(res => {
            console.log(res)
        })
    }
}
複製程式碼

到此,系列文章基本完結啦~ 近期也在整理一些 ts 實踐性的文章,主要以react框架為主,過段時間會陸續上傳,如果對大家有幫助記得點個贊~ , 如有錯誤請指正, 我們一起解決,一起進步。

相關文章