Skip to content

Commit

Permalink
feat(react): 新增 useControllableValue
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jul 20, 2020
1 parent a1c30c1 commit da57d15
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './ExtendComponentProps'
export * from './isVisibleValue'
export * from './renderComponent'
export * from './useClassName'
export * from './useControllableValue'
export * from './useLoadMore'
export * from './useReachBottom'
export * from './useScrollLoadMore'
Expand Down
36 changes: 36 additions & 0 deletions src/react/useControllableValue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { act, renderHook } from '@testing-library/react-hooks'
import { useControllableValue } from './useControllableValue'
import { useSetState } from 'react-use'

describe('useControllableValue', () => {
test('表现正常', () => {
const { result } = renderHook(() => {
const [props, updateProps] = useSetState<
Partial<{
value: string
defaultValue: string
onChange: (value: string) => any
}>
>({
defaultValue: '0',
onChange: value => {
updateProps({ value })
},
})
const [value, setValue] = useControllableValue(
props,
'defaultValue',
'value',
'onChange',
)
return { props, value, setValue, updateProps }
})
expect(result.current.value).toBe('0')
act(() => result.current.updateProps({ defaultValue: '0.1' }))
expect(result.current.value).toBe('0')
act(() => result.current.setValue('1'))
expect(result.current.props.value).toBe(result.current.value).toBe('1')
act(() => result.current.updateProps({ value: '2' }))
expect(result.current.props.value).toBe(result.current.value).toBe('2')
})
})
58 changes: 58 additions & 0 deletions src/react/useControllableValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Defined } from '../types'
import { useCallback, useState } from 'react'
import { useUpdateEffect } from 'react-use'

export type UseControllableValueResult<
TProps,
TValuePropName extends keyof TProps,
TCallbackPropName extends keyof TProps
> = [TProps[TValuePropName], Defined<TProps[TCallbackPropName]>]

/**
* 受控值。
*
* @param props 组件的属性
* @param defaultValuePropName 默认值的属性名
* @param valuePropName 值的属性名
* @param callbackPropName 值改变时的回调函数的属性名
*/
export function useControllableValue<
TProps,
TDefaultValuePropName extends keyof TProps,
TValuePropName extends keyof TProps,
TCallbackPropName extends keyof TProps
>(
props: TProps,
defaultValuePropName: TDefaultValuePropName,
valuePropName: TValuePropName,
callbackPropName: TCallbackPropName,
): UseControllableValueResult<TProps, TValuePropName, TCallbackPropName> {
const [value, setValue] = useState(() => {
if (valuePropName in props) {
return props[valuePropName]
}
if (defaultValuePropName in props) {
return props[defaultValuePropName]
}
})

useUpdateEffect(() => {
if (valuePropName in props) {
setValue(props[valuePropName])
}
}, [props[valuePropName]])

const handleSetValue = useCallback(
(nextValue: typeof value) => {
if (!(valuePropName in props)) {
setValue(nextValue)
}
if (typeof props[callbackPropName] === 'function') {
;(props[callbackPropName] as any)(nextValue)
}
},
[props, valuePropName, callbackPropName],
)

return [value, handleSetValue] as any
}

0 comments on commit da57d15

Please sign in to comment.