❗️ 准备知识 :
- 熟悉 React
- 熟悉 Typescript
=>
使用用 React.FC 声明函数组件和普通声明以及 PropsWithChildren 的区别是:- React.FC 显式地定义了返回类型,其他方式是隐式推导的
- React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全
- React.FC 为 children 提供了隐式的类型(ReactElement | null),但是目前,提供的类型存在一些 issue(问题)
解决方法:const
App
:
React
.
FC
=
props
=>
props
.
children
const
App
:
React
.
FC
=
=>
[
1
,
2
,
3
]
const
App
:
React
.
FC
=
=>
'hello'
在通常情况下,使用 React.FC 的方式声明最简单有效,推荐使用;如果出现类型不兼容问题,建议使用以下两种方式:第二种:使用 PropsWithChildren,这种方式可以为你省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode: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
=>
as
JSX
.
Element
const
App
:
React
.
FC
<
{}
>
=
=>
as
JSX
.
Element
const
App
:
React
.
FC
<
{}
>
=
=>
as
JSX
.
Element
第三种:直接声明:type
AppProps
=
React
.
PropsWithChildren
<
{
message
:
string
}
>
const
App
=
=>
HooksuseStatetype
AppProps
=
{
message
:
string
children
:
React
.
ReactNode
}
const
App
=
=>
大部分情况下,TS 会自动为你推导 state 的类型:
使用推导类型作为接口/类型:// `val`会推导为boolean类型, toggle接收boolean类型参数
const
[
val
,
toggle
]
=
React
.
useState
// obj会自动推导为类型: {name: string}
const
[
obj
]
=
React
.
useState
// arr会自动推导为类型: string[]
const
[
arr
]
=
React
.
useState
但是,一些状态初始值为空时(null),需要显示地声明类型:export
default
function
App
{
// user会自动推导为类型: {name: string}
const
[
user
]
=
React
.
useState
const
showUser
=
React
.
useCallback
=>
{
return
`My name is
${
obj
.
name
}
, My age is
${
obj
.
age
}
`
},
[])
return
<
p
className
=
"App"
>
用户
:
{
showUser
}
<
/p>
}
useReftype
User
=
{
name
:
string
age
:
number
}
const
[
user
,
setUser
]
=
React
.
useState
<
User
|
null
>
当初始值为 null 时,有两种创建方式:
这两种的区别在于:const
ref1
=
React
.
useRef
<
HTMLInputElement
>
const
ref2
=
React
.
useRef
<
HTMLInputElement
|
null
>
- 第一种方式的 ref1.current 是只读的(read-only),并且可以传递给内置的 ref 属性,绑定 DOM 元素 ;
- 第二种方式的 ref2.current 是可变的(类似于声明类的成员变量)
这两种方式在使用时,都需要对类型进行检查:const
ref
=
React
.
useRef
React
.
useEffect
=>
{
ref
.
current
+=
1
},
[])
在某种情况下,可以省去类型检查,通过添加 ! 断言,不推荐:const
onButtonClick
=
=>
{
ref1
.
current
.
focus
ref2
.
current
.
focus
}
useEffectuseEffect 需要注意回调函数的返回值只能是函数或者 undefined// Bad
function
MyComponent
{
const
ref1
=
React
.
useRef
<
HTMLDivElement
>
React
.
useEffect
=>
{
// 不需要做类型检查,需要人为保证ref1.current.focus一定存在
doSomethingWith
)
})
return
<
p
ref
=
{
ref1
}
>
etc
<
/p>
}
useMemofunction
App
{
// undefined作为回调函数的返回值
React
.
useEffect
=>
{
// do something...
},
[])
// 返回值是一个函数
React
.
useEffect
=>
{
// do something...
return
=>
{}
},
[])
}
/ useCallback useMemo 和 useCallback 都可以直接从它们返回的值中推断出它们的类型useCallback 的参数必须制定类型,否则 ts 不会报错,默认指定为 any
同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型const
value
=
10
// 自动推断返回值为 number
const
result
=
React
.
useMemo
=>
value
*
2
,
[
value
])
// 自动推断 => number
const
multiply
=
React
.
useCallback
=>
value
*
multiplier
,
[
multiplier
,
])
自定义 Hooks需要注意,自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,而我们实际需要的是数组里里每一项的具体类型,需要手动添加 const 断言 进行处理:// 也可以显式的指定返回值类型,返回值不一致会报错
const
result
=
React
.
useMemo
<
string
>
=>
2
,
[])
// 类型“ => number”的参数不能赋给类型“ => string”的参数。
const
handleChange
=
React
.
useCallback
<
React
.
ChangeEventHandler
<
HTMLInputElement
>
>
},
[])
如果使用 const 断言遇到问题,也可以直接定义返回类型:function
useLoading
{
const
[
isLoading
,
setState
]
=
React
.
useState
const
load
=
=>
{
setState
return
aPromise
.
then
=>
setState
)
}
// 实际需要: [boolean, typeof load] 类型
// 而不是自动推导的:[]
return
[
isLoading
,
load
]
as
const
}
如果有大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值:export
function
useLoading
:
[
boolean
,
=>
Promise
<
any
>
]
{
const
[
isLoading
,
setState
]
=
React
.
useState
const
load
=
=>
{
setState
return
aPromise
.
then
=>
setState
)
}
return
[
isLoading
,
load
]
}
默认属性 defaultProps大部分文章都不推荐使用 defaultProps , 相关讨论可以**参考链接**推荐方式:使用默认参数值来代替默认属性:function
tuplify
<
T
extends
any
[]
>
{
return
elements
}
function
useLoading
{
const
[
isLoading
,
setState
]
=
React
.
useState
const
load
=
=>
{
setState
return
aPromise
.
then
=>
setState
)
}
// []
return
[
isLoading
,
load
]
}
function
useTupleLoading
{
const
[
isLoading
,
setState
]
=
React
.
useState
const
load
=
=>
{
setState
return
aPromise
.
then
=>
setState
)
}
// [boolean, typeof load]
return
tuplify
}
defaultProps 类型Typescript3.0+ 在默认属性 的类型推导上有了极大的改进,虽然尚且存在一些边界 case 仍然存在问题,不推荐使用,如果有需要使用的场景,可参照如下方式:type
GreetProps
=
{
age
:
number
}
const
Greet
=
=>
{
}
Types or Interfaces在日常的 react 开发中 interface 和 type 的使用场景十分类似implements 与 extends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:type
IProps
=
{
name
:
string
}
const
defaultProps
=
{
age
:
25
,
}
// 类型定义
type
GreetProps
=
IProps
&
typeof
defaultProps
const
Greet
=
=>
<
p
><
/p>
Greet
.
defaultProps
=
defaultProps
// 使用
const
TestComponent
=
=>
{
return
<
h1
/>
}
const
el
=
<
TestComponent
name
=
"foo"
/>
使用 Type 还是 Interface?有几种常用规则:class
Point
{
x
:
number
=
2
y
:
number
=
3
}
interface
IShape
{
area
:
number
}
type
Perimeter
=
{
perimeter
:
number
}
type
RectangleShape
=
&
Point
class
Rectangle
implements
RectangleShape
{
// 类只能实现具有静态已知成员的对象类型或对象类型的交集。
x
=
2
y
=
3
area
{
return
this
.
x
+
this
.
y
}
}
interface
ShapeOrPerimeter
extends
RectangleShape
{}
// 接口只能扩展使用静态已知成员的对象类型或对象类型的交集
- 在定义公共 API 时
=>
alert
}
{...
props
}
/>
)
// 获取返回值类型
function
foo
{
return
{
baz
:
1
}
}
type
FooReturn
=
ReturnType
<
typeof
foo
>
// { baz: number }
Props通常我们使用 type 来定义 Props,为了提高可维护性和代码可读性,在日常的开发过程中我们希望可以添加清晰的注释。现在有这样一个 type
在使用的过程中,hover 对应类型会有如下展示type
OtherProps
=
{
name
:
string
color
:
string
}
增加相对详细的注释,使用时会更清晰,需要注意,注释需要使用 , // 无法被 vscode 识别// type OtherProps = {
// name: string;
// color: string;
// }
const
OtherHeading
:
React
.
FC
<
OtherProps
>
=
=>
常用 Props ts 类型基础属性类型// Great
type
Props
=
{
color
:
string
children
:
React
.
ReactNode
onClick
:
=>
void
}
// type Props
// @param color — color
// @param children — children
// @param onClick — onClick
const
Button
:
React
.
FC
<
Props
>
=
=>
{
return
}
常用 React 属性类型type
AppProps
=
{
message
:
string
count
:
number
disabled
:
boolean
names
:
string
[]
status
:
'waiting'
|
'success'
obj
:
object
obj2
:
{}
obj3
:
{
id
:
string
title
:
string
}
objArr
:
{
id
:
string
title
:
string
}[]
dict1
:
{
[
key
:
string
]
:
MyTypeHere
}
dict2
:
Record
<
string
,
MyTypeHere
>
onSomething
:
Function
onClick
:
=>
void
onChange
:
=>
void
onClick
:
void
optional
:
OptionalType
}
Forms and EventsonChangechange 事件,有两个定义参数类型的方法。第一种方法使用推断的方法签名(例如:React.FormEventexport
declare
interface
AppBetterProps
{
children
:
React
.
ReactNode
// 一般情况下推荐使用,支持所有类型 Great
functionChildren
:
=>
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
:
=>
React
.
ReactNode
// recommended function as a child render prop type
style
:
React
.
CSSProperties
// 传递style对象
onChange
:
React
.
FormEventHandler
<
HTMLInputElement
>
// 表单事件, 泛型参数是event.target的类型
}
:void)
第二种方法强制使用 @types / react 提供的委托类型,两种方法均可。import
*
as
React
from
'react'
type
changeFn
=
=>
void
const
App
:
React
.
FC
=
=>
{
const
[
state
,
setState
]
=
React
.
useState
const
onChange
:
changeFn
=
e
=>
{
setState
}
return
}
onSubmit如果不太关心事件的类型,可以直接使用 React.SyntheticEvent,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展import
*
as
React
from
'react'
const
App
:
React
.
FC
=
=>
{
const
[
state
,
setState
]
=
React
.
useState
const
onChange
:
React
.
ChangeEventHandler
<
HTMLInputElement
>
=
e
=>
{
setState
}
return
}
Operators常用的操作符,常用于类型判断import
*
as
React
from
'react'
const
App
:
React
.
FC
=
=>
{
const
onSubmit
=
=>
{
e
.
preventDefault
const
target
=
e
.
target
as
typeof
e
.
target
&
{
password
:
{
value
:
string
}
}
// 类型扩展
const
password
=
target
.
password
.
value
}
return
}
- typeof and instanceof: 用于类型区分
- keyof: 获取 object 的 key
- O[K]: 属性查找
- [K in O]: 映射类型
- + or - or readonly or : 加法、减法、只读和可选修饰符
- x Y : Z: 用于泛型类型、类型别名、函数参数类型的条件类型
- !: 可空类型的空断言
- as: 类型断言
- is: 函数返回类型的类型保护
现在我们有一个 Counter 组件,需要 name 这个必传参数:
在其他引用它的组件中我们有两种方式获取到 Counter 的参数类型第一种是通过 typeof 操作符(推荐)// counter.tsx
import
*
as
React
from
'react'
export
type
Props
=
{
name
:
string
}
const
Counter
:
React
.
FC
<
Props
>
=
props
=>
{
return
<><
/>
}
export
default
Counter
第二种是通过在原组件进行导出// 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
不要在 type 或 interface 中使用函数声明保持一致性,类型/接口的所有成员都通过相同的语法定义。import
Counter
,
{
Props
}
from
'./d-tips1'
type
PropsNew
=
Props
&
{
age
:
number
}
const
App
:
React
.
FC
<
PropsNew
>
=
props
=>
{
return
}
export
default
App
--strictFunctionTypes 在比较函数类型时强制执行更严格的类型检查,但第一种声明方式下严格检查不生效。
事件处理我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们通过 clientX、clientY 去获取指针的坐标。大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。✅
interface
ICounter
{
start
:
=>
string
}
❌
interface
ICounter1
{
start
:
string
}
interface
Animal
{}
interface
Dog
extends
Animal
{
wow
:
=>
void
}
interface
Comparer
<
T
>
{
compare
:
=>
number
}
declare
let
animalComparer
:
Comparer
<
Animal
>
declare
let
dogComparer
:
Comparer
<
Dog
>
animalComparer
=
dogComparer
// Error
dogComparer
=
animalComparer
// Ok
interface
Comparer1
<
T
>
{
compare
:
number
}
declare
let
animalComparer1
:
Comparer1
<
Animal
>
declare
let
dogComparer1
:
Comparer1
<
Dog
>
animalComparer1
=
dogComparer
// Ok
dogComparer1
=
animalComparer
// Ok
试想下当我们注册一个 Touch 事件,然后错误的通过事件处理函数中的 event 对象去获取其 clientY 属性的值,在这里我们已经将 event 设置为 any 类型,导致 Typescript 在编译时并不会提示我们错误, 当我们通过 event.clientY 访问时就有问题了,因为 Touch 事件的 event 对象并没有 clientY 这个属性。通过 interface 对 event 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。function
handleEvent
{
console
.
log
}
Event 事件对象类型- ClipboardEvent
剪切板事件对象 - DragEvent
拖拽事件对象 - ChangeEvent
Change 事件对象 - KeyboardEvent
键盘事件对象 - MouseEvent
鼠标事件对象 - TouchEvent
触摸事件对象 - WheelEvent
滚轮时间对象 - AnimationEvent
动画事件对象 - TransitionEvent
过渡事件对象
bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 voidtype
EventHandler
<
E
extends
React
.
SyntheticEvent
<
any
>>
=
{
bivarianceHack
:
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 而不是: void,这与 strictfunctionTypes 选项下的功能兼容性有关。
: void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。
Promise 类型在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promiseclass
Animal
{
private
x
:
undefined
}
class
Dog
extends
Animal
{
private
d
:
undefined
}
type
EventHandler
<
E
extends
Animal
>
=
=>
void
let
z
:
EventHandler
<
Animal
>
=
=>
{}
// fails under strictFunctionTyes
type
BivariantEventHandler
<
E
extends
Animal
>
=
{
bivarianceHack
:
void
}[
'bivarianceHack'
]
let
y
:
BivariantEventHandler
<
Animal
>
=
=>
{}
是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。
首先声明 IResponse 的泛型接口用于定义 response 的类型,通过 T 泛型变量来确定 result 的类型。然后声明了一个 异步函数 getResponse 并且将函数返回值的类型定义为 Promisetype
IResponse
<
T
>
=
{
message
:
string
result
:
T
success
:
boolean
}
async
function
getResponse
:
Promise
<
IResponse
<
number
[]
>>
{
return
{
message
:
'获取成功'
,
result
:
[
1
,
2
,
3
],
success
:
true
,
}
}
getResponse
.
then
})
> 。最后调用 getResponse 方法会返回一个 promise 类型,通过 then 调用,此时 then 方法接收的第一个回调函数的参数 response 的类型为,{ message: string, result: number[], success: boolean} 。泛型参数的组件下面这个组件的 name 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。
如果需要外部传入参数类型,只需 ->const
TestB
=
=>
{
return
}
什么时候使用泛型当你的函数,接口或者类:type
Props
<
T
>
=
{
name
:
T
name2
:
T
}
const
TestC
:
<
T
>
=>
React
.
ReactElement
=
=>
{
return
}
const
TestD
=
=>
{
return
}
- 需要作用到很多类型的时候,举个
由于其可以接受任意值,也就是说我们的函数的入参和返回值都应该可以是任意类型,如果不使用泛型,我们只能重复的进行定义const
id
=
arg
=>
arg
如果使用泛型,我们只需要type
idBoolean
=
=>
boolean
type
idNumber
=
=>
number
type
idString
=
=>
string
// ...
function
id
<
T
>
:
T
{
return
arg
}
// 或
const
id1
:
<
T
>
=>
T
=
arg
=>
{
return
arg
}
- 需要被用到很多地方的时候,比如常用的工具泛型 Partial。
如果需要深 Partial 我们可以通过泛型递归来实现type
Partial
<
T
>
=
{
[
P
in
keyof
T
]
:
T
[
P
]
}
字节跳动懂车帝团队招聘我们是字节跳动旗下懂车帝产品线,目前业务上正处于高速发展阶段,懂车帝自 2017 年 8 月正式诞生,仅三年时间已经是汽车互联网行业第二。现在前端团队主流的技术栈是 React、Typescript,主要负责懂车帝 App、M 站、PC 站、懂车帝小程序产品矩阵、商业化海量业务,商业数据产品等。type
DeepPartial
<
T
>
=
T
extends
Function
T
:
T
extends
object
{
[
P
in
keyof
T
]
:
DeepPartial
<
T
[
P
]
>
}
:
T
type
PartialedWindow
=
DeepPartial
<
Window
>
我们在类客户端、多宿主、技术建站、中后台系统、全栈、富交互等多种应用场景都有大量技术实践,致力于技术驱动业务发展,探索所有可能性。加入懂车帝,一起打造汽车领域最专业最开放的前端团队!简历直达:dcar_fe@bytedance.com邮件标题:应聘+城市+岗位名称关注「 字节前端 ByteFE 」,了解更多信息。