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

前言

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

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)

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css