Skip to content

Commit

Permalink
feat(react): 新增 defineComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jul 20, 2020
1 parent 63fb480 commit a9c95d7
Show file tree
Hide file tree
Showing 6 changed files with 920 additions and 357 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"execa": "4.0.3",
"fs-extra": "9.0.1",
"globby": "11.0.1",
"haoma": "2.2.0",
"haoma": "2.3.0",
"husky": "4.2.5",
"jest": "26.1.0",
"lint-staged": "10.2.11",
Expand Down
1,116 changes: 760 additions & 356 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions src/react/defineComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import { defineComponent } from './defineComponent'
import { render } from '@testing-library/react'

describe('defineComponent', () => {
test('默认转发 ref', () => {
const Button = defineComponent<
{ id?: number; kind: string },
HTMLDivElement
>({
defaultProps: {
id: 0,
},
component: function Button(props, ref) {
return <div ref={ref} data-testid='button' />
},
})
const buttonRef = React.createRef<HTMLDivElement>()
const { queryByTestId } = render(<Button ref={buttonRef} kind='primary' />)
expect(queryByTestId('button')).toBe(buttonRef.current)
})

test('不转发 ref', () => {
const Button = defineComponent<
{ id?: number; kind: string },
HTMLDivElement
>({
defaultProps: {
id: 0,
},
forwardRef: false,
component: function Button(props) {
return <div data-testid='button2'>{props.kind}</div>
},
})
const { queryByTestId } = render(<Button kind='primary' />)
expect(queryByTestId('button2')!.innerHTML).toBe('primary')
})

test('组件的展示名称默认为组件函数的名称', () => {
const Button = defineComponent({
component: function Button() {
return null
},
})
expect(Button.displayName).toBe('Button')
})

test('可以通过选项 displayName 设置组件的展示名称', () => {
const Button = defineComponent({
displayName: 'XButton',
component: function Button() {
return null
},
})
expect(Button.displayName).toBe('XButton')
})
})
95 changes: 95 additions & 0 deletions src/react/defineComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react'
import { Defined, OptionalKeys, RequiredBy } from '../types'

/**
* 定义组件的选项。
*/
export type DefineComponentOptions<
/**
* 组件属性。
*/
TProps extends Record<string, any>,
/**
* 是否转发 ref。
*/
TForwardRef extends boolean,
/**
* 要转发的 ref。
*/
TRef extends any = never
> = ([OptionalKeys<TProps>] extends [never]
? {
/**
* 可选属性的默认值。
*/
defaultProps?: never
}
: {
/**
* 可选属性的默认值。
*/
defaultProps: {
[K in OptionalKeys<TProps>]: Defined<TProps[K]>
}
}) &
(TForwardRef extends true
? {
/**
* 是否转发 ref。
*/
forwardRef?: true
}
: {
/**
* 是否转发 ref。
*/
forwardRef: false
}) & {
/**
* 组件展示名称。
*/
displayName?: string
/**
* 组件。
*/
component: TForwardRef extends true
? React.ForwardRefRenderFunction<TRef, RequiredBy<TProps, keyof TProps>>
: React.FC<RequiredBy<TProps, keyof TProps>>
}

/**
* 定义组件。
*
* @param options 选项
*/
export function defineComponent<
TProps extends Record<string, any>,
TRef extends any = any
>(
options: DefineComponentOptions<TProps, true, TRef>,
): React.ForwardRefExoticComponent<
React.PropsWithoutRef<TProps> & React.RefAttributes<TRef>
>

/**
* 定义组件。
*
* @param options 选项
*/
export function defineComponent<
TProps extends Record<string, any>,
TRef extends any = any
>(options: DefineComponentOptions<TProps, false>): React.FC<TProps>

export function defineComponent(
options: DefineComponentOptions<any, any, any>,
): any {
const forwardRef = options.forwardRef ?? true
const displayName = options.displayName ?? options.component.name
const component = forwardRef
? React.forwardRef(options.component as any)
: options.component
component.displayName = displayName
component.defaultProps = options.defaultProps
return component
}
1 change: 1 addition & 0 deletions src/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export * from 'react-use'

// @index(['./**/*.ts', '!./**/*.{test,taro}.*', '!./{useToggle,createGlobalState,useTitle,useInterval,useSearchParam,useLocalStorage,useWindowSize}.*'], f => `export * from '${f.path}'`)
export * from './defineComponent'
export * from './useClassName'
export * from './useLoadMore'
export * from './useReachBottom'
Expand Down
5 changes: 5 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type { LiteralUnion, AsyncReturnType, FixedLengthArray } from 'type-fest'
export type {
AnyArray,
ValueOf,
ElementOf,
AsyncOrSync,
Buildable,
Writable,
Expand All @@ -29,6 +30,10 @@ export type {
DeepWritable as WritableDeep,
DeepNullable as NullableDeep,
DeepNonNullable as NonNullableDeep,
ReadonlyKeys,
WritableKeys,
OptionalKeys,
RequiredKeys,
} from 'ts-essentials'

// @index(['./**/*.ts', '!./**/*.test.*'], f => `export * from '${f.path}'`)
Expand Down

0 comments on commit a9c95d7

Please sign in to comment.