首页 > 其他分享 >React实现气泡确认框(PopConfirm)

React实现气泡确认框(PopConfirm)

时间:2024-09-19 15:21:03浏览次数:11  
标签:PopConfirm const top React visible 点击 气泡 children left

近期模仿Ant Design,实现了一个气泡确认框

先来看效果图

  • 想要这样使用组件

<PopConfirm
    title="操作1"
    description="描述1"
    onConfirm={handleConfirm}
    onCancel={handleCancel}
    >
    <Button style={{ position: "fixed", top: 0, left: 0 }} type="primary">
        左上角按钮
    </Button>
</PopConfirm>
  • 首先,我定义了一个变量visible,来动态的控制popConfirm的显示与隐藏。并接受一个children,来放置原有的按钮组件

interface PopConfirmProps {
    children: ReactElement; 
}

function PopConfirm({ children }: PopConfirmProps){
    const [visible, setVisible] = useState<boolean>(false);
    return (
        <div>
            <Layout show={visible}>popConfirm</Layout>
            {children}
        </div>
    )
}

export default PopConfirm;
  • 如何才能在点击按钮时,先弹出popConfirm呢?

  • 这里我使用了cloneElement,来为传入的children注入onClick属性。然后将Layout设为固定定位fixed,这样popConfirm就不会影响页面的布局了。

interface PopConfirmProps {
    children: ReactElement; 
}

function PopConfirm({ children }: PopConfirmProps){
    const [visible, setVisible] = useState<boolean>(false);
    return (
        <div>
            <Layout show={visible}>popConfirm</Layout>
            {cloneElement(children, {
                onClick() {
                    setVisible((visible) => !visible);
                }
            })}
        </div>
    )
}

export default PopConfirm;
  • 这样就可以控制popConfirm是否显示了。

  • 现在只需要将popConfirm放在所点击按钮的旁边就可以了。

  • 获取按钮元素的位置信息,我使用了getBoundingClientRect这个方法。

  • 但如若要获取按钮的位置,需要先获取到按钮元素,可以使用cloneElement的第二个参数,为按钮元素指定ref属性

interface PopConfirmProps {
    children: ReactElement; 
}
function PopConfirm({ children }: PopConfirmProps){
    const [visible, setVisible] = useState<boolean>(false);
    const triggerRef = useRef<HTMLElement>(null);
    const popRef = useRef<HTMLDivElement>(null);
    return (
        <div>
            <Layout show={visible} ref={popRef}>popConfirm</Layout>
            {cloneElement(children, {
                onClick() {
                    setVisible((visible) => !visible);
                },
                ref: triggerRef,
            })}
        </div>
    )
}
export default PopConfirm;
  • 之后就是计算位置,然后动态地设置popConfirm的位置

  • 定义了一个变量position来存储topleft

const [visible, setVisible] = useState<boolean>(false);
const [position, setPosition] = useState({
    top: 0,
    left: 0,
});
const triggerRef = useRef<HTMLElement>(null);
const popRef = useRef<HTMLDivElement>(null);

/* 计算气泡确认框的位置 */
useEffect(() => {
    if (triggerRef.current && popRef.current) {
        const triggerRect = triggerRef.current.getBoundingClientRect(); // 触发点击事件元素的位置信息
        const popRect = popRef.current.getBoundingClientRect(); // 气泡确认框的位置信息
        // 根据所点击元素的位置,决定其放置的位置(上面放不下,则放在下面,下面放不下,则放在上面......)
        const newPosition = {
            top:
            triggerRect.top < popRect.height
            ? triggerRect.bottom // 放在上面
            : triggerRect.top - popRect.height, // 放在下面
            left:
            triggerRect.left < popRect.width / 2
            ? triggerRect.left // 偏右
            : triggerRect.left - popRect.width / 2, // 偏左
        };
        setPosition(newPosition);
    }
}, [visible]);
  • Layout组件动态的设置topleft值即可

<Layout show={visible} positon={position} ref={popRef}></Layout>
  • 这里使用到了Styled-components来编写样式组件,只展示了一些关键点

export interface Position {
    top: number;
    left: number;
}
interface Props {
    show: boolean; // 是否显示气泡确认框
    positon: Position; // 位置信息
}
const excludeProps = ["positon", "show"];

const Layout = styled.div.withConfig({
    shouldForwardProp: (prop) => !excludeProps.includes(prop),
})<Props>`
  ${({ positon: { left, top }, show }) => css`
    position: fixed;
    top: ${top}px;
    left: ${left}px;
    ${!show &&
      css`
      display: none;
    `}
  `}
`;
export default Layout;
  • 当点击页面中,除了popConfirm组件之外的地方时,需要隐藏popConfirm

  • 我的做法是:当打开popConfirm时,为页面添加一个点击事件,隐藏时移除点击事件。

/* 为页面绑定点击事件,当点击气泡确认框之外的地方时,关闭气泡确认框
   为什么要排除触发点击事件的元素?若不排除,当用户点击指定元素之后,此次点击会立即被刚刚所添加的全局click事件捕获
  */
useEffect(() => {
    function closePopConfirm(e: MouseEvent) {
        if (
            triggerRef.current?.contains(e.target as Node) ||
            popRef.current?.contains(e.target as Node)
        ) {
            return;
        }
        setVisible(false);
    }
    if (visible) {
        document.addEventListener("click", closePopConfirm);
    }
    return () => {
        document.removeEventListener("click", closePopConfirm);
    };
}, [visible]);
  • 基本功能实现后,就可以添加个性化需求了,完整代码如下

import { cloneElement, ReactElement, useEffect, useRef, useState } from "react";
import Button from "../Button/Button";
import BtnLayout from "./components/BtnLayout";
import Description from "./components/Description";
import Layout from "./components/Layout";
import Title from "./components/Title";

interface PopConfirmProps {
    children: ReactElement;
    title: string; // 标题
    description?: string; // 描述
    okText?: string; // 确定按钮的文本
    cancelText?: string; // 取消按钮的文本
    onConfirm?: () => unknown; // 点击确认按钮后触发的回调
    onCancel?: () => unknown; // 点击取消按钮后触发的回调
    showCancel?: boolean; // 是否显示取消按钮
}

function PopConfirm({
    children,
    title,
    description,
    showCancel = true,
    okText = "确认",
    cancelText = "取消",
    onConfirm,
    onCancel,
}: PopConfirmProps) {
    const [visible, setVisible] = useState<boolean>(false);
    const [position, setPosition] = useState({
        top: 0,
        left: 0,
    });
    const triggerRef = useRef<HTMLElement>(null);
    const popRef = useRef<HTMLDivElement>(null);

    /* 为页面绑定点击事件,当点击气泡确认框之外的地方时,关闭气泡确认框
    为什么要排除触发点击事件的元素?若不排除,当用户点击指定元素之后,此次点击会立即被刚刚所添加的全局click事件捕获
  */
    useEffect(() => {
        function closePopConfirm(e: MouseEvent) {
            if (
                triggerRef.current?.contains(e.target as Node) ||
                popRef.current?.contains(e.target as Node)
            ) {
                return;
            }
            setVisible(false);
        }

        if (visible) {
            document.addEventListener("click", closePopConfirm);
        }
        return () => {
            document.removeEventListener("click", closePopConfirm);
        };
    }, [visible]);

    /* 计算气泡确认框的位置 */
    useEffect(() => {
        if (triggerRef.current && popRef.current) {
            const triggerRect = triggerRef.current.getBoundingClientRect(); // 触发点击事件元素的位置信息
            const popRect = popRef.current.getBoundingClientRect(); // 气泡确认框的位置信息
            // 根据所点击元素的位置,决定其放置的位置(上面放不下,则放在下面,下面放不下,则放在上面......)
            const newPosition = {
                top:
                triggerRect.top < popRect.height
                ? triggerRect.bottom // 放在上面
                : triggerRect.top - popRect.height, // 放在下面
                left:
                triggerRect.left < popRect.width / 2
                ? triggerRect.left // 偏右
                : triggerRect.left - popRect.width / 2, // 偏左
            };
            setPosition(newPosition);
        }
    }, [visible]);

    function handleCancel() {
        onCancel?.();
        setVisible(false);
    }

    function handleConfirm() {
        onConfirm?.();
        setVisible(false);
    }

    return (
        <div>
            <Layout show={visible} positon={position} ref={popRef}>
                <Title>{title}</Title>
                {description && <Description>{description}</Description>}
                <BtnLayout showCancel={showCancel}>
                    {showCancel && (
                        <Button size="small" onClick={handleCancel}>
                            {cancelText}
                        </Button>
                    )}
                    <Button size="small" type="primary" onClick={handleConfirm}>
                        {okText}
                    </Button>
                </BtnLayout>
            </Layout>
            {cloneElement(children, {
                onClick() {
                    setVisible((visible) => !visible);
                },
                ref: triggerRef,
            })}
        </div>
    );
}

export default PopConfirm;
  • 使用示例,效果图在前面

import Button from "@/ui/Button/Button";
import PopConfirm from "@/ui/PopConfirm/PopConfirm";

function Test() {
    function handleCancel() {
        console.log("点击了取消");
    }

    function handleConfirm() {
        console.log("点击了确认");
    }

    return (
        <div>
            <PopConfirm
                title="操作1"
                description="描述1"
                onConfirm={handleConfirm}
                onCancel={handleCancel}
                >
                <Button style={{ position: "fixed", top: 0, left: 0 }} type="primary">
                    左上角按钮
                </Button>
            </PopConfirm>
            <PopConfirm
                title="操作2"
                description="描述2"
                onConfirm={handleConfirm}
                onCancel={handleCancel}
                >
                <Button
                    style={{ position: "fixed", bottom: 0, left: 0 }}
                    type="primary"
                    >
                    左下角按钮
                </Button>
            </PopConfirm>
        </div>
    );
}

export default Test;

标签:PopConfirm,const,top,React,visible,点击,气泡,children,left
From: https://blog.csdn.net/qq_64157464/article/details/142349919

相关文章

  • React升级18总结
    升级1818有哪些更新root节点的处理//旧ReactDOM.render(<App/>,document.getElementById('root'));//新constroot=createRoot(document.getElementById("root"));root.render(App);render中移除了回调函数//旧constcontainer=document.getElementB......
  • 解决React Warning: Function components cannot be given refs. Attempts to access
    问题当我使用如下方式调用组件子组件UploadModal并且绑定Ref时React报错“Warning:Functioncomponentscannotbegivenrefs.Attemptstoaccessthisrefwillfail.DidyoumeantouseReact.forwardRef()?”;constUploadModalRef=useRef(null);constopenUploadModa......
  • react react18+vite+typeScript+eslint+prettier+husky+lint-staged+commitlint 快速
    技术栈react18react-router6antd5zustand4vite45axiosfakerjs模拟数据dayjslodashtypescriptechartscommitlint、prettier、eslinthusky、lint-staged自定义commitlint、cz-cli自定义eslint、prettier代码规范技术栈代码格式规范和语法检测vscode:统一前端编辑器。editor......
  • react hooks--useCallback
    概述useCallback缓存的是一个函数,主要用于性能优化!!!基本用法如何进行性能的优化呢?useCallback会返回一个函数的memoized(记忆的)值;在依赖不变的情况下,多次定义的时候,返回的值是相同的;语法:constmemoizedCallback=useCallback(()=>{doSomething(a,b);......
  • react是什么?
    React是一个由Facebook开发和维护的开源JavaScript库,用于构建用户界面,特别是单页应用程序(SPA)。它通过组件化的方式来帮助开发者创建可重用的UI组件,从而简化了前端开发的复杂度。React的核心特点包括:核心特点React是一个强大的工具,用于构建动态和高效的用户界面。通过组......
  • 开闭原则在react中应用
    定义“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”这意味着我们应该能够添加新的功能或行为,而无需修改现有的代码。对扩展开放 => 允许通过拓展来添加新功能或行为对修改关闭 => 不直接修改现有代码分析对应到react中,首选的场景就是组件了。react的组件的props其实......
  • ReAct && MRKL
    ReActhttps://learnprompting.org/docs/advanced_applications/react WhatisReAct?ReAct1(Reason+Act)isaparadigmthatenableslanguagemodelstosolvecomplextasksthroughnaturallanguagereasoningandactions.ItallowsanLLMtoperformcertain......
  • react-pdf预览在线PDF的使用
    1、在react项目中安装react-pdf依赖包建议安装8.0.2版本的react-pdf,如果安装更高版本的可能出现一些浏览器的兼容性问题;[email protected] 1、PC端的使用1.1、封装一个组件:PdfViewModal.tsximportReact,{useState}from'react'import{Modal,Spin,......
  • PyQt5 使用 QFrame 绘制聊天(三角)气泡,并显示文字
    PyQt5使用QFrame绘制聊天(三角)气泡,并显示文字在PyQt5中,当需要想得到一个自定义的聊天气泡时,可以使用QPainter进行自定义绘制代码如下使用QPainter进行自定义绘制#!/usr/bin/envpython3#-*-coding:UTF-8-*-"""@File:test_QFrame.py@Author:......
  • react-intl
    react-intl6.6.8 • Public • Published 4monthsago ReadmeCode Beta10Dependencies3,811Dependents330VersionsReactIntlWe'vemigratedthedocsto https://formatjs.io/docs/getting-started/installation.ReadmeKeywordsintli18nin......