使用高阶组件封装路由拦截逻辑

前言

用户在编辑表单时,经常编辑一半中途切换页面,我们需要进行路由拦截,对用户进行询问,是否需要保存当前信息。

React中实现路由拦截使用useBlocker就可以了。本文将使用高阶组件对路由拦截进行封装来扩展功能。

  1. 最基本的路由拦截,默认拦截弹窗
  2. 自定义控制是否需要拦截
  3. 自定义弹窗取消和确定事件
  4. 自定义弹窗样式

后面有代码仓库地址

正文

基本的路由拦截

编写一个WithBlocker来控制路由拦截,在需要拦截的页面使用就行

tsx 复制代码
import { Modal } from 'antd';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useBlocker } from 'react-router-dom';

const WithBlocker = (WrappedComponent: React.ComponentType<any>) => {
  return (props: any) => {
    /** 是否出现弹窗*/
    const [isOpen, setIsOpen] = useState(false);
    const blocker: any = useBlocker(
      ({ currentLocation, nextLocation }) => currentLocation.pathname !== nextLocation.pathname
    );

    useEffect(() => {
      if (blocker.state === 'blocked') {
        setIsOpen(true);
      } else {
        setIsOpen(false);
      }
    }, [blocker]);

    return (
      <>
        <WrappedComponent {...props} />

        <Modal
          title="默认弹窗"
          open={isOpen}
          onCancel={() => {
            setIsOpen(false);
            blocker.reset();
          }}
          onOk={() => {
            setIsOpen(false);
            blocker.proceed();
          }}
        >
          <div>是否退出</div>
        </Modal>
      </>
    );
  };
};

export default WithBlocker;

这就是最基本,只要离开页面就拦截。

tsx 复制代码
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import WithBlocker from '../../feature/RouteInterception/WithBlocker';

const Index = () => {
  return <div>home</div>;
};

export default WithBlocker(Index);

自定义控制是否需要拦截

我们只要控制useBlocker里面函数的返回值就可以控制是否拦截,true拦截,false不拦截。

typescript 复制代码
const blocker: any = useBlocker( ({ currentLocation, nextLocation }) => currentLocation.pathname !== nextLocation.pathname );

使用isIntercept开控制,是否要进行路由拦截

tsx 复制代码
import { Modal } from 'antd';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useBlocker } from 'react-router-dom';

const WithBlocker = (WrappedComponent: React.ComponentType<any>) => {
  return (props: any) => {
    /** 是否出现弹窗*/
    const [isOpen, setIsOpen] = useState(false);
    /** 是否拦截路由*/
    const [isIntercept, setIsIntercept] = useState(true);
    const blocker: any = useBlocker(
      ({ currentLocation, nextLocation }) => isIntercept && currentLocation.pathname !== nextLocation.pathname
    );

    useEffect(() => {
      if (blocker.state === 'blocked') {
        setIsOpen(true);
      } else {
        setIsOpen(false);
      }
    }, [blocker]);

    return (
      <>
        <WrappedComponent {...props} setIsIntercept={setIsIntercept} />

        <Modal
          title="默认弹窗"
          open={isOpen}
          onCancel={() => {
            setIsOpen(false);
            blocker.reset();
          }}
          onOk={() => {
            setIsOpen(false);
            blocker.proceed();
          }}
        >
          <div>是否退出</div>
        </Modal>
      </>
    );
  };
};

export default WithBlocker;

使用场景:修改一个表单,没有发生改变可以直接退出,有修改进行询问。

tsx 复制代码
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import WithBlocker from '../../feature/RouteInterception/WithBlocker';
import { Modal } from 'antd';

const Index = (data: any) => {
  const {  setIsIntercept } = data;

  useEffect(() => {
    setIsIntercept(false);
  }, []);

  return <div>home</div>;
};

export default WithBlocker(Index);

自定义弹窗取消和确定事件

需要自定义事件,我们使用的是高阶组件,事件需要从子元素传递给父元素,那就要使用useImperativeHandle这个hook

tsx 复制代码
import { Modal } from 'antd';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useBlocker } from 'react-router-dom';

const WithBlocker = (WrappedComponent: React.ComponentType<any>) => {
  return (props: any) => {
    const childRef = useRef<{
      proceed: () => Promise<void>;
      reset: () => Promise<void>;
    }>();
    /** 是否出现弹窗*/
    const [isOpen, setIsOpen] = useState(false);
    /** 是否拦截路由*/
    const [isIntercept, setIsIntercept] = useState(true);
    const blocker: any = useBlocker(
      ({ currentLocation, nextLocation }) => isIntercept && currentLocation.pathname !== nextLocation.pathname
    );

    useEffect(() => {
      if (blocker.state === 'blocked') {
        setIsOpen(true);
      } else {
        setIsOpen(false);
      }
    }, [blocker]);

    return (
      <>
        <WrappedComponent ref={childRef} {...props} isOpen={isOpen} setIsIntercept={setIsIntercept} />

        {isIntercept && (
          <Modal
            title="默认弹窗"
            open={isOpen}
            onCancel={() => {
              if (childRef.current) {
                childRef.current
                  .reset()
                  .then(() => {
                    setIsOpen(false);
                    blocker.reset();
                  })
                  .catch(() => {
                    setIsOpen(false);
                    blocker.reset();
                  });
                return;
              }
              setIsOpen(false);
              blocker.reset();
            }}
            onOk={() => {
              if (childRef.current) {
                childRef.current
                  .proceed()
                  .then(() => {
                    setIsOpen(false);
                    blocker.proceed();
                  })
                  .catch(() => {
                    setIsOpen(false);
                    blocker.reset();
                  });
                return;
              }
              setIsOpen(false);
              blocker.proceed();
            }}
          >
            <div>是否退出</div>
          </Modal>
        )}
      </>
    );
  };
};

export default WithBlocker;

使用场景,往往修改完表格退出点击确认,需要调用保存接口,我们使用Promise就可以在请求成功或者失败的时候进行放行和拦截。

tsx 复制代码
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import WithBlocker from '../../feature/RouteInterception/WithBlocker';
import { Modal } from 'antd';

const Index = forwardRef((data: any, ref: any) => {
  const {  setIsIntercept } = data;

  useImperativeHandle(ref, () => ({
    proceed: () => {
      return new Promise((resolve, reject) => {
        //dosomething
        resolve(true);
      });
    },
    reset: () => {
      return new Promise((resolve, reject) => {
        //dosomething
        resolve(true);
      });
    },
  }));

  return <div>home</div>;
});

export default WithBlocker(Index);

自定义弹窗样式

使用isCustomModule来标记是否需要自定义弹窗,blockerComponent来设置自定义的弹窗组件。

tsx 复制代码
import { Modal } from 'antd';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useBlocker } from 'react-router-dom';

const WithBlocker = (WrappedComponent: React.ComponentType<any>) => {
  return (props: any) => {
    const childRef = useRef<{
      proceed: () => Promise<void>;
      reset: () => Promise<void>;
    }>();
    /** 是否出现弹窗*/
    const [isOpen, setIsOpen] = useState(false);
    /** 是否拦截路由*/
    const [isIntercept, setIsIntercept] = useState(true);
    /** 是否使用自定义弹窗*/
    const [isCustomModule, setIsCustomModule] = useState(false);
    const blocker: any = useBlocker(
      ({ currentLocation, nextLocation }) => isIntercept && currentLocation.pathname !== nextLocation.pathname
    );
    /** 自定义弹窗的组件*/
    const [blockerComponent, setBlockerComponent] = useState<any>();
    /** 设置自定义弹窗*/
    const setInterceptionModule = useCallback(
      (Component: React.ComponentType<any>) => {
        if (!isCustomModule) {
          return;
        }

        setBlockerComponent(() => {
          return <Component />;
        });
      },
      [isCustomModule]
    );

    useEffect(() => {
      if (blocker.state === 'blocked') {
        setIsOpen(true);
      } else {
        setIsOpen(false);
      }
    }, [blocker]);

    return (
      <>
        <WrappedComponent
          ref={childRef}
          {...props}
          setIsCustomModule={setIsCustomModule}
          blocker={blocker}
          isOpen={isOpen}
          setInterceptionModule={setInterceptionModule}
          setIsBlocking={setIsOpen}
          setIsIntercept={setIsIntercept}
        />
        {isCustomModule && isIntercept && blockerComponent}
        {!isCustomModule && isIntercept && (
          <Modal
            title="默认弹窗"
            open={isOpen}
            onCancel={() => {
              if (childRef.current) {
                childRef.current
                  .reset()
                  .then(() => {
                    setIsOpen(false);
                    blocker.reset();
                  })
                  .catch(() => {
                    setIsOpen(false);
                    blocker.reset();
                  });
                return;
              }
              setIsOpen(false);
              blocker.reset();
            }}
            onOk={() => {
              if (childRef.current) {
                childRef.current
                  .proceed()
                  .then(() => {
                    setIsOpen(false);
                    blocker.proceed();
                  })
                  .catch(() => {
                    setIsOpen(false);
                    blocker.reset();
                  });
                return;
              }
              setIsOpen(false);
              blocker.proceed();
            }}
          >
            <div>是否退出</div>
          </Modal>
        )}
      </>
    );
  };
};

export default WithBlocker;

使用

tsx 复制代码
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import WithBlocker from '../../feature/RouteInterception/WithBlocker';
import { Modal } from 'antd';

const Index = forwardRef((data: any, ref: any) => {
  const { blocker, isOpen, setInterceptionModule, setIsCustomModule, setIsOpen } = data;

  useEffect(() => {
    setIsCustomModule(true);
    setInterceptionModule(() => {
      return (
        <Modal
          title="自定义弹窗"
          open={isOpen}
          onCancel={() => {
            setIsOpen(false);
            blocker.reset();
          }}
          onOk={() => {
            setIsOpen(false);
            blocker.proceed();
          }}
        >
          <div>sadasd</div>
          <div>sadasd</div>
        </Modal>
      );
    });
  }, [isOpen, blocker]);

  return <div>home</div>;
});

export default WithBlocker(Index);

结语

感兴趣的可以去试试,有更好的写法可以在评论区说说

仓库地址:权限管理和路由拦截 (gitee.com)

相关推荐
hongkid5 分钟前
React Native 如何打包正式apk
javascript·react native·react.js
李少兄8 分钟前
简单讲讲 SVG:前端开发中的矢量图形
前端·svg
前端小万9 分钟前
告别 CJS 库加载兼容坑
前端·前端工程化
恋猫de小郭9 分钟前
Flutter 3.38.1 之后,因为某些框架低级错误导致提交 Store 被拒
android·前端·flutter
JarvanMo13 分钟前
Flutter 需要 Hooks 吗?
前端
光影少年23 分钟前
前端如何虚拟列表优化?
前端·react native·react.js
Moment25 分钟前
一杯茶时间带你基于 Yjs 和 reactflow 构建协同流程图编辑器 😍😍😍
前端·后端·面试
invicinble43 分钟前
对于前端数据的生命周期的认识
前端
PieroPc1 小时前
用FastAPI 后端 和 HTML/CSS/JavaScript 前端写一个博客系统 例
前端·html·fastapi
hunter14501 小时前
2026.1.4 html简单制作
java·前端·笔记·html