如果另一个模式正在转换出视图,则模态不会过渡到视图中



我正在尝试在召唤和关闭弹出窗口时将弹出窗口移入和移出视图。

最初呈现为 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>
  )
}
当我单独打开弹出窗口时,在

打开下一个弹出窗口之前花时间关闭一个弹出窗口,过渡会起作用。但是,如果我在另一个弹出窗口完全消失之前打开一个弹出窗口,则过渡从一开始就会卡在最终状态。

问题是为什么?

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,但至少它可以工作......

相关内容

  • 没有找到相关文章

最新更新