我发现我正在整个应用程序中重用行为,当用户在某个元素之外单击时,我可以将其隐藏。
通过引入钩子,我可以将其放在钩子中并在各个组件之间共享,以免我在每个组件中编写相同的逻辑吗?
我已经在以下组件中实现了一次。
const Dropdown = () => {
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);
const handleHideDropdown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setIsDropdownVisible(false);
}
};
const handleClickOutside = (event: Event) => {
if (
wrapperRef.current &&
!wrapperRef.current.contains(event.target as Node)
) {
setIsDropdownVisible(false);
}
};
useEffect(() => {
document.addEventListener('keydown', handleHideDropdown, true);
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('keydown', handleHideDropdown, true);
document.removeEventListener('click', handleClickOutside, true);
};
});
return(
<DropdownWrapper ref={wrapperRef}>
<p>Dropdown</p>
</DropdownWrapper>
);
}
答案 0 :(得分:2)
这是可能的。
您可以创建一个名为useComponentVisible
的可重用钩子
import { useState, useEffect, useRef } from 'react';
export default function useComponentVisible(initialIsVisible) {
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
const ref = useRef<HTMLDivElement>(null);
const handleHideDropdown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setIsElementVisible(false);
}
};
const handleClickOutside = (event: Event) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
setIsElementVisible(false);
}
};
useEffect(() => {
document.addEventListener('keydown', handleHideDropdown, true);
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('keydown', handleHideDropdown, true);
document.removeEventListener('click', handleClickOutside, true);
};
});
return { ref, isComponentVisible, setIsComponentVisible };
}
然后在要添加功能的组件中执行以下操作:
const DropDown = () => {
const { ref, isComponentVisible } = useComponentVisible(true);
return (
<div ref={ref}>
{isComponentVisible && (<p>Going into Hiding</p>)}
</div>
);
}
在此处找到一个codesandbox示例。
答案 1 :(得分:1)
好吧,在苦苦挣扎了一点之后,我来到了下一个工作坊,除了保罗·菲茨杰拉德所做的那样,而且我的回答也包括了转变
首先,我希望我的下拉菜单在ESCAPE键事件和鼠标外部单击时关闭。为了避免为每个事件创建一个useEffect,我以一个辅助函数结尾:
//useDocumentEvent.js
import { useEffect } from 'react'
export const useDocumentEvent = (events) => {
useEffect(
() => {
events.forEach((event) => {
document.addEventListener(event.type, event.callback)
})
return () =>
events.forEach((event) => {
document.removeEventListener(event.type, event.callback)
})
},
[events]
)
}
然后,使用useDropdown挂钩带来所有所需的功能:
//useDropdown.js
import { useCallback, useState, useRef } from 'react'
import { useDocumentEvent } from './useDocumentEvent'
/**
* Functions which performs a click outside event listener
* @param {*} initialState initialState of the dropdown
* @param {*} onAfterClose some extra function call to do after closing dropdown
*/
export const useDropdown = (initialState = false, onAfterClose = null) => {
const ref = useRef(null)
const [isOpen, setIsOpen] = useState(initialState)
const handleClickOutside = useCallback(
(event) => {
if (ref.current && ref.current.contains(event.target)) {
return
}
setIsOpen(false)
onAfterClose && onAfterClose()
},
[ref, onAfterClose]
)
const handleHideDropdown = useCallback(
(event) => {
if (event.key === 'Escape') {
setIsOpen(false)
onAfterClose && onAfterClose()
}
},
[onAfterClose]
)
useDocumentEvent([
{ type: 'click', callback: handleClickOutside },
{ type: 'keydown', callback: handleHideDropdown },
])
return [ref, isOpen, setIsOpen]
}
最后,要使用此功能(它具有一些情感样式):
//Dropdown.js
import React, { useState, useEffect } from 'react'
import styled from '@emotion/styled'
import { COLOR } from 'constants/styles'
import { useDropdown } from 'hooks/useDropdown'
import { Button } from 'components/Button'
const Dropdown = ({ children, closeText, openText, ...rest }) => {
const [dropdownRef, isOpen, setIsOpen] = useDropdown()
const [inner, setInner] = useState(false)
const [disabled, setDisabled] = useState(false)
const timeout = 150
useEffect(() => {
if (isOpen) {
setInner(true)
} else {
setDisabled(true)
setTimeout(() => {
setDisabled(false)
setInner(false)
}, timeout + 10)
}
}, [isOpen])
return (
<div style={{ position: 'relative' }} ref={dropdownRef}>
<Button onClick={() => setIsOpen(!isOpen)} disabled={disabled}>
{isOpen ? closeText || 'Close' : openText || 'Open'}
</Button>
<DropdownContainer timeout={timeout} isVisible={isOpen} {...rest}>
{inner && children}
</DropdownContainer>
</div>
)
}
const DropdownContainer = styled.div(
{
position: 'absolute',
backgroundColor: COLOR.light,
color: COLOR.dark,
borderRadius: '2px',
width: 400,
boxShadow: '0px 0px 2px 0px rgba(0,0,0,0.5)',
zIndex: 1,
overflow: 'hidden',
right: 0,
},
(props) => ({
transition: props.isVisible
? `all 700ms ease-in-out`
: `all ${props.timeout}ms ease-in-out`,
maxHeight: props.isVisible ? props.maxHeight || 300 : 0,
})
)
export { Dropdown }
要使用它,只需:
//.... your code
<Dropdown>
<Whatever.Needs.To.Be.Rendered />
</Dropdown>
//... more code
此解决方案的信用额为previous answer,此处为this entry,中为article。