通过react中的createPortal只做自己的Dropdown下拉框

53 阅读1分钟

要实现该功能的核心点有两个,getBoundingClientRect() 和 ReactDOM.createPortal

父组件 Slide.jsx

import Drop from './Drop';
.....


const dropdownData = [
    {
        label: 'Menu 1',
        children: [
            { label: 'Submenu 1.1' },
            { label: 'Submenu 1.2', children: [{ label: 'Submenu 1.2.1' }] }
        ]
    },
    { label: 'Menu 2' }
];


<Drop data={dropdownData}>
    <div className="mi-icon-box">
        <Icon icon='jtm-add'></Icon>
    </div>
</Drop>

子组件 Drop.jsx

import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

function CustomDropdown({ children, content }) {
    const hasContent = content.props.items?.length !== 0;
    console.log(children, content, hasContent, '数据');
    const [isOpen, setIsOpen] = useState(false);
    const triggerRef = useRef(null); // 触发元素的引用
    const dropdownRef = useRef(null); // 子菜单容器的引用

    useEffect(() => {
        if (isOpen && triggerRef.current) {
            const rect = triggerRef.current.getBoundingClientRect(); // 获取触发元素的位置信息
            const dropdownEl = dropdownRef.current;

            // 动态设置子菜单的位置
            dropdownEl.style.position = 'absolute';
            dropdownEl.style.top = `${rect.top + window.scrollY}px`; // 顶部对齐
            dropdownEl.style.left = `${rect.right + window.scrollX}px`; // 右侧对齐
        }
    }, [isOpen]);

    return (
        <>
            {/* 触发元素 */}
            <div
                ref={triggerRef}
                onMouseEnter={() => setIsOpen(true)} // 鼠标进入时显示子菜单
                onMouseLeave={() => setIsOpen(false)} // 鼠标离开时隐藏子菜单
                style={{ cursor: 'pointer', display: 'inline-block' }}
            >
                {children} {/* 图标或触发内容 */}
            </div>

            {/* 子菜单 */}
            {isOpen &&
                ReactDOM.createPortal(
                    <div
                        ref={dropdownRef}
                        onMouseEnter={() => setIsOpen(true)} // 鼠标进入子菜单时不关闭
                        onMouseLeave={() => setIsOpen(false)} // 鼠标离开子菜单时关闭
                        style={
                            hasContent
                                ? {
                                      backgroundColor: '#fff',
                                      border: '1px solid #ccc',
                                      borderRadius: '4px',
                                      boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
                                      zIndex: 1000,
                                      padding: '8px'
                                  }
                                : null
                        }
                    >
                        {content}
                    </div>,
                    document.body // 将子菜单挂载到 body 上
                )}
        </>
    );
}

function NestedDropdown({ items }) {
    // console.log(items, 'items');
    return (
        <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
            {items.map((item, index) => (
                <li key={index} style={{ padding: '8px 16px', cursor: 'pointer' }}>
                    <CustomDropdown content={<NestedDropdown items={item.children || []} />}>{item.label}</CustomDropdown>
                </li>
            ))}
        </ul>
    );
}

export default function Drop(props) {
    return (
        <div>
            {/* 图标作为触发元素 */}
            <CustomDropdown content={<NestedDropdown items={props.data} />}>
                <div>{props.children}</div>
            </CustomDropdown>
        </div>
    );
}