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

前言

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

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)

相关推荐
裴嘉靖1 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824264 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang1 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
西洼工作室2 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
广州华水科技2 小时前
北斗形变监测传感器在水库安全中的应用及技术优势分析
前端
开发者如是说2 小时前
Compose 开发桌面程序的一些问题
前端·架构
旺代2 小时前
Token 存储与安全防护
前端
洋不写bug3 小时前
html实现简历信息填写界面
前端·html