Migration: Select to hooks component

This commit is contained in:
Dreamacro 2019-07-03 13:49:41 +08:00
parent 724cd44d69
commit a92ee4862f

View File

@ -1,4 +1,4 @@
import * as React from 'react'
import React, { useRef, useLayoutEffect, useState, useMemo } from 'react'
import classnames from 'classnames'
import { Icon } from '@components'
import { BaseComponentProps } from '@models'
@ -17,81 +17,59 @@ interface SelectProps extends BaseComponentProps {
onSelect?: (value: OptionValue, e: React.MouseEvent<HTMLLIElement>) => void
}
interface SelectState {
dropdownListStyles: React.CSSProperties
showDropDownList: boolean,
hasCreateDropList: boolean
}
export function Select (props: SelectProps) {
const { value, onSelect, children, className: cn, style } = props
export class Select extends React.Component<SelectProps, SelectState> {
const portalRef = useRef<HTMLDivElement>()
const attachmentRef = useRef<HTMLDivElement>()
const targetRef = useRef<HTMLDivElement>()
// portal container
$container: Element
// drop down list
$attachment = React.createRef<HTMLDivElement>()
// target position element
$target = React.createRef<HTMLDivElement>()
state = {
dropdownListStyles: {},
showDropDownList: false,
hasCreateDropList: false
}
componentDidMount () {
document.addEventListener('click', this.handleGlobalClick, true)
this.setState({ dropdownListStyles: this.calculateAttachmentPosition() })
}
componentWillUnmount () {
if (this.state.hasCreateDropList) {
document.body.removeChild(this.$container)
useLayoutEffect(() => {
document.addEventListener('click', handleGlobalClick, true)
return () => {
document.addEventListener('click', handleGlobalClick, true)
if (portalRef.current) {
document.body.removeChild(portalRef.current)
}
}
document.removeEventListener('click', this.handleGlobalClick, true)
}
}, [])
shouldComponentUpdate (nextProps, nextState) {
if (nextProps.value === this.props.value && nextState.showDropDownList === this.state.showDropDownList) {
return false
const [showDropDownList, setShowDropDownList] = useState(false)
const [hasCreateDropList, setHasCreateDropList] = useState(false)
const dropdownListStyles = useMemo(() => {
if (targetRef.current) {
const targetRectInfo = targetRef.current.getBoundingClientRect()
return {
top: Math.floor(targetRectInfo.top) - 10,
left: Math.floor(targetRectInfo.left) - 10
}
}
return true
}
return {}
}, [])
handleShowDropList = () => {
if (!this.state.hasCreateDropList) {
// create container element
const container = document.createElement('div')
document.body.appendChild(container)
this.$container = container
this.setState({
hasCreateDropList: true
})
}
this.setState({
showDropDownList: true
})
}
private handleGlobalClick = (e) => {
const el = this.$attachment.current
function handleGlobalClick (e) {
const el = attachmentRef.current
if (el && !el.contains(e.target)) {
this.setState({ showDropDownList: false })
setShowDropDownList(false)
}
}
private calculateAttachmentPosition () {
const targetRectInfo = this.$target.current.getBoundingClientRect()
return {
top: Math.floor(targetRectInfo.top) - 10,
left: Math.floor(targetRectInfo.left) - 10
function handleShowDropList (e) {
if (!hasCreateDropList) {
if (!portalRef.current) {
// create container element
const container = document.createElement('div')
document.body.appendChild(container)
portalRef.current = container
}
setHasCreateDropList(true)
}
setShowDropDownList(true)
}
private getSelectedOption = (value: OptionValue, children: React.ReactNode) => {
const matchChild = useMemo(() => {
let matchChild: React.ReactElement<any> = null
React.Children.forEach(children, (child: React.ReactElement<any>) => {
@ -101,13 +79,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
})
return matchChild
}
}, [value, children])
private hookChildren = (
children: React.ReactNode,
value: OptionValue,
onSelect: SelectProps['onSelect']
) => {
const hookedChildren = useMemo(() => {
return React.Children.map(children, (child: React.ReactElement<any>) => {
if (!child.props || !child.type) {
return child
@ -123,43 +97,40 @@ export class Select extends React.Component<SelectProps, SelectState> {
return React.cloneElement(child, Object.assign({}, child.props, {
onClick: (e: React.MouseEvent<HTMLLIElement>) => {
onSelect(child.props.value, e)
this.setState({ showDropDownList: false })
setShowDropDownList(false)
rawOnClickEvent && rawOnClickEvent(e)
},
className
}))
})
}
}, [children, value, onSelect])
render () {
const { value, onSelect, children, className: cn, style } = this.props
const { dropdownListStyles, showDropDownList, hasCreateDropList } = this.state
const matchChild = this.getSelectedOption(value, children)
const dropDownList = (
<div
className={classnames('select-list', { 'select-list-show': showDropDownList })}
ref={this.$attachment}
style={dropdownListStyles}
>
<ul className="list">
{this.hookChildren(children, value, onSelect)}
</ul>
</div>
)
const dropDownList = (
<div
className={classnames('select-list', { 'select-list-show': showDropDownList })}
ref={attachmentRef}
style={dropdownListStyles}
>
<ul className="list">
{ hookedChildren }
</ul>
</div>
)
return <>
return (
<>
<div
className={classnames('select', cn)}
style={style}
ref={this.$target}
onClick={this.handleShowDropList}
ref={targetRef}
onClick={handleShowDropList}
>
{matchChild && matchChild.props && matchChild.props.children}
<Icon type="triangle-down" />
</div>
{hasCreateDropList && createPortal(dropDownList, this.$container)}
{hasCreateDropList && createPortal(dropDownList, portalRef.current)}
</>
}
)
}
interface OptionProps extends BaseComponentProps {
@ -169,13 +140,11 @@ interface OptionProps extends BaseComponentProps {
onClick?: (e: React.MouseEvent<HTMLLIElement>) => void
}
export class Option extends React.Component<OptionProps, {}> {
render () {
const { className: cn, style, key, disabled = false, children, onClick = () => {} } = this.props
const className = classnames('option', { disabled }, cn)
export const Option: React.SFC<OptionProps> = props => {
const { className: cn, style, key, disabled = false, children, onClick = () => {} } = props
const className = classnames('option', { disabled }, cn)
return (
<li className={className} style={style} key={key} onClick={onClick}>{children}</li>
)
}
return (
<li className={className} style={style} key={key} onClick={onClick}>{children}</li>
)
}