在 React + TypeScript 开发中,有时我们需要使用组件库中某些类型,但组件库并未导出这些类型。最近我就遇到一个这样的场景 - 在XTaro的开发中,需要获取某个组件 onInput 事件对应的 event 类型,但组件库没有导出这个类型。这促使我深入研究了 React.ComponentProps 这个实用的工具类型,在此分享给大家。
从实际问题说起
假设我们在使用一个第三方组件库中的 Input 组件:
import { Input } from 'some-ui-library'
// 我们需要获取 onInput 事件的类型
const MyComponent = () => {
// 这里不确定 event 的具体类型
const handleInput = (event: unknown) => {
console.log(event.target.value) // TypeScript 报错:Object is of type 'unknown'
}
return <Input onInput={handleInput} />
}
这时 React.ComponentProps 就派上用场了:
import { ComponentProps } from 'react'
import { Input } from 'some-ui-library'
// 提取 Input 组件的 onInput 属性类型
type InputEvent = ComponentProps<typeof Input>['onInput'] extends
(event: infer E) => void ? E : never
const MyComponent = () => {
// 现在 event 有了正确的类型
const handleInput = (event: InputEvent) => {
console.log(event.target.value) // TypeScript 不再报错
}
return <Input onInput={handleInput} />
}
ComponentProps 的强大之处
1. 提取原生 HTML 元素的属性类型
type DivProps = ComponentProps<'div'>
type ButtonProps = ComponentProps<'button'>
// 创建带有原生属性的自定义组件
const CustomButton = (props: ComponentProps<'button'> & { theme: 'light' | 'dark' }) => {
const { theme, ...buttonProps } = props
return (
<button
{...buttonProps}
className={`${props.className || ''} theme-${theme}`}
/>
)
}
2. 提取第三方组件的属性类型
import { Table } from 'antd'
// 提取表格列的配置类型
type ColumnConfig = ComponentProps<typeof Table>['columns'][number]
// 提取分页配置的类型
type PaginationConfig = NonNullable<ComponentProps<typeof Table>['pagination']>
const MyTable = () => {
const columns: ColumnConfig[] = [
{
title: '姓名',
dataIndex: 'name',
sorter: true
}
]
const pagination: PaginationConfig = {
current: 1,
pageSize: 10,
total: 100
}
return <Table columns={columns} pagination={pagination} />
}
3. 创建类型安全的高阶组件
import { ComponentProps, ComponentType } from 'react'
// 创建一个添加主题功能的高阶组件
function withTheme<T extends ComponentProps<any>>(
WrappedComponent: ComponentType<T>
) {
return (props: T & { theme?: 'light' | 'dark' }) => {
const { theme = 'light', ...componentProps } = props
return (
<div className={`theme-${theme}`}>
<WrappedComponent {...(componentProps as T)} />
</div>
)
}
}
// 使用高阶组件
const ThemedButton = withTheme(CustomButton)
4. 提取事件处理器的参数类型
import { Select } from 'some-ui-library'
// 提取 onChange 事件处理器的参数类型
type SelectChangeHandler = ComponentProps<typeof Select>['onChange']
type SelectValue = Parameters<NonNullable<SelectChangeHandler>>[0]
const MySelect = () => {
const handleChange = (value: SelectValue) => {
console.log('Selected:', value)
}
return <Select onChange={handleChange} />
}
5. 组合多个组件的属性类型
import { Input, Select } from 'some-ui-library'
// 组合 Input 和 Select 的某些属性
type ComboProps = Pick<ComponentProps<typeof Input>, 'placeholder' | 'disabled'> &
Pick<ComponentProps<typeof Select>, 'options'>
const ComboBox = (props: ComboProps) => {
const { placeholder, disabled, options } = props
return (
<div className="combo-box">
<Input placeholder={placeholder} disabled={disabled} />
<Select options={options} disabled={disabled} />
</div>
)
}
最佳实践和注意事项
- 使用
NonNullable处理可能为空的属性:
type DefiniteHandler = NonNullable<ComponentProps<typeof Input>['onChange']>
- 使用
Pick和Omit选择性地提取或排除属性:
type PickedProps = Pick<ComponentProps<typeof Component>, 'onClick' | 'className'>
type OmittedProps = Omit<ComponentProps<typeof Component>, 'ref'>
- 处理泛型组件时注意类型参数:
type TableProps<T> = ComponentProps<typeof Table<T>>
- 提取嵌套属性类型时使用类型索引:
type OptionType = ComponentProps<typeof Select>['options'][number]
总结
React.ComponentProps 是一个强大的工具类型,它可以帮助我们:
- 提取未导出的组件属性类型
- 复用现有组件的类型定义
- 创建类型安全的组件封装
- 处理事件处理器的类型
- 组合多个组件的属性类型
在实际开发中,合理使用 ComponentProps 可以大大提高代码的类型安全性和开发效率。特别是在使用第三方组件库时,它能帮助我们获取那些未导出的类型定义,是一个不可或缺的工具。
(小声逼逼:要不是它,我可能又要忍不住开启AnyScript大法了)