React自定义Hook之useMutilpleRef

概要

我们在React开发时候,有时候需要绑定列表中的多个元素,便于后面对列表中单个元素的操作,但是常用的hook函数useRef只能绑定一个DOM元素,本文提供一个可以解决该问题的自定义hook方法,useMutilpleRef。

代码及实现

基本功能

本例使用Map来缓存DOM元素的引用,如果DOM元素存在,则缓存在Map中;如果DOM元素被删除,则在Map中对应的键值对也就被删除。

代码实现

export const useMutipleRefs = <T extends HTMLElement>() => {
    const refs = useRef<Map<string|number, T> |null>(new Map<string|number, T>());
    const setRef = (key:string) => (el:T | null) => {
        if (el) {
            refs.current!.set(key, el);
        }else {
            refs.current!.delete(key);
        }
    } 

    useEffect(()=>{
        return ()=> {refs.current = null};
    }, []);

    return {refs, setRef};
}
  1. 该方法不用接受任何参数,采用泛型约束,只处理DOM元素;
  2. 采用Map来缓存DOM元素,key值可以是字符串,以支持React 18的useId hook函数,也可以是普通数字;
  3. 该方法的返回值包括两部分,首先是绑定了DOM元素引用的Map,该Map的内容可以被迭代查询,以支持各种复杂的应用场景;
  4. 返回值的另一部分是setRef 方法,该方法在绑定元素的时候被调用,参数可以接受HTMLElement和空值,空值用于删除的情况;
  5. 在组件被卸载时候,释放资源。

使用实例

下面是一个人员列表的例子,可以通过性别过滤列表中的元素,每个元素需要支持删除操作。
React自定义Hook之useMutilpleRef_第1张图片

关键代码如下, 完整代码请见附录:

响应式数据定义如下:

const [gender, setGender] = useState<Gender>(Gender.Female);
const [filteredIds, setFilteredIds] = useState<Array<number|string>>([]);
const userUI = useMemo(()=> users.filter(u=> u.gender === gender && !filteredIds.includes(u.id)), [gender, filteredIds]);

监听内容如下:

  1. 对当前选中的性别进行监听;
  2. 对要删除的User进行监听;
  3. 对列表内容进行缓存,只有选中的性别和要删除的Id数组发生变化,它才变化。

主要的点击事件代码如下:

type funcType = (event:MouseEvent)=>void;
const handleClickGender = (gender:Gender):funcType=>(event:MouseEvent)=> {
     setGender(gender);
} 
const clickDelete = (index:number|string)=> {
  console.log(index);
  setFilteredIds(prev=>[...prev, index]);
}

  1. 性别过滤按钮点击时候,触发handleClickGender
  2. 删除按钮点击时候,触发clickDelete

Refs 相关的代码

const {refs, setRef} = useMutipleRefs();
useEffect(()=> {
    console.log(refs.current); 
}, [userUI])

 <tbody>
 {
      userUI.map((user, index)=>(
          <tr key={user.id} ref={setRef(user.id)}>
              <td>{index + 1}</td>
              <td>{user.name}</td>
              <td>{user.gender === Gender.Female ? "Female": "Male"}</td>
              <td>{user.married? "Yes": "No"}</td>
              <td><button className="btn btn-success" onClick={()=>clickDelete(user.id)}>Delete</button></td>
          </tr>
      ))
  }
</tbody>
  1. 调用useMutipleRefs过的初始的refs的Map对象和setRef方法
  2. 对表格中的每个tr,调用setRef,传入用户Id

代码运行

React自定义Hook之useMutilpleRef_第2张图片
程序默认是获取所有Female的User信息,因此初始时候,有三个User的引用

点击Male按钮,显示有两个Male的User

React自定义Hook之useMutilpleRef_第3张图片
Map中的数据更新为所有Male的Tr引用。
React自定义Hook之useMutilpleRef_第4张图片

点击删除第一个Delete按钮
React自定义Hook之useMutilpleRef_第5张图片
数据成功删除

React自定义Hook之useMutilpleRef_第6张图片
Map中缓存的DOM引用,也同时被删除
React自定义Hook之useMutilpleRef_第7张图片

使用中额外注意的问题

本文提供的缓存方法,监听的DOM对象的类型是HTMLElement,它是DOM元素的基类,如果要使用其它DOM的特有属性,请在调用时候,明确具体类型。

例如存的DOM元素是input,并且需要在后面使用input的value,在调用该方法是,请明确Input的的类型,useMutipleRefs(),否则无获取不到其value值。

结论

本文提供的自动Hook useMutipleRefs可以实现对列表中多个元素引用值的缓存,在DOM元素在创建时候,可以同步创建,删除时候可以同步删除。

附录

完整代码

"use client"
import { FC, useEffect, useMemo, useState } from "react";
import { ReactElement, MouseEvent,useId } from "react";
import { usePrevious, useMutipleRefs } from "../hooks";
import "@/app/assets/bootstrap.min.css"
interface IProps{

}

enum  Gender {
    Male=0,
    Female
}

interface IUser {
    id:number|string;
    name:string;
    gender:Gender;
    married:boolean;
}

const Team:FC<IProps> = ({}):ReactElement => {
    const users = [
        {
            id:useId(),
            name:"abc",
            gender:Gender.Female,
            married:false
        },
        {
            id:useId(),
            name:"def",
            gender:Gender.Male,
            married:false
        },
        {
            id:useId(),
            name:"ghi",
            gender:Gender.Female,
            married:true
        },
        {
            id:useId(),
            name:"jkl",
            gender:Gender.Male,
            married:false
        },
        {
            id:useId(),
            name:"mno",
            gender:Gender.Female,
            married:true
        }
    ] as Array<IUser>;


        
const [gender, setGender] = useState<Gender>(Gender.Female);

type funcType = (event:MouseEvent)=>void;
const handleClickGender = (gender:Gender):funcType=>(event:MouseEvent)=> 					{
    setGender(gender);
} 


const [filteredIds, setFilteredIds] = useState<Array<number|string>>([]); 
const userUI = useMemo(()=> users.filter(u=> u.gender === gender && !filteredIds.includes(u.id)), [gender, filteredIds]);
 
 
 const {refs, setRef} = useMutipleRefs();
 const clickDelete = (index:number|string)=> {
     console.log(index);
     setFilteredIds(prev=>[...prev, index]);
 }
 useEffect(()=> {
     console.log(refs.current); 
 }, [userUI])
        return <>
                <div className="btn-group" role="group" aria-label="Basic outlined example">
                    <button className="btn btn-primary" onClick={handleClickGender(Gender.Female)}>Female</button>
                    <button className="btn btn-warning" onClick={handleClickGender(Gender.Male)}>Male</button>
                </div>
                <table className="table table-striped" style={{width:"50%"}}>
                    <thead>
                        <tr>
                            <th>Id</th>
                            <th>Name</th>
                            <th>Gender</th>
                            <th>Married</th>
                            <th>Operation</th>
                        </tr>    
                    </thead>
                    <tbody>
                        {
                            userUI.map((user, index)=>(
                                <tr key={user.id} ref={setRef(user.id)}>
                                    <td>{index + 1}</td>
                                    <td>{user.name}</td>
                                    <td>{user.gender === Gender.Female ? "Female": "Male"}</td>
                                    <td>{user.married? "Yes": "No"}</td>
                                    <td><button className="btn btn-success" onClick={()=>clickDelete(user.id)}>Delete</button></td>
                                </tr>
                            ))
                        }
                    </tbody>
                </table>
            </>
}

export default Team;

你可能感兴趣的:(前端,React,react.js,前端,前端框架)