熟悉 TypeScript (參考書籍:2ality's guide, 初學者建議閱讀:chibicode's tutorial)html
熟讀 React 官方文檔 TS部分前端
熟讀 TypeScript playground React部分react
本文檔參考 TypeScript 最新版本git
import * as React from 'react'
import * as ReactDOM from 'react-dom'
複製代碼
這種引用方式被證實是最可靠的一種方式, 推薦使用。github
而另一種引用方式:typescript
import React from 'react'
import ReactDOM from 'react-dom'
複製代碼
須要添加額外的配置:"allowSyntheticDefaultImports": true小程序
聲明的幾種方式數組
第一種:也是比較推薦的一種,使用 React.FunctionComponent,簡寫形式:React.FC:promise
// Great
type AppProps = {
message: string
}
const App: React.FC<AppProps> = ({ message, children }) => (
<div> {message} {children} </div>
)
複製代碼
使用用React.FC聲明函數組件和普通聲明以及 PropsWithChildren 的區別是:babel
React.FC對靜態屬性:displayName、propTypes、defaultProps提供了類型檢查和自動補全
React.FC爲children提供了隱式的類型(ReactElement | null),可是目前,提供的類型存在一些 issue(問題)
好比如下用法 React.FC 會報類型錯誤:
const App: React.FC = props => props.children
const App: React.FC = () => [1, 2, 3]
const App: React.FC = () => 'hello'
複製代碼
解決方法:
const App: React.FC<{}> = props => props.children as any
const App: React.FC<{}> = () => [1, 2, 3] as any
const App: React.FC<{}> = () => 'hello' as any
// 或者
const App: React.FC<{}> = props => (props.children as unknown) as JSX.Element
const App: React.FC<{}> = () => ([1, 2, 3] as unknown) as JSX.Element
const App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element
複製代碼
在一般狀況下,使用 React.FC 的方式聲明最簡單有效,推薦使用;若是出現類型不兼容問題,建議使用如下兩種方式:
第二種:使用 PropsWithChildren,這種方式能夠爲你省去頻繁定義 children 的類型,自動設置 children 類型爲 ReactNode:
type AppProps = React.PropsWithChildren<{ message: string }>
const App = ({ message, children }: AppProps) => (
<div> {message} {children} </div>
)
複製代碼
第三種:直接聲明:
type AppProps = {
message: string
children?: React.ReactNode
}
const App = ({ message, children }: AppProps) => (
<div> {message} {children} </div>
)
複製代碼
大部分狀況下,TS 會自動爲你推導 state 的類型:
// `val`會推導爲boolean類型, toggle接收boolean類型參數
const [val, toggle] = React.useState(false)
// obj會自動推導爲類型: {name: string}
const [obj] = React.useState({ name: 'sj' })
// arr會自動推導爲類型: string[]
const [arr] = React.useState(['One', 'Two'])
複製代碼
使用推導類型做爲接口/類型:
export default function App() {
// user會自動推導爲類型: {name: string}
const [user] = React.useState({ name: 'sj', age: 32 })
const showUser = React.useCallback((obj: typeof user) => {
return `My name is ${obj.name}, My age is ${obj.age}`
}, [])
return <div className="App">用戶: {showUser(user)}</div>
}
複製代碼
可是,一些狀態初始值爲空時(null),須要顯示地聲明類型:
type User = {
name: string
age: number
}
const [user, setUser] = React.useState<User | null>(null)
複製代碼
當初始值爲 null 時,有兩種建立方式:
const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)
複製代碼
這兩種的區別在於:
const ref = React.useRef(0)
React.useEffect(() => {
ref.current += 1
}, [])
複製代碼
這兩種方式在使用時,都須要對類型進行檢查:
const onButtonClick = () => {
ref1.current?.focus()
ref2.current?.focus()
}
複製代碼
在某種狀況下,能夠省去類型檢查,經過添加 ! 斷言,不推薦:
// Bad
function MyComponent() {
const ref1 = React.useRef<HTMLDivElement>(null!)
React.useEffect(() => {
// 不須要作類型檢查,須要人爲保證ref1.current.focus必定存在
doSomethingWith(ref1.current.focus())
})
return <div ref={ref1}> etc </div>
}
複製代碼
useEffect 須要注意回調函數的返回值只能是函數或者 undefined
function App() {
// undefined做爲回調函數的返回值
React.useEffect(() => {
// do something...
}, [])
// 返回值是一個函數
React.useEffect(() => {
// do something...
return () => {}
}, [])
}
複製代碼
useMemo 和 useCallback 均可以直接從它們返回的值中推斷出它們的類型
useCallback 的參數必須制定類型,不然ts不會報錯,默認指定爲 any
const value = 10
// 自動推斷返回值爲 number
const result = React.useMemo(() => value * 2, [value])
// 自動推斷 (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
multiplier,
])
複製代碼
同時也支持傳入泛型, useMemo 的泛型指定了返回值類型,useCallback 的泛型指定了參數類型
// 也能夠顯式的指定返回值類型,返回值不一致會報錯
const result = React.useMemo<string>(() => 2, [])
// 類型「() => number」的參數不能賦給類型「() => string」的參數。
const handleChange = React.useCallback<
React.ChangeEventHandler<HTMLInputElement>
>(evt => {
console.log(evt.target.value)
}, [])
複製代碼
須要注意,自定義 Hook 的返回值若是是數組類型,TS 會自動推導爲 Union 類型,而咱們實際須要的是數組裏裏每一項的具體類型,須要手動添加 const 斷言 進行處理:
function useLoading() {
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() => setState(false))
}
// 實際須要: [boolean, typeof load] 類型
// 而不是自動推導的:(boolean | typeof load)[]
return [isLoading, load] as const
}
複製代碼
若是使用 const 斷言遇到問題,也能夠直接定義返回類型:
export function useLoading(): [ boolean, (aPromise: Promise<any>) => Promise<any> ] {
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() => setState(false))
}
return [isLoading, load]
}
複製代碼
若是有大量的自定義 Hook 須要處理,這裏有一個方便的工具方法能夠處理 tuple 返回值:
function tuplify<T extends any[]>(...elements: T) {
return elements
}
function useLoading() {
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() => setState(false))
}
// (boolean | typeof load)[]
return [isLoading, load]
}
function useTupleLoading() {
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() => setState(false))
}
// [boolean, typeof load]
return tuplify(isLoading, load)
}
複製代碼
大部分文章都不推薦使用 defaultProps , 相關討論能夠**參考連接**
推薦方式:使用默認參數值來代替默認屬性:
type GreetProps = { age?: number }
const Greet = ({ age = 21 }: GreetProps) => {
/* ... */
}
複製代碼
TypeScript3.0+ 在默認屬性 的類型推導上有了極大的改進,雖然尚且存在一些邊界case仍然存在問題,不推薦使用,若是有須要使用的場景,可參照以下方式:
type IProps = {
name: string
}
const defaultProps = {
age: 25,
}
// 類型定義
type GreetProps = IProps & typeof defaultProps
const Greet = (props: GreetProps) => <div></div>
Greet.defaultProps = defaultProps
// 使用
const TestComponent = (props: React.ComponentProps<typeof Greet>) => {
return <h1 />
}
const el = <TestComponent name="foo" />
複製代碼
在平常的react開發中 interface 和 type 的使用場景十分相似
implements 與 extends 靜態操做,不容許存在一種或另外一種實現的狀況,因此不支持使用聯合類型:
class Point {
x: number = 2
y: number = 3
}
interface IShape {
area(): number
}
type Perimeter = {
perimeter(): number
}
type RectangleShape = (IShape | Perimeter) & Point
class Rectangle implements RectangleShape {
// 類只能實現具備靜態已知成員的對象類型或對象類型的交集。
x = 2
y = 3
area() {
return this.x + this.y
}
}
interface ShapeOrPerimeter extends RectangleShape {}
// 接口只能擴展使用靜態已知成員的對象類型或對象類型的交集
複製代碼
有幾種經常使用規則:
interface 和 type 在ts中是兩個不一樣的概念,但在 React 大部分使用的 case 中,interface 和 type 能夠達到相同的功能效果,type 和 interface 最大的區別是:
interface Animal {
name: string
}
// 能夠繼續在原有屬性基礎上,添加新屬性:color
interface Animal {
color: string
}
/********************************/
type Animal = {
name: string
}
// type類型不支持屬性擴展
// Error: Duplicate identifier 'Animal'
type Animal = {
color: string
}
複製代碼
某些場景下咱們在引入第三方的庫時會發現想要使用的組件並無導出咱們須要的組件參數類型或者返回值類型,這時候咱們能夠經過 ComponentProps/ ReturnType 來獲取到想要的類型。
// 獲取參數類型
import { Button } from 'library' // 可是未導出props type
type ButtonProps = React.ComponentProps<typeof Button> // 獲取props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick
const AlertButton: React.FC<AlertButtonProps> = props => (
<Button onClick={() => alert('hello')} {...props} />
)
複製代碼
// 獲取返回值類型
function foo() {
return { baz: 1 }
}
type FooReturn = ReturnType<typeof foo> // { baz: number }
複製代碼
一般咱們使用 type 來定義 Props,爲了提升可維護性和代碼可讀性,在平常的開發過程當中咱們但願能夠添加清晰的註釋。
如今有這樣一個 type
type OtherProps = {
name: string
color: string
}
複製代碼
在使用的過程當中,hover 對應類型會有以下展現
// type OtherProps = {
// name: string;
// color: string;
// }
const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
<h1>My Website Heading</h1>
)
複製代碼
增長相對詳細的註釋,使用時會更清晰,須要注意,註釋須要使用 /**/ , // 沒法被 vscode 識別
// Great
/** * @param color color * @param children children * @param onClick onClick */
type Props = {
/** color */
color?: string
/** children */
children: React.ReactNode
/** onClick */
onClick: () => void
}
// type Props
// @param color — color
// @param children — children
// @param onClick — onClick
const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
return (
<button style={{ backgroundColor: color }} onClick={onClick}> {children} </button>
)
}
複製代碼
type AppProps = {
message: string
count: number
disabled: boolean
/** array of a type! */
names: string[]
/** string literals to specify exact string values, with a union type to join them together */
status: 'waiting' | 'success'
/** 任意須要使用其屬性的對象(不推薦使用,可是做爲佔位頗有用) */
obj: object
/** 做用和`object`幾乎同樣,和 `Object`徹底同樣 */
obj2: {}
/** 列出對象所有數量的屬性 (推薦使用) */
obj3: {
id: string
title: string
}
/** array of objects! (common) */
objArr: {
id: string
title: string
}[]
/** 任意數量屬性的字典,具備相同類型*/
dict1: {
[key: string]: MyTypeHere
}
/** 做用和dict1徹底相同 */
dict2: Record<string, MyTypeHere>
/** 任意徹底不會調用的函數 */
onSomething: Function
/** 沒有參數&返回值的函數 */
onClick: () => void
/** 攜帶參數的函數 */
onChange: (id: number) => void
/** 攜帶點擊事件的函數 */
onClick(event: React.MouseEvent<HTMLButtonElement>): void
/** 可選的屬性 */
optional?: OptionalType
}
複製代碼
export declare interface AppBetterProps {
children: React.ReactNode // 通常狀況下推薦使用,支持全部類型 Great
functionChildren: (name: string) => React.ReactNode
style?: React.CSSProperties // 傳遞style對象
onChange?: React.FormEventHandler<HTMLInputElement>
}
export declare interface AppProps {
children1: JSX.Element // 差, 不支持數組
children2: JSX.Element | JSX.Element[] // 通常, 不支持字符串
children3: React.ReactChildren // 忽略命名,不是一個合適的類型,工具類類型
children4: React.ReactChild[] // 很好
children: React.ReactNode // 最佳,支持全部類型 推薦使用
functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type
style?: React.CSSProperties // 傳遞style對象
onChange?: React.FormEventHandler<HTMLInputElement> // 表單事件, 泛型參數是event.target的類型
}
複製代碼
change 事件,有兩個定義參數類型的方法。
第一種方法使用推斷的方法簽名(例如:React.FormEvent <HTMLInputElement> :void)
import * as React from 'react'
type changeFn = (e: React.FormEvent<HTMLInputElement>) => void
const App: React.FC = () => {
const [state, setState] = React.useState('')
const onChange: changeFn = e => {
setState(e.currentTarget.value)
}
return (
<div> <input type="text" value={state} onChange={onChange} /> </div>
)
}
複製代碼
第二種方法強制使用 @types / react 提供的委託類型,兩種方法都可。
import * as React from 'react'
const App: React.FC = () => {
const [state, setState] = React.useState('')
const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
setState(e.currentTarget.value)
}
return (
<div> <input type="text" value={state} onChange={onChange} /> </div>
)
}
複製代碼
若是不太關心事件的類型,能夠直接使用 React.SyntheticEvent,若是目標表單有想要訪問的自定義命名輸入,可使用類型擴展
import * as React from 'react'
const App: React.FC = () => {
const onSubmit = (e: React.SyntheticEvent) => {
e.preventDefault()
const target = e.target as typeof e.target & {
password: { value: string }
} // 類型擴展
const password = target.password.value
}
return (
<form onSubmit={onSubmit}> <div> <label> Password: <input type="password" name="password" /> </label> </div> <div> <input type="submit" value="Log in" /> </div> </form>
)
}
複製代碼
經常使用的操做符,經常使用於類型判斷
keyof: 獲取object的key
O[K]: 屬性查找
[K in O]: 映射類型
+ or - or readonly or ?: 加法、減法、只讀和可選修飾符
x ? Y : Z: 用於泛型類型、類型別名、函數參數類型的條件類型
!: 可空類型的空斷言
as: 類型斷言
is: 函數返回類型的類型保護
經過查找類型減小 type 的非必要導出,若是須要提供複雜的 type,應當提取到做爲公共 API 導出的文件中。
如今咱們有一個 Counter 組件,須要 name 這個必傳參數:
// counter.tsx
import * as React from 'react'
export type Props = {
name: string
}
const Counter: React.FC<Props> = props => {
return <></>
}
export default Counter
複製代碼
在其餘引用它的組件中咱們有兩種方式獲取到 Counter 的參數類型
第一種是經過 typeof 操做符(推薦)
// Great
import Counter from './d-tips1'
type PropsNew = React.ComponentProps<typeof Counter> & {
age: number
}
const App: React.FC<PropsNew> = props => {
return <Counter {...props} />
}
export default App
複製代碼
第二種是經過在原組件進行導出
import Counter, { Props } from './d-tips1'
type PropsNew = Props & {
age: number
}
const App: React.FC<PropsNew> = props => {
return (
<> <Counter {...props} /> </>
)
}
export default App
複製代碼
保持一致性,類型/接口的全部成員都經過相同的語法定義。
--strictFunctionTypes 在比較函數類型時強制執行更嚴格的類型檢查,但第一種聲明方式下嚴格檢查不生效。
✅
interface ICounter {
start: (value: number) => string
}
❌
interface ICounter1 {
start(value: number): string
}
🌰
interface Animal {}
interface Dog extends Animal {
wow: () => void
}
interface Comparer<T> {
compare: (a: T, b: T) => number
}
declare let animalComparer: Comparer<Animal>
declare let dogComparer: Comparer<Dog>
animalComparer = dogComparer // Error
dogComparer = animalComparer // Ok
interface Comparer1<T> {
compare(a: T, b: T): number
}
declare let animalComparer1: Comparer1<Animal>
declare let dogComparer1: Comparer1<Dog>
animalComparer1 = dogComparer // Ok
dogComparer1 = animalComparer // Ok
複製代碼
咱們在進行事件註冊時常常會在事件處理函數中使用 event 事件對象,例如當使用鼠標事件時咱們經過 clientX、clientY 去獲取指針的座標。
你們可能會想到直接把 event 設置爲 any 類型,可是這樣就失去了咱們對代碼進行靜態檢查的意義。
function handleEvent(event: any) {
console.log(event.clientY)
}
複製代碼
試想下當咱們註冊一個 Touch 事件,而後錯誤的經過事件處理函數中的 event 對象去獲取其 clientY 屬性的值,在這裏咱們已經將 event 設置爲 any 類型,致使 TypeScript 在編譯時並不會提示咱們錯誤, 當咱們經過 event.clientY 訪問時就有問題了,由於 Touch 事件的 event 對象並無 clientY 這個屬性。
經過 interface 對 event 對象進行類型聲明編寫的話又十分浪費時間,幸運的是 React 的聲明文件提供了 Event 對象的類型聲明。
DragEvent<T =Element> 拖拽事件對象
ChangeEvent<T = Element> Change事件對象
KeyboardEvent<T = Element> 鍵盤事件對象
MouseEvent<T = Element> 鼠標事件對象
TouchEvent<T = Element> 觸摸事件對象
WheelEvent<T = Element> 滾輪時間對象
AnimationEvent<T = Element> 動畫事件對象
TransitionEvent<T = Element> 過渡事件對象
當咱們定義事件處理函數時有沒有更方便定義其函數類型的方式呢?答案是使用 React 聲明文件所提供的 EventHandler 類型別名,經過不一樣事件的 EventHandler 的類型別名來定義事件處理函數的類型
type EventHandler<E extends React.SyntheticEvent<any>> = {
bivarianceHack(event: E): void
}['bivarianceHack']
type ReactEventHandler<T = Element> = EventHandler<React.SyntheticEvent<T>>
type ClipboardEventHandler<T = Element> = EventHandler<React.ClipboardEvent<T>>
type DragEventHandler<T = Element> = EventHandler<React.DragEvent<T>>
type FocusEventHandler<T = Element> = EventHandler<React.FocusEvent<T>>
type FormEventHandler<T = Element> = EventHandler<React.FormEvent<T>>
type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>
type TouchEventHandler<T = Element> = EventHandler<React.TouchEvent<T>>
type PointerEventHandler<T = Element> = EventHandler<React.PointerEvent<T>>
type UIEventHandler<T = Element> = EventHandler<React.UIEvent<T>>
type WheelEventHandler<T = Element> = EventHandler<React.WheelEvent<T>>
type AnimationEventHandler<T = Element> = EventHandler<React.AnimationEvent<T>>
type TransitionEventHandler<T = Element> = EventHandler<
React.TransitionEvent<T>
>
複製代碼
bivarianceHack 爲事件處理函數的類型定義,函數接收一個 event 對象,而且其類型爲接收到的泛型變量 E 的類型, 返回值爲 void
關於爲什麼是用bivarianceHack而不是(event: E): void,這與strictfunctionTypes選項下的功能兼容性有關。(event: E): void,若是該參數是派生類型,則不能將其傳遞給參數是基類的函數。
class Animal {
private x: undefined
}
class Dog extends Animal {
private d: undefined
}
type EventHandler<E extends Animal> = (event: E) => void
let z: EventHandler<Animal> = (o: Dog) => {} // fails under strictFunctionTyes
type BivariantEventHandler<E extends Animal> = {
bivarianceHack(event: E): void
}['bivarianceHack']
let y: BivariantEventHandler<Animal> = (o: Dog) => {}
複製代碼
在作異步操做時咱們常用 async 函數,函數調用時會 return 一個 Promise 對象,可使用 then 方法添加回調函數。Promise<T> 是一個泛型類型,T 泛型變量用於肯定 then 方法時接收的第一個回調函數的參數類型。
type IResponse<T> = {
message: string
result: T
success: boolean
}
async function getResponse(): Promise<IResponse<number[]>> {
return {
message: '獲取成功',
result: [1, 2, 3],
success: true,
}
}
getResponse().then(response => {
console.log(response.result)
})
複製代碼
首先聲明 IResponse 的泛型接口用於定義 response 的類型,經過 T 泛型變量來肯定 result 的類型。而後聲明瞭一個 異步函數 getResponse 而且將函數返回值的類型定義爲 Promise<IResponse<number[]>> 。最後調用 getResponse 方法會返回一個 promise 類型,經過 then 調用,此時 then 方法接收的第一個回調函數的參數 response 的類型爲,{ message: string, result: number[], success: boolean} 。
下面這個組件的name屬性都是指定了傳參格式,若是想不指定,而是想經過傳入參數的類型去推導實際類型,這就要用到泛型。
const TestB = ({ name, name2 }: { name: string; name2?: string }) => {
return (
<div className="test-b"> TestB--{name} {name2} </div>
)
}
複製代碼
若是須要外部傳入參數類型,只需 ->
type Props<T> = {
name: T
name2?: T
}
const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => {
return (
<div className="test-b"> TestB--{name} {name2} </div>
)
}
const TestD = () => {
return (
<div> <TestC<string> name="123" /> </div> ) } 複製代碼
當你的函數,接口或者類:
當咱們須要一個 id 函數,函數的參數能夠是任何值,返回值就是將參數原樣返回,而且其只能接受一個參數,在 js 時代咱們會很輕易地甩出一行
const id = arg => arg
複製代碼
因爲其能夠接受任意值,也就是說咱們的函數的入參和返回值都應該能夠是任意類型,若是不使用泛型,咱們只能重複的進行定義
type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string
// ...
複製代碼
若是使用泛型,咱們只須要
function id<T>(arg: T): T {
return arg
}
// 或
const id1: <T>(arg: T) => T = arg => {
return arg
}
複製代碼
功能是將類型的屬性變成可選, 注意這是淺 Partial。
type Partial<T> = { [P in keyof T]?: T[P] }
複製代碼
若是須要深 Partial 咱們能夠經過泛型遞歸來實現
type DeepPartial<T> = T extends Function
? T
: T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T
type PartialedWindow = DeepPartial<Window>
複製代碼
咱們是字節跳動旗下懂車帝產品線,目前業務上正處於高速發展階段,懂車帝自2017年8月正式誕生,僅三年時間已是汽車互聯網行業第二。
如今前端團隊主流的技術棧是React、Typescript,主要負責懂車帝App、M站、PC站、懂車帝小程序產品矩陣、商業化海量業務,商業數據產品等。咱們在類客戶端、多宿主、技術建站、中後臺系統、全棧、富交互等多種應用場景都有大量技術實踐,致力於技術驅動業務發展,探索全部可能性。
加入懂車帝,一塊兒打造汽車領域最專業最開放的前端團隊!
郵件標題:應聘+城市+崗位名稱