类型安全是咱们在运用 TypeScript 时最大的优势,但是在 React 项目中,咱们怎么正确运用 TypeScript 呢,也许本文能给你带来一些帮助。
标注 React 组件的类型
假设咱们有这样一个 Card
组件,其包含了 title
特点用于显现标题 及 onClick
点击事情:
interface CardProps {
className?: string
children: React.ReactNode
title?: string
onClick?: (e: React.MouseEvent) => void
}
function Card(props: CardProps) {
return (
<div
className="rounder-xl"
onClick={props.onClick}
>
<span>{props.title}</span>
{props.children}
</div>
)
}
怎么传递 className
通常咱们会为组件增加 className
,在 React 中咱们需求凭借一些第三方库如 classnames
或 clsx
来处理:
function Card(props: CardProps) {
const { className, children, ...rest } = props
return (
<div className={clsx('rounder-xl', className)}>
<span>{rest.title}</span>
{children}
</div>
)
}
怎么承继 HTML 原生标签的特点
咱们能够运用 React.HTMLAttributes
来承继 HTML 原生标签的特点:
// Bad ❌
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
更加推荐的方法是运用 React.ComponentProps
来承继 HTML 原生标签的特点:
// Good ✅
interface CardProps extends React.ComponentProps<'div'> {}
再具体一点,咱们能够一起指定是否暴露 ref
特点给外部:
interface CardProps extends React.ComponentPropsWithRef<'div'> {}
// Perfect ✅
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {}
这样咱们就能够让 CardProps
具有一切 div
标签的特点,一起不暴露 ref
特点给外部。
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
title?: string
}
function Card(props: CardProps) {
const { className, children, title, ...rest } = props
return (
<div
className={clsx('rounder-xl', className)}
{...rest}
>
<span>{title}</span>
{children}
</div>
)
}
CardProps
承继了 div
的特点后,经过 {...rest}
传参,咱们省去了手动绑定 onClick
事情。
注意:上面将 className
单独解构,是为了避免传入 className
覆盖了组件内部的 className
。
范型组件
动态指定标签类型
假如咱们需求动态指定 Card
组件最外层的标签类型:
type CardProps<T extends React.ElementType> =
React.ComponentPropsWithoutRef<T> &
React.PropsWithChildren<{
tag?: T
title?: string
}>
function Card<T extends React.ElementType = 'div'>(props: CardProps<T>) {
const { tag: Component = 'div', children, title, ...rest } = props
return (
<Component {...rest}>
<span>{title}</span>
{children}
</Component>
)
}
这样咱们能够经过 tag
特点操控 Card
组件最外层的标签类型,且能够经过范型来得到对应标签的类型提示。
此时默认 Card
最外层的标签为 div
,咱们仅能够传递 div
的特点,假如将 tag
特点指定为 button
,则能够传递 button
的特点,如 disabled
,这个特点是不在 div
上的。
在 .tsx 文件中为箭头函数增加范型
在 .tsx
文件中为箭头函数增加范型时,假如仅有 T,需求在后面增加 ,
避免范型被识别为 JSX 语法:
这样编译器无法识别:
const Card = <T>() => {}
这样就不会报错了:
const Card = <T,>() => {}
完成能够动态烘托的列表组件
interface CardListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
}
function CardList<T>(props: CardListProps<T>) {
return (
<div className="flex flex-col space-y-4">
{props.items.map(props.renderItem)}
</div>
)
}
上述代码界说了一个 CardList
组件,其承受一个 items
数组和一个 renderItem
函数,用于烘托列表中的每一项。
依据咱们的数据类型进行烘托:
interface Framework {
id: number
name: string
}
const data = [
{ id: 1, name: 'React' },
{ id: 2, name: 'Vue' },
{ id: 3, name: 'Angular' }
]
const App = () => {
return (
<CardList<Framework>
items={data}
renderItem={(framework) => {
return (
<div>
<span>{framework.id}</span>
<span>{framework.name}</span>
</div>
)
}}
/>
)
}
上述代码咱们能够经过传入 Framework
类型来指定 CardList
组件的数据类型,renderItem
函数也会依据 Framework
类型拿到对应的参数类型。
这是一个常见的 React 进阶技术,叫做 Render Props,这方便了咱们在父组件中进行自界说烘托,一起也完成了组件的关注点分离。在 React Native 中,FlatList
组件便是采用了这样的方法进行了封装。
类型缩窄
假如需求依据特点条件烘托组件,咱们能够基于 TypeScript 的类型缩窄来完成:
interface ButtonProps {
text?: string
}
interface LinkProps {
href?: string
text?: string
}
function isLinkProps(props: ButtonProps | LinkProps): props is LinkProps {
return 'href' in props
}
function Clickable(props: ButtonProps | LinkProps) {
if (isLinkProps(props)) {
// 此处的 Props 类型为 LinkProps
return <a href={props.href}>{props.text}</a>
} else {
// 此处的 Props 类型为 ButtonProps
return <button>{props.text}</button>
}
}
上述代码能够依据是否传入 href
特点来烘托 a
标签或 button
标签。
事情处理
咱们能够经过 React.MouseEventHandler
和 React.ChangeEventHandler
来界说事情处理的类型:
interface DemoProps {
onClick: React.MouseEventHandler<HTMLButtonElement>
onChange: React.ChangeEventHandler<HTMLInputElement>
}
// Good ✅
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
console.log(e.currentTarget)
}
// Good ✅
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
console.log(e.currentTarget.value)
}
// Bad ❌
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget)
}
// Bad ❌
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.currentTarget.value)
}
function Demo(props: DemoProps) {
return (
<div>
<button onClick={props.onClick}>Click</button>
<input onChange={props.onChange} />
</div>
)
}
运用 React.MouseEvent
和 React.ChangeEvent
并不推荐,它们仅仅只能限定事情的参数类型。