如何让 React Portal 与 React Hook 一起工作?

新手上路,请多包涵

我特别需要在浏览器中收听自定义事件,从那里,我有一个按钮可以打开一个弹出窗口。我目前正在使用 React Portal 打开另一个窗口 (PopupWindow),但是当我在其中使用挂钩时它不起作用 - 但如果我使用类则可以。我的意思是,当窗口打开时,两者都显示其下方的 div,但当事件数据刷新时,带有挂钩的那个会擦除它。要进行测试,请将窗口打开至少 5 秒钟。

我在 CodeSandbox 中有一个示例,但我也会在此处发布,以防网站出现故障或发生其他问题:

https://codesandbox.io/s/k20poxz2j7

下面的代码不会运行,因为我不知道如何通过 react cdn 使 react hooks 工作,但你现在可以用上面的链接测试它

 const { useState, useEffect } = React;
function getRandom(min, max) {
  const first = Math.ceil(min)
  const last = Math.floor(max)
  return Math.floor(Math.random() * (last - first + 1)) + first
}
function replaceWithRandom(someData) {
  let newData = {}
  for (let d in someData) {
    newData[d] = getRandom(someData[d], someData[d] + 500)
  }
  return newData
}

const PopupWindowWithHooks = props => {
  const containerEl = document.createElement('div')
  let externalWindow = null

  useEffect(
    () => {
      externalWindow = window.open(
        '',
        '',
        `width=600,height=400,left=200,top=200`
      )

      externalWindow.document.body.appendChild(containerEl)
      externalWindow.addEventListener('beforeunload', () => {
        props.closePopupWindowWithHooks()
      })
      console.log('Created Popup Window')
      return function cleanup() {
        console.log('Cleaned up Popup Window')
        externalWindow.close()
        externalWindow = null
      }
    },
    // Only re-renders this component if the variable changes
    []
  )
  return ReactDOM.createPortal(props.children, containerEl)
}

class PopupWindow extends React.Component {
  containerEl = document.createElement('div')
  externalWindow = null
  componentDidMount() {
    this.externalWindow = window.open(
      '',
      '',
      `width=600,height=400,left=200,top=200`
    )
    this.externalWindow.document.body.appendChild(this.containerEl)
    this.externalWindow.addEventListener('beforeunload', () => {
      this.props.closePopupWindow()
    })
    console.log('Created Popup Window')
  }
  componentWillUnmount() {
    console.log('Cleaned up Popup Window')
    this.externalWindow.close()
  }
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.containerEl
    )
  }
}

function App() {
  let data = {
    something: 600,
    other: 200
  }
  let [dataState, setDataState] = useState(data)
  useEffect(() => {
    let interval = setInterval(() => {
      setDataState(replaceWithRandom(dataState))
      const event = new CustomEvent('onOverlayDataUpdate', {
        detail: dataState
      })
      document.dispatchEvent(event)
    }, 5000)
    return function clear() {
      clearInterval(interval)
    }
  }, [])
  useEffect(
    function getData() {
      document.addEventListener('onOverlayDataUpdate', e => {
        setDataState(e.detail)
      })
      return function cleanup() {
        document.removeEventListener(
          'onOverlayDataUpdate',
          document
        )
      }
    },
    [dataState]
  )
  console.log(dataState)

  // State handling
  const [isPopupWindowOpen, setIsPopupWindowOpen] = useState(false)
  const [
    isPopupWindowWithHooksOpen,
    setIsPopupWindowWithHooksOpen
  ] = useState(false)
  const togglePopupWindow = () =>
    setIsPopupWindowOpen(!isPopupWindowOpen)
  const togglePopupWindowWithHooks = () =>
    setIsPopupWindowWithHooksOpen(!isPopupWindowWithHooksOpen)
  const closePopupWindow = () => setIsPopupWindowOpen(false)
  const closePopupWindowWithHooks = () =>
    setIsPopupWindowWithHooksOpen(false)

  // Side Effect
  useEffect(() =>
    window.addEventListener('beforeunload', () => {
      closePopupWindow()
      closePopupWindowWithHooks()
    })
  )
  return (
    <div>
      <button type="buton" onClick={togglePopupWindow}>
        Toggle Window
      </button>
      <button type="buton" onClick={togglePopupWindowWithHooks}>
        Toggle Window With Hooks
      </button>
      {isPopupWindowOpen && (
        <PopupWindow closePopupWindow={closePopupWindow}>
          <div>What is going on here?</div>
          <div>I should be here always!</div>
        </PopupWindow>
      )}
      {isPopupWindowWithHooksOpen && (
        <PopupWindowWithHooks
          closePopupWindowWithHooks={closePopupWindowWithHooks}
        >
          <div>What is going on here?</div>
          <div>I should be here always!</div>
        </PopupWindowWithHooks>
      )}
    </div>
  )
}

const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
 <script crossorigin src="https://unpkg.com/react@16.7.0-alpha.2/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16.7.0-alpha.2/umd/react-dom.development.js"></script>
<div id="root"></div>

原文由 Rafael ‘BSIDES’ Pereira 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 353
2 个回答

const [containerEl] = useState(document.createElement('div'));

编辑

按钮 onClick 事件,调用功能组件 PopupWindowWithHooks第一次 调用,它按预期工作(创建新的 <div> ,在 useEffect 中附加 <div> 到弹出窗口)。

事件刷新, 再次 调用功能组件 PopupWindowWithHooks 和行 const containerEl = document.createElement('div') create new <div> 。但是(第二个)新的 <div> 永远不会附加到弹出窗口,因为行 externalWindow.document.body.appendChild(containerEl) 在 useEffect 挂钩中,它只会在挂载时运行并在卸载时清理(第二个参数是空数组 [])。

最后 return ReactDOM.createPortal(props.children, containerEl) 使用第二个参数 containerEl 创建门户 - 新的未附加 <div>

使用 containerEl 作为有状态值(useState hook),问题就解决了:

 const [containerEl] = useState(document.createElement('div'));

编辑2

代码沙盒: https://codesandbox.io/s/l5j2zp89k9

原文由 Boris Traljić 发布,翻译遵循 CC BY-SA 4.0 许可协议

Thought id 与一个对我来说非常有效的解决方案相呼应,它动态创建一个门户元素,通过 props 具有可选的 className 和元素类型,并在组件卸载时删除所述元素:

 export const Portal = ({
  children,
  className = 'root-portal',
  element = 'div',
}) => {
  const [container] = React.useState(() => {
    const el = document.createElement(element)
    el.classList.add(className)
    return el
  })

  React.useEffect(() => {
    document.body.appendChild(container)
    return () => {
      document.body.removeChild(container)
    }
  }, [])

  return ReactDOM.createPortal(children, container)
}

原文由 Samuel 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题