hooks
是一种能够在函数式组件中调用的函数,他可以让你在不编写class
的情况下使用state
以及其他的react
特性。
常用的hook
有下面这些:
- useState
- useEffect
- useRef
- useContext
- useReducer
这篇文章主要讲解如果useRef
实现对元素外边点击的检测
useRef
useRef
是一个非常强大的基础hook
,下面是三个主要的应用场景:
- 获取DOM元素节点
- 获取子组件的实例
- 渲染周期之间共享数据的数据(state不能存储跨渲染周期的数据,因为state的保存会触发组件的重渲染)
在这里我们通过
useRef
来获取真实的DOM
节点
useRef
函数接收一个变量用于ref
的初始值,然后返回一个ref
对象
const elementRef = useRef(null)
然后需要在对应的JSX节点中添加ref
属性,这个ref
属性值就是调用useRef
返回的ref
对象。此时需要获取真实DOM
,只需要获取到ref
的current
属性
const DOM = elementRef.current;
具体如下:
function Index() {
const elementRef = useRef(null)
return (
<>
<div ref={elementRef}>
Hello World
</div>
</>
)
}
具体实现
下面来检测一下是否在元素外边发生了点击事件
下面列出了两种场景,我们需要检测是否在元素外边进行了点击:
- 当创建了一个弹窗,当点击弹窗外的内容,我们需要关闭弹窗
- 当创建了一个
dropdown
,当点击dropdown
外的内容时候,需要关闭它
下面给一个简单的例子:
function Index() {
const [isOpen, setIsOpen] = useState(true)
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<div id="modal">
<Modal isOpen={isOpen}>
Modal组件内容
</Modal>
</div>
</>
)
}
在上面组件中点击按钮展示Modal
。我们的目标就是点击modal
外边的时候,关闭modal
。
下面是实现这个目标的具体流程:
-
使用
ref
引用Modal
组件 -
检测点击
-
验证是否在
modal
组件外发生点击 -
如果在
Modal
组件外发生点击,执行setIsOpen(false)
第一步: 引用Modal
首先使用useRef
引用Modal
节点
function Index() {
const [isOpen, setIsOpen] = useState(false);
const modalRef = useRef();
return (
<>
<div>
<h2>App with a Modal</h2>
<button onClick={() => setIsOpen(true)} type="button">
Open Modal
</button>
<div id="modal" ref={modalRef}>
<Modal isOpen={isOpen}>这是Modal</Modal>
</div>
</div>
</>
);
}
当当前组件被渲染的时候,我们可以通过modalRef.current
就可以获取DOM节点
第二步:添加全局点击事件监听
第二部在全局添加一个事件监听
useEffect(() => {
function handler(event) {
console.log(event, 'clicked somewhere')
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
我们在window
上添加了点击监听,监听整个页面的事件。需要注意的是,在组件移除的时候,一定要移除绑定的全局事件,不然可能会造成内存泄漏或者未知的错误
第三步: 检测是否在元素外边发生点击
当事件点击时,回调函数的入参是Event
对象,这个对象包含了点击事件的一系列信息。如果要获取到当前点击的元素使用event.target
就行。下面检测modal
元素是否包含event.target
:
useEffect(() => {
function handler(event) {
if (!modalRef.current?.contains(event.target)) {
console.log('clicked outside of the modal')
}
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
}, [])
第四步: 点击modal外的时候,关闭modal
在检测到在modal
外点击的时候,执行setIsOpen(false)
关闭弹窗
useEffect(() => {
function handler(event) {
if (!modalRef.current?.contains(event.target)) {
console.log('clicked outside of the modal')
}
}
window.addEventListener('click', handler)
return () => window.removeEventListener('click', handler)
})
封装hook
下面把上面的功能封装成新hook
:
export function useOnClickOutside(ref, callback) {
useEffect(() => {
function handler(event) {
if (!ref.current?.contains(event.target)) {
callback();
}
}
window.addEventListener('click', handler);
return () => window.removeEventListener('click', handler)
}, [callback, ref]);
}
在上面hook
中,需要传入两个参数:
ref
: 需要有clickOutside
的效果的ref
对象
callback
: 触发clickOutside
的时候的回调函数
函数内部在window
上绑定了监听事件,点击时判断点击元素是否在ref
对应DOM
结构中:如果不是在对应DOM
结构中,就触发传入的回调函数。
下面是使用useOnClickOutside
hook
的函数
function Index() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
useOnClickOutside(ref, () => setIsOpen(false));
return (
<div>
<h2>App with ad Modal</h2>
<button type="button" onClick={() => setIsOpen(false)}>
Open Modal
</button>
<div ref={ref} id="modal">
<Modal isOpen={isOpen}>This is a Modal</Modal>
</div>
</div>
);
}
在上面代码中,实现了同样的功能。需要更改的添加以下的代码
useOnClickOutside(ref, () => setIsOpen(false));
useRef
的使用远不止此。通常我们会使用它来保存变量,这里讲讲useRef
和useState
的应用场景。在hook
中,useState
和useRef
都可以用来保存变量,不同的是当useRef
的值发生更改时,组件并不会发生重渲染。
github地址: github.com/skychenbo/s… 欢迎关注