如何将useComponentVisible Hook改写适配React类组件使用
问题背景
我有一套检测组件外部点击的逻辑,原本封装为自定义Hook在函数式组件中使用,检测到外部点击、按下ESC键时会触发下拉组件隐藏逻辑,现在需要把这套逻辑迁移到类组件中使用。
原有自定义Hook实现
原useComponentVisible Hook代码如下:
import { useState, useEffect, useRef } from 'react'; export default function useComponentVisible(visible) { const [isComponentVisible, setIsComponentVisible] = useState(visible); const ref = useRef(null); const handleHideDropdown = (event) => { if (event.key === 'Escape') { setIsComponentVisible(false); } }; const handleClickOutside = (event) => { if (ref.current && !ref.current.contains(event.target)) { setIsComponentVisible(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 }; }
函数组件中的原有使用方式
之前在函数式组件里的调用示例:
import useComponentVisible from '../../hooks/useComponentVisible'; import { MenuItem, MenuWrapper } from './MenuDropDownStyle'; const MenuDropDown = () => { const { ref, isComponentVisible } = useComponentVisible(true); return ( <> {isComponentVisible && ( <MenuWrapper ref={ref}> <MenuItem to="/lectures">Your profile</MenuItem> <MenuItem to="/lectures">Log out</MenuItem> </MenuWrapper> )} </> ); }; export default MenuDropDown;
待改造的类组件代码
现在需要在类组件CurrencyDropdown中实现相同的显隐控制逻辑,组件现有代码如下:
import React, { Component } from "react"; import { SelectWrapper } from "./CurrencyDropdownStyle"; import { getCurrency } from "../../../utls/MakeQuery"; import { SelectInput } from '../../'; import useComponentVisible from '../../hooks/useComponentVisible' export class CurrencyDropdown extends Component { constructor() { super(); this.state = { currency: [], }; // 类组件中无法直接调用Hook,此处原调用方式注释 // const { ref, isComponentVisible } = useComponentVisible(true); } componentDidMount() { getCurrency() .then((res) => { this.setState({ currency: res.data.currencies, }); this.props.toggleCurrency("USD+$"); }) .catch((err) => { console.log(err); }); } handleToggleCurrency(value){ this.props.toggleCurrency(value) } render() { return <SelectWrapper> {this.state.currency ? this.state.currency.map((item,index)=>( <SelectInput key={index} value={`${item.label}+${item.symbol}`} label={`${item.symbol} ${item.label}`}/> )):""} </SelectWrapper>; } } export default CurrencyDropdown;
实现方案
类组件不能直接调用自定义Hook,把Hook里的逻辑对应映射到类组件的生命周期和实例属性即可,对应关系如下:
- 用
React.createRef()替代Hook里的useRef保存DOM引用 - 把
isComponentVisible放到组件state中管理,初始值设为默认可见状态true - 事件监听的绑定放到
componentDidMount生命周期,移除逻辑放到componentWillUnmount生命周期,和useEffect的挂载/清理逻辑完全对应 - 事件回调定义为类的实例方法,提前绑定
this避免指向错误
改造完成的类组件代码如下:
import React, { Component } from "react"; import { SelectWrapper } from "./CurrencyDropdownStyle"; import { getCurrency } from "../../../utls/MakeQuery"; import { SelectInput } from '../../'; export class CurrencyDropdown extends Component { constructor(props) { super(props); this.state = { currency: [], // 对应Hook里的isComponentVisible状态,初始默认可见 isComponentVisible: true }; // 对应Hook里的ref,保存下拉容器DOM引用 this.dropdownRef = React.createRef(); // 绑定事件回调的this指向 this.handleHideDropdown = this.handleHideDropdown.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); this.setIsComponentVisible = this.setIsComponentVisible.bind(this); } // 对应Hook里的ESC按键隐藏逻辑 handleHideDropdown(event) { if (event.key === 'Escape') { this.setIsComponentVisible(false); } } // 对应Hook里的外部点击隐藏逻辑 handleClickOutside(event) { if (this.dropdownRef.current && !this.dropdownRef.current.contains(event.target)) { this.setIsComponentVisible(false); } } // 对应Hook返回的setIsComponentVisible方法,供外部手动控制显隐 setIsComponentVisible(visible) { this.setState({ isComponentVisible: visible }) } componentDidMount() { // 绑定全局事件,和useEffect的绑定逻辑一致 document.addEventListener('keydown', this.handleHideDropdown, true); document.addEventListener('click', this.handleClickOutside, true); getCurrency() .then((res) => { this.setState({ currency: res.data.currencies, }); this.props.toggleCurrency("USD+$"); }) .catch((err) => { console.log(err); }); } componentWillUnmount() { // 组件卸载时移除事件监听,对应useEffect返回的清理函数 document.removeEventListener('keydown', this.handleHideDropdown, true); document.removeEventListener('click', this.handleClickOutside, true); } handleToggleCurrency(value){ this.props.toggleCurrency(value) } render() { const { isComponentVisible, currency } = this.state; return ( <SelectWrapper ref={this.dropdownRef}> {/* 按显隐状态渲染内容 */} {isComponentVisible && currency ? currency.map((item,index)=>( <SelectInput key={index} value={`${item.label}+${item.symbol}`} label={`${item.symbol} ${item.label}`} onClick={() => this.handleToggleCurrency(`${item.label}+${item.symbol}`)} /> )):""} </SelectWrapper> ) } } export default CurrencyDropdown;
如果需要支持传入自定义初始visible值,只需要在constructor里读取props对应字段给
isComponentVisible赋初始值即可,逻辑和原Hook完全一致。
内容的提问来源于stack exchange,提问作者sarangkkl
相关产品推荐
相关产品推荐

