如何使React Portal与React Hook一起使用? [英] How can I make React Portal work with React Hook?

查看:62
本文介绍了如何使React Portal与React Hook一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have this specific need to listen to a custom event in the browser and from there, I have a button that will open a popup window. I'm currently using React Portal to open this other window (PopupWindow), but when I use hooks inside it doesn't work - but works if I use classes. By working I mean, when the window opens, both shows the div below it but the one with hooks erases it when the data from the event refreshes. To test, leave the window open for at least 5 seconds.

我在CodeSandbox中有一个示例,但如果网站关闭或其他原因,我也会在此处发布:

I have an example in a CodeSandbox, but I'm also post here in case the website is down or something:

https://codesandbox.io/s/k20poxz2j7

下面的代码将无法运行,因为我不知道如何通过react cdn使react挂钩起作用,但是您现在可以使用上面的链接对其进行测试

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>

推荐答案

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

编辑

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

Button onClick event, invoke first call of functional component PopupWindowWithHooks and it works as expected (create new <div>, in useEffect append <div> to popup window).

事件刷新,调用功能组件 PopupWindowWithHooks second 调用,并在 const containerEl = document.createElement('div')行创建新再次< div> .但是,(第二个)新的< div> 永远不会追加到弹出窗口,因为 externalWindow.document.body.appendChild(containerEl)行在useEffect钩子中,该钩子会仅在mount上运行,然后在unmount上清除(第二个参数是一个空数组[]).

The event refresh, invoke second call of functional component PopupWindowWithHooks and line const containerEl = document.createElement('div') create new <div> again. But that (second) new <div> will never be appended to popup window, because line externalWindow.document.body.appendChild(containerEl) is in useEffect hook that would run only on mount and clean up on unmount (the second argument is an empty array []).

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

Finally return ReactDOM.createPortal(props.children, containerEl) create portal with second argument containerEl - new unappended <div>

使用 containerEl 作为有状态值(useState挂钩),问题得以解决:

With containerEl as a stateful value (useState hook), problem is solved:

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

EDIT2

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

这篇关于如何使React Portal与React Hook一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆