bsi*_*des 19 javascript reactjs react-hooks
我有一个在浏览器中监听自定义事件的特定需求,然后我有一个按钮可以打开一个弹出窗口。我目前正在使用 React Portal 打开另一个窗口(PopupWindow),但是当我在里面使用钩子时它不起作用 - 但如果我使用类就可以工作。通过工作,我的意思是,当窗口打开时,两者都显示其下方的 div,但带有钩子的 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)Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
Sam*_*uel 22
思想 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)
}
Run Code Online (Sandbox Code Playgroud)
Bor*_*jić 12
const [containerEl] = useState(document.createElement('div'));
编辑
按钮 onClick 事件,调用功能组件PopupWindowWithHooks 的第一次调用,它按预期工作(创建新的,在 useEffect append<div><div>到弹出窗口)。
事件刷新,调用功能组件PopupWindowWithHooks 的第二次调用并再次创建新行。但是那个(第二个)new永远不会被附加到弹出窗口,因为 line在 useEffect 钩子中,它只会在安装时运行并在卸载时清理(第二个参数是一个空数组 [])。const containerEl = document.createElement('div')<div><div>externalWindow.document.body.appendChild(containerEl)
最后return ReactDOM.createPortal(props.children, containerEl)使用第二个参数containerEl创建门户- 新的未附加<div>
将containerEl作为有状态值(useState hook),问题解决了:
const [containerEl] = useState(document.createElement('div'));
Run Code Online (Sandbox Code Playgroud)
编辑2
代码沙盒:https : //codesandbox.io/s/l5j2zp89k9
您可以创建一个小的辅助钩子,它会首先在 dom 中创建一个元素:
import { useLayoutEffect, useRef } from "react";
import { createPortal } from "react-dom";
const useCreatePortalInBody = () => {
const wrapperRef = useRef(null);
if (wrapperRef.current === null && typeof document !== 'undefined') {
const div = document.createElement('div');
div.setAttribute('data-body-portal', '');
wrapperRef.current = div;
}
useLayoutEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper || typeof document === 'undefined') {
return;
}
document.body.appendChild(wrapper);
return () => {
document.body.removeChild(wrapper);
}
}, [])
return (children => wrapperRef.current && createPortal(children, wrapperRef.current);
}
Run Code Online (Sandbox Code Playgroud)
您的组件可能如下所示:
const Demo = () => {
const createBodyPortal = useCreatePortalInBody();
return createBodyPortal(
<div style={{position: 'fixed', top: 0, left: 0}}>
In body
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
请注意,此解决方案不会在服务器端渲染期间渲染任何内容。
所选/流行的答案很接近,但它不必要地在每个渲染上创建未使用的 DOM 元素。可以为钩子useState提供一个函数以确保初始值仅创建一次:
const [containerEl] = useState(() => document.createElement('div'));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
18455 次 |
| 最近记录: |