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… 欢迎关注