React + TypeScript 實(shí)踐
?? 準(zhǔn)備知識(shí)
熟悉 React
熟悉 TypeScript (參考書籍:2ality's guide[1], 初學(xué)者建議閱讀:chibicode's tutorial[2])
熟讀 React 官方文檔 TS 部分[3]
熟讀 TypeScript playground React 部分[4]
本文檔參考 TypeScript 最新版本
如何引入 React
import * as React from 'react'
import * as ReactDOM from 'react-dom'
這種引用方式被證明[5]是最可靠的一種方式, 推薦使用。
而另外一種引用方式:
import React from 'react'
import ReactDOM from 'react-dom'
需要添加額外的配置:"allowSyntheticDefaultImports": true
函數(shù)式組件的聲明方式
聲明的幾種方式
第一種:也是比較推薦的一種,使用 React.FunctionComponent,簡(jiǎn)寫形式:React.FC:
// Great
type AppProps = {
message: string
}
const App: React.FC<AppProps> = ({ message, children }) => (
<div>
{message}
{children}
</div>
)
使用用 React.FC 聲明函數(shù)組件和普通聲明以及 PropsWithChildren 的區(qū)別是:
React.FC 顯式地定義了返回類型,其他方式是隱式推導(dǎo)的
React.FC 對(duì)靜態(tài)屬性:displayName、propTypes、defaultProps 提供了類型檢查和自動(dòng)補(bǔ)全
React.FC 為 children 提供了隱式的類型(ReactElement | null),但是目前,提供的類型存在一些 issue[6](問題)
比如以下用法 React.FC 會(huì)報(bào)類型錯(cuò)誤:
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 的方式聲明最簡(jiǎn)單有效,推薦使用;如果出現(xiàn)類型不兼容問題,建議使用以下兩種方式:
第二種:使用 PropsWithChildren,這種方式可以為你省去頻繁定義 children 的類型,自動(dòng)設(shè)置 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>
)
Hooks
useState<T>
大部分情況下,TS 會(huì)自動(dòng)為你推導(dǎo) state 的類型:
// `val`會(huì)推導(dǎo)為boolean類型, toggle接收boolean類型參數(shù)
const [val, toggle] = React.useState(false)
// obj會(huì)自動(dòng)推導(dǎo)為類型: {name: string}
const [obj] = React.useState({ name: 'sj' })
// arr會(huì)自動(dòng)推導(dǎo)為類型: string[]
const [arr] = React.useState(['One', 'Two'])
使用推導(dǎo)類型作為接口/類型:
export default function App() {
// user會(huì)自動(dòng)推導(dǎo)為類型: {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>
}
但是,一些狀態(tài)初始值為空時(shí)(null),需要顯示地聲明類型:
type User = {
name: string
age: number
}const [user, setUser] = React.useState<User | null>(null)
useRef<T>
當(dāng)初始值為 null 時(shí),有兩種創(chuàng)建方式:
const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)
這兩種的區(qū)別在于:
第一種方式的 ref1.current 是只讀的(read-only),并且可以傳遞給內(nèi)置的 ref 屬性,綁定 DOM 元素 ; 第二種方式的 ref2.current 是可變的(類似于聲明類的成員變量)
const ref = React.useRef(0)
React.useEffect(() => {
ref.current += 1
}, [])
這兩種方式在使用時(shí),都需要對(duì)類型進(jìn)行檢查:
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
useEffect 需要注意回調(diào)函數(shù)的返回值只能是函數(shù)或者 undefined
function App() {
// undefined作為回調(diào)函數(shù)的返回值
React.useEffect(() => {
// do something...
}, [])
// 返回值是一個(gè)函數(shù)
React.useEffect(() => {
// do something...
return () => {}
}, [])
}
useMemo<T> / useCallback<T>
useMemo 和 useCallback 都可以直接從它們返回的值中推斷出它們的類型
useCallback 的參數(shù)必須制定類型,否則 ts 不會(huì)報(bào)錯(cuò),默認(rèn)指定為 any
const value = 10
// 自動(dòng)推斷返回值為 number
const result = React.useMemo(() => value * 2, [value])
// 自動(dòng)推斷 (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
multiplier,
])
同時(shí)也支持傳入泛型, useMemo 的泛型指定了返回值類型,useCallback 的泛型指定了參數(shù)類型
// 也可以顯式的指定返回值類型,返回值不一致會(huì)報(bào)錯(cuò)
const result = React.useMemo<string>(() => 2, [])
// 類型“() => number”的參數(shù)不能賦給類型“() => string”的參數(shù)。
const handleChange = React.useCallback<
React.ChangeEventHandler<HTMLInputElement>
>(evt => {
console.log(evt.target.value)
}, [])
自定義 Hooks
需要注意,自定義 Hook 的返回值如果是數(shù)組類型,TS 會(huì)自動(dòng)推導(dǎo)為 Union 類型,而我們實(shí)際需要的是數(shù)組里里每一項(xiàng)的具體類型,需要手動(dòng)添加 const 斷言 進(jìn)行處理:
function useLoading() {
const [isLoading, setState] = React.useState(false)
const load = (aPromise: Promise<any>) => {
setState(true)
return aPromise.then(() => setState(false))
}
// 實(shí)際需要: [boolean, typeof load] 類型
// 而不是自動(dòng)推導(dǎo)的:(boolean | typeof load)[]
return [isLoading, load] as const
}
如果使用 const 斷言遇到問題[7],也可以直接定義返回類型:
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 需要處理,這里有一個(gè)方便的工具方法可以處理 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)
}
默認(rèn)屬性 defaultProps
大部分文章都不推薦使用 defaultProps , 相關(guān)討論可以點(diǎn)擊參考鏈接[8]
推薦方式:使用默認(rèn)參數(shù)值來代替默認(rèn)屬性:
type GreetProps = { age?: number }
const Greet = ({ age = 21 }: GreetProps) => {
/* ... */
}
defaultProps 類型
TypeScript3.0+[9] 在默認(rèn)屬性 的類型推導(dǎo)上有了極大的改進(jìn),雖然尚且存在一些邊界 case 仍然存在問題[10],不推薦使用,如果有需要使用的場(chǎng)景,可參照如下方式:
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" />
Types or Interfaces
在日常的 react 開發(fā)中 interface 和 type 的使用場(chǎng)景十分類似
implements 與 extends 靜態(tài)操作,不允許存在一種或另一種實(shí)現(xiàn)的情況,所以不支持使用聯(lián)合類型:
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 {
// 類只能實(shí)現(xiàn)具有靜態(tài)已知成員的對(duì)象類型或?qū)ο箢愋偷慕患?/span>
x = 2
y = 3
area() {
return this.x + this.y
}
}
interface ShapeOrPerimeter extends RectangleShape {}
// 接口只能擴(kuò)展使用靜態(tài)已知成員的對(duì)象類型或?qū)ο箢愋偷慕患?/span>
使用 Type 還是 Interface?
有幾種常用規(guī)則:
在定義公共 API 時(shí)(比如編輯一個(gè)庫)使用 interface,這樣可以方便使用者繼承接口
在定義組件屬性(Props)和狀態(tài)(State)時(shí),建議使用 type,因?yàn)?type的約束性更強(qiáng)
interface 和 type 在 ts 中是兩個(gè)不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以達(dá)到相同的功能效果,type 和 interface 最大的區(qū)別是:
type 類型不能二次編輯,而 interface 可以隨時(shí)擴(kuò)展
interface Animal {
name: string
}
// 可以繼續(xù)在原有屬性基礎(chǔ)上,添加新屬性:color
interface Animal {
color: string
}
/********************************/
type Animal = {
name: string
}
// type類型不支持屬性擴(kuò)展
// Error: Duplicate identifier 'Animal'
type Animal = {
color: string
}
獲取未導(dǎo)出的 Type
某些場(chǎng)景下我們?cè)谝氲谌降膸鞎r(shí)會(huì)發(fā)現(xiàn)想要使用的組件并沒有導(dǎo)出我們需要的組件參數(shù)類型或者返回值類型,這時(shí)候我們可以通過 ComponentProps/ ReturnType 來獲取到想要的類型。
// 獲取參數(shù)類型
import { Button } from 'library' // 但是未導(dǎo)出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 }
Props
通常我們使用 type 來定義 Props,為了提高可維護(hù)性和代碼可讀性,在日常的開發(fā)過程中我們希望可以添加清晰的注釋。
現(xiàn)在有這樣一個(gè) type
type OtherProps = {
name: string
color: string
}
在使用的過程中,hover 對(duì)應(yīng)類型會(huì)有如下展示
// type OtherProps = {
// name: string;
// color: string;
// }
const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
<h1>My Website Heading</h1>
)
增加相對(duì)詳細(xì)的注釋,使用時(shí)會(huì)更清晰,需要注意,注釋需要使用 /**/ , // 無法被 vscode 識(shí)別
// 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>
)
}
常用 Props ts 類型
基礎(chǔ)屬性類型
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'
/** 任意需要使用其屬性的對(duì)象(不推薦使用,但是作為占位很有用) */
obj: object
/** 作用和`object`幾乎一樣,和 `Object`完全一樣 */
obj2: {}
/** 列出對(duì)象全部數(shù)量的屬性 (推薦使用) */
obj3: {
id: string
title: string
}
/** array of objects! (common) */
objArr: {
id: string
title: string
}[]
/** 任意數(shù)量屬性的字典,具有相同類型*/
dict1: {
[key: string]: MyTypeHere
}
/** 作用和dict1完全相同 */
dict2: Record<string, MyTypeHere>
/** 任意完全不會(huì)調(diào)用的函數(shù) */
onSomething: Function
/** 沒有參數(shù)&返回值的函數(shù) */
onClick: () => void
/** 攜帶參數(shù)的函數(shù) */
onChange: (id: number) => void
/** 攜帶點(diǎn)擊事件的函數(shù) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void
/** 可選的屬性 */
optional?: OptionalType
}
常用 React 屬性類型
export declare interface AppBetterProps {
children: React.ReactNode // 一般情況下推薦使用,支持所有類型 Great
functionChildren: (name: string) => React.ReactNode
style?: React.CSSProperties // 傳遞style對(duì)象
onChange?: React.FormEventHandler<HTMLInputElement>
}
export declare interface AppProps {
children1: JSX.Element // 差, 不支持?jǐn)?shù)組
children2: JSX.Element | JSX.Element[] // 一般, 不支持字符串
children3: React.ReactChildren // 忽略命名,不是一個(gè)合適的類型,工具類類型
children4: React.ReactChild[] // 很好
children: React.ReactNode // 最佳,支持所有類型 推薦使用
functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type
style?: React.CSSProperties // 傳遞style對(duì)象
onChange?: React.FormEventHandler<HTMLInputElement> // 表單事件, 泛型參數(shù)是event.target的類型
}
Forms and Events
onChange
change 事件,有兩個(gè)定義參數(shù)類型的方法。
第一種方法使用推斷的方法簽名(例如: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>
)
}
第二種方法強(qiáng)制使用 @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>
)
}
onSubmit
如果不太關(guān)心事件的類型,可以直接使用 React.SyntheticEvent,如果目標(biāo)表單有想要訪問的自定義命名輸入,可以使用類型擴(kuò)展
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 }
} // 類型擴(kuò)展
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>
)
}
Operators
常用的操作符,常用于類型判斷
typeof and instanceof: 用于類型區(qū)分
keyof: 獲取 object 的 key
O[K]: 屬性查找
[K in O]: 映射類型
+ or - or readonly or ?: 加法、減法、只讀和可選修飾符
x ? Y : Z: 用于泛型類型、類型別名、函數(shù)參數(shù)類型的條件類型
!: 可空類型的空斷言
as: 類型斷言
is: 函數(shù)返回類型的類型保護(hù)
Tips
使用查找類型訪問組件屬性類型
通過查找類型減少 type 的非必要導(dǎo)出,如果需要提供復(fù)雜的 type,應(yīng)當(dāng)提取到作為公共 API 導(dǎo)出的文件中。
現(xiàn)在我們有一個(gè) Counter 組件,需要 name 這個(gè)必傳參數(shù):
// counter.tsx
import * as React from 'react'
export type Props = {
name: string
}
const Counter: React.FC<Props> = props => {
return <></>
}
export default Counter
在其他引用它的組件中我們有兩種方式獲取到 Counter 的參數(shù)類型
第一種是通過 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
第二種是通過在原組件進(jìn)行導(dǎo)出
import Counter, { Props } from './d-tips1'
type PropsNew = Props & {
age: number
}
const App: React.FC<PropsNew> = props => {
return (
<>
<Counter {...props} />
</>
)
}
export default App
不要在 type 或 interface 中使用函數(shù)聲明
保持一致性,類型/接口的所有成員都通過相同的語法定義。
--strictFunctionTypes 在比較函數(shù)類型時(shí)強(qiáng)制執(zhí)行更嚴(yán)格的類型檢查,但第一種聲明方式下嚴(yán)格檢查不生效。
?
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
事件處理
我們?cè)谶M(jìn)行事件注冊(cè)時(shí)經(jīng)常會(huì)在事件處理函數(shù)中使用 event 事件對(duì)象,例如當(dāng)使用鼠標(biāo)事件時(shí)我們通過 clientX、clientY 去獲取指針的坐標(biāo)。
大家可能會(huì)想到直接把 event 設(shè)置為 any 類型,但是這樣就失去了我們對(duì)代碼進(jìn)行靜態(tài)檢查的意義。
function handleEvent(event: any) {、
console.log(event.clientY)
}
試想下當(dāng)我們注冊(cè)一個(gè) Touch 事件,然后錯(cuò)誤的通過事件處理函數(shù)中的 event 對(duì)象去獲取其 clientY 屬性的值,在這里我們已經(jīng)將 event 設(shè)置為 any 類型,導(dǎo)致 TypeScript 在編譯時(shí)并不會(huì)提示我們錯(cuò)誤, 當(dāng)我們通過 event.clientY 訪問時(shí)就有問題了,因?yàn)?Touch 事件的 event 對(duì)象并沒有 clientY 這個(gè)屬性。
通過 interface 對(duì) event 對(duì)象進(jìn)行類型聲明編寫的話又十分浪費(fèi)時(shí)間,幸運(yùn)的是 React 的聲明文件提供了 Event 對(duì)象的類型聲明。
Event 事件對(duì)象類型
ClipboardEvent<T = Element> 剪切板事件對(duì)象
DragEvent<T =Element> 拖拽事件對(duì)象
ChangeEvent<T = Element> Change 事件對(duì)象
KeyboardEvent<T = Element> 鍵盤事件對(duì)象
MouseEvent<T = Element> 鼠標(biāo)事件對(duì)象
TouchEvent<T = Element> 觸摸事件對(duì)象
WheelEvent<T = Element> 滾輪時(shí)間對(duì)象
AnimationEvent<T = Element> 動(dòng)畫事件對(duì)象
TransitionEvent<T = Element> 過渡事件對(duì)象
事件處理函數(shù)類型
當(dāng)我們定義事件處理函數(shù)時(shí)有沒有更方便定義其函數(shù)類型的方式呢?答案是使用 React 聲明文件所提供的 EventHandler 類型別名,通過不同事件的 EventHandler 的類型別名來定義事件處理函數(shù)的類型
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 為事件處理函數(shù)的類型定義,函數(shù)接收一個(gè) event 對(duì)象,并且其類型為接收到的泛型變量 E 的類型, 返回值為 void
關(guān)于為何是用 bivarianceHack 而不是(event: E): void,這與 strictfunctionTypes 選項(xiàng)下的功能兼容性有關(guān)。(event: E): void,如果該參數(shù)是派生類型,則不能將其傳遞給參數(shù)是基類的函數(shù)。
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) => {}
Promise 類型
在做異步操作時(shí)我們經(jīng)常使用 async 函數(shù),函數(shù)調(diào)用時(shí)會(huì) return 一個(gè) Promise 對(duì)象,可以使用 then 方法添加回調(diào)函數(shù)。Promise<T> 是一個(gè)泛型類型,T 泛型變量用于確定 then 方法時(shí)接收的第一個(gè)回調(diào)函數(shù)的參數(shù)類型。
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 的類型。然后聲明了一個(gè) 異步函數(shù) getResponse 并且將函數(shù)返回值的類型定義為 Promise<IResponse<number[]>> 。最后調(diào)用 getResponse 方法會(huì)返回一個(gè) promise 類型,通過 then 調(diào)用,此時(shí) then 方法接收的第一個(gè)回調(diào)函數(shù)的參數(shù) response 的類型為,{ message: string, result: number[], success: boolean} 。
泛型參數(shù)的組件
下面這個(gè)組件的 name 屬性都是指定了傳參格式,如果想不指定,而是想通過傳入?yún)?shù)的類型去推導(dǎo)實(shí)際類型,這就要用到泛型。
const TestB = ({ name, name2 }: { name: string; name2?: string }) => {
return (
<div className="test-b">
TestB--{name}
{name2}
</div>
)
}
如果需要外部傳入?yún)?shù)類型,只需 ->
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>
)
}
什么時(shí)候使用泛型
當(dāng)你的函數(shù),接口或者類:
需要作用到很多類型的時(shí)候,舉個(gè) ??
當(dāng)我們需要一個(gè) id 函數(shù),函數(shù)的參數(shù)可以是任何值,返回值就是將參數(shù)原樣返回,并且其只能接受一個(gè)參數(shù),在 js 時(shí)代我們會(huì)很輕易地甩出一行
const id = arg => arg
由于其可以接受任意值,也就是說我們的函數(shù)的入?yún)⒑头祷刂刀紤?yīng)該可以是任意類型,如果不使用泛型,我們只能重復(fù)的進(jìn)行定義
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
}
需要被用到很多地方的時(shí)候,比如常用的工具泛型 Partial。
功能是將類型的屬性變成可選, 注意這是淺 Partial。
type Partial<T> = { [P in keyof T]?: T[P] }
如果需要深 Partial 我們可以通過泛型遞歸來實(shí)現(xiàn)
type DeepPartial<T> = T extends Function
? T
: T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T
type PartialedWindow = DeepPartial<Window>
字節(jié)跳動(dòng)懂車帝團(tuán)隊(duì)招聘
我們是字節(jié)跳動(dòng)旗下懂車帝產(chǎn)品線,目前業(yè)務(wù)上正處于高速發(fā)展階段,懂車帝自 2017 年 8 月正式誕生,僅三年時(shí)間已經(jīng)是汽車互聯(lián)網(wǎng)行業(yè)第二。
現(xiàn)在前端團(tuán)隊(duì)主流的技術(shù)棧是 React、Typescript,主要負(fù)責(zé)懂車帝 App、M 站、PC 站、懂車帝小程序產(chǎn)品矩陣、商業(yè)化海量業(yè)務(wù),商業(yè)數(shù)據(jù)產(chǎn)品等。我們?cè)陬惪蛻舳恕⒍嗨拗鳌⒓夹g(shù)建站、中后臺(tái)系統(tǒng)、全棧、富交互等多種應(yīng)用場(chǎng)景都有大量技術(shù)實(shí)踐,致力于技術(shù)驅(qū)動(dòng)業(yè)務(wù)發(fā)展,探索所有可能性。
加入懂車帝,一起打造汽車領(lǐng)域最專業(yè)最開放的前端團(tuán)隊(duì)!
簡(jiǎn)歷直達(dá):[email protected]
郵件標(biāo)題:應(yīng)聘+城市+崗位名稱
參考資料
2ality's guide: http://2ality.com/2018/04/type-notation-typescript.html
[2]chibicode's tutorial: https://ts.chibicode.com/todo/
[3]TS 部分: https://reactjs.org/docs/static-type-checking.html#typescript
[4]React 部分: http://www.typescriptlang.org/play/index.html?jsx=2&esModuleInterop=true&e=181#example/typescript-with-react
[5]被證明: https://www.reddit.com/r/reactjs/comments/iyehol/import_react_from_react_will_go_away_in_distant/
[6]一些 issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006
[7]問題: https://github.com/babel/babel/issues/9800
[8]參考鏈接: https://twitter.com/hswolff/status/1133759319571345408
[9]TypeScript3.0+: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html
[10]存在一些邊界 case 仍然存在問題: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61
