在本文中,我们将讨论如何优雅地包装第三方 React 组件,并介绍一个具体实例:如何包装 react-photo-album
中导出的 <PhotoAlbum />
组件。
我们有两个需求
- 给
<PhotoAlbum />
组件的某些属性设置预设值,但不影响其他属性。 - 为
<PhotoAlbum />
组件添加React.memo()
层。
需求一:设置预设值
首先,我们想给 <PhotoAlbum />
组件的 layout
、columns
和 spacing
属性增加预设值,但又不希望影响其他的 props
属性。
最初的尝试如下:
import { memo } from 'react'
import PhotoAlbum from 'react-photo-album'
import type { Photo, PhotoAlbumProps } from 'react-photo-album'
const IDEAL_COLUMN_WIDTH = 150
function CustomPhotoAlbum<T extends Photo>(props: PhotoAlbumProps<T>) {
const {
layout = 'masonry',
columns = (containerWidth: number) => Math.floor(containerWidth / IDEAL_COLUMN_WIDTH),
spacing = 16,
...restProps
} = props
return <PhotoAlbum layout={layout} columns={columns} spacing={spacing} {...restProps} />
}
然而,这样的代码在使用时会报错,提示 layout
属性缺失。这是因为 layout
属性在 PhotoAlbumProps<T>
中是 required
的。尽管我们为其增加了预设值,但由于使用了原有的类型声明,所以在使用时仍然要求 layout
属性。
为了解决这个问题,我们需要将 <CustomPhotoAlbum />
组件的 layout
属性变为可选的,同时不影响其他属性的类型。
这里我们可以使用类型编程的方法来实现这个目标。
import { memo } from 'react'
import PhotoAlbum from 'react-photo-album'
import type { Photo, PhotoAlbumProps } from 'react-photo-album'
const IDEAL_COLUMN_WIDTH = 150
+ type PartialSubset<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
- function CustomPhotoAlbum<T extends Photo>(props: PhotoAlbumProps<T>) {
+ function CustomPhotoAlbum<T extends Photo>(props: PartialSubset<PhotoAlbumProps<T>, 'layout'>) {
const {
layout = 'masonry',
columns = (containerWidth: number) => Math.floor(containerWidth / IDEAL_COLUMN_WIDTH),
spacing = 16,
...restProps
} = props
return <PhotoAlbum layout={layout} columns={columns} spacing={spacing} {...restProps} />
}
在上述代码中,我们定义了一个泛型函数类型 PartialSubset<T, K extends keyof T>
,它接受一个类型 T
和一个属性键集合 K
作为参数,并返回一个新的类型。这个新类型是将类型 T
中指定属性 K
变为可选的,并保留其他属性的类型构成的。通过使用这个类型,我们将 <CustomPhotoAlbum />
组件的 layout
属性变为可选的,而其他属性不受任何影响。
现在使用 <CustomPhotoAlbum photos={photos} onClick={onClick} />
时,不会再提示 layout
属性缺失的问题。
需求二:添加 React.memo()
第二个需求是给 <PhotoAlbum />
组件添加 React.memo()
层,以提高性能。
在解决这个需求时,我们面临了一个比较棘手的问题。React.memo()
会影响组件的泛型,如果直接使用 React.memo()
进行包裹,可能会导致组件在使用时出现泛型相关的问题。
通过参考 issuecomment-65659662,我们找到了一个合适的方式来处理 React.memo()
对泛型的影响,即使用类型断言来处理,具体做法如下:
const MemoizedComponent = React.memo(InnerComponent) as typeof InnerComponent
综上所述,最终的代码如下:
import { memo } from 'react'
import PhotoAlbum from 'react-photo-album'
import type { Photo, PhotoAlbumProps } from 'react-photo-album'
const IDEAL_COLUMN_WIDTH = 150
type PartialSubset<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
function CustomPhotoAlbum<T extends Photo>(props: PartialSubset<PhotoAlbumProps<T>, 'layout'>) {
const {
layout = 'masonry',
columns = (containerWidth: number) => Math.floor(containerWidth / IDEAL_COLUMN_WIDTH),
spacing = 16,
...restProps
} = props
return <PhotoAlbum layout={layout} columns={columns} spacing={spacing} {...restProps} />
}
+ export default memo(CustomPhotoAlbum) as typeof CustomPhotoAlbum
在上述代码中,我们成功地给 <CustomPhotoAlbum />
组件添加了 React.memo()
层,并使用类型断言来处理泛型问题,保证了组件在使用时不会出现泛型相关的错误。