我正在尝试在弹出式菜单被召唤和关闭时将其移入和移出视图。
最初表示为null
的点击,将隐藏的组件安装起来,然后触发CSS中定义的过渡,以将弹出窗口过渡到视图中。
在安装时,该组件会在文档上注册一个单击处理程序,并在弹出窗口之外监听单击,以便首先将其移出视图,然后将其完全卸载,并同时删除事件侦听器。
转换是通过更改组件的style
属性来触发的,但是我也尝试过使用className
来产生完全相同的结果。
import { useRef, useState, useEffect } from 'react'
/*
* Popup
*
* - [x] auto-dismiss on click outside without hogging the click event
* (i.e. without using `stopPropagation()`)
* - [ ] transition into and out of view
* ! No transition when opening a popup while another is still transitionning out out view
*/
function Popup ({ dismiss }) {
const popupRef = useRef(null)
const [style, setStyle] = useState(hiddenStyles)
useEffect(() => {
setStyle(showingStyles)
}, [])
useEffect(() => {
global.document.addEventListener('click', onClickOutside, false)
return () => {
global.document.removeEventListener('click', onClickOutside, false)
}
}, [])
function onClickOutside (event) {
if (!popupRef.current.contains(event.target)) {
setStyle(hiddenStyles)
setTimeout(dismiss, 900) // TODO Find better way to dismiss (unmount) popup on animation end (and move this responsibility to the Item?)
}
}
return (
<div
className='popup'
ref={popupRef}
style={style}
>
<style jsx>{`
.popup {
z-index: 1;
color: black;
background: white;
border: 1px solid black;
position: absolute;
transition: opacity 0.9s ease;
}
`}</style>
<pre>{JSON.stringify(style, null, 2)}</pre>
</div>
)
}
/*
* Popup-producing item
*
* - [x] only render popup when wanted, unmount when dismissed
*/
const hiddenStyles = { opacity: 0 }
const showingStyles = { opacity: 1 }
function Item ({ id, body }) {
const [showActions, setShowActions] = useState(false)
function openActions () {
setShowActions(true)
}
function hideActions () {
setShowActions(false)
}
return (
<li className='row' onClick={openActions}>
<style jsx>{`
.row {
position: relative;
cursor: pointer;
padding: 5px;
}
.row:hover {
background: #d636e9;
color: #ffe2f0;
}
`}</style>
{body}
{showActions
? (
<Popup dismiss={hideActions} />
) : null}
</li>
)
}
当我分别打开弹出窗口时,花时间在打开下一个窗口之前先将其关闭,然后进行过渡。但是,如果我在另一个弹出窗口完全消失之前打开一个弹出窗口,则过渡会从一开始就停留在最终状态。
问题是为什么?
答案 0 :(得分:0)
stackoverflow片段工具不支持React 16.8,因此我将代码从钩子重构为类,并偶然发现了一个解决方案,这在钩子世界中也适用:替换
useEffect(() => {
setStyle(showingStyles)
}, [])
与
useEffect(() => {
setTimeout(() => {
setStyle(showingStyles)
}, 10)
}, [])
我无法使用钩子显示完整的功能代码,但此处使用类显示工作版本:
/*
* Popup
*
* - [x] auto-dismiss on click outside without hogging the click event
* (i.e. without using `stopPropagation()`)
* - [ ] transition into and out of view
* ! No transition when opening a popup while another is still transitionning out out view
*/
class Popup extends React.Component {
constructor (props) {
super(props)
this.onClickOutside = this.onClickOutside.bind(this)
this.popupRef = null
this.state = {
style: hiddenStyles
}
}
componentDidMount () {
setTimeout(() => {
this.setState({ style: showingStyles })
}, 10)
document.addEventListener('click', this.onClickOutside, false)
}
componentWillUnmount () {
document.removeEventListener('click', this.onClickOutside, false)
}
onClickOutside (event) {
if (!this.popupRef.contains(event.target)) {
this.setState({ style: hiddenStyles })
setTimeout(this.props.dismiss, 900) // TODO Find better way to dismiss (unmount) popup on animation end (and move this responsibility to the Item?)
}
}
render () {
return (
<div
className='popup'
ref={(el) => {
this.popupRef = el
}}
style={this.state.style}
>
<pre>{JSON.stringify(this.state.style, null, 2)}</pre>
</div>
)
}
}
/*
* Popup-producing item
*
* - [x] only render popup when wanted, unmount when dismissed
*/
const hiddenStyles = { opacity: 0 }
const showingStyles = { opacity: 1 }
class Item extends React.Component {
constructor (props) {
super(props)
this.openActions = this.openActions.bind(this)
this.hideActions = this.hideActions.bind(this)
this.state = {
showActions: false
}
}
openActions () {
this.setState({ showActions: true })
}
hideActions () {
this.setState({ showActions: false })
}
render () {
const { body } = this.props
return (
<li className='row' onClick={this.openActions}>
{body}
{this.state.showActions
? (
<Popup dismiss={this.hideActions} />
) : null}
</li>
)
}
}
/*
* App
*
* - [x] Show list of items
*/
const items = [
{ id: 'a', body: 'Green!' },
{ id: 'b', body: 'Green!' },
{ id: 'c', body: 'Yellow?' },
{ id: 'd', body: 'Red' },
{ id: 'e', body: 'Gray' }
]
class App extends React.Component {
render () {
return (
<ul>
{items.map((item) => {
return <Item {...item} key={item.id} />
})}
</ul>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
.row {
position: relative;
cursor: pointer;
padding: 5px;
}
.row:hover {
background: #d636e9;
color: #ffe2f0;
}
.popup {
z-index: 1;
color: black;
background: white;
border: 1px solid black;
position: absolute;
transition: opacity 0.9s ease;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />
我根本不喜欢这种解决方案,因为我不理解为什么需要setTimeout
,但至少可以使用...