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

前言

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

文末有 已成功帮助500多人拿到前端offer的文章 !!

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

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

后面有代码仓库地址

正文

基本的路由拦截

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

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;

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

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不拦截。

const blocker: any = useBlocker( ({ currentLocation, nextLocation }) => currentLocation.pathname !== nextLocation.pathname );

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

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;

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

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

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} 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就可以在请求成功或者失败的时候进行放行和拦截。

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来标记是否需要自定义弹窗,并将isOpenblocker等传递给子元素。

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
    );

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

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

        {!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;


我们可以根据接收到的isOpen控制自定义弹窗的显示与否

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, setIsCustomModule, setIsOpen } = data;

  useEffect(() => {
    setIsCustomModule(true);
  }, []);

  return (
    <>
      <div>home</div>
      <Modal
        title="自定义弹窗"
        open={isOpen}
        onCancel={() => {
          setIsOpen(false);
          blocker.reset();
        }}
        onOk={() => {
          setIsOpen(false);
          blocker.proceed();
        }}
      >
        <div>sadasd</div>
        <div>sadasd</div>
      </Modal>
    </>
  );
});

export default WithBlocker(Index);


结语

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

程序员玫玫:堪称2024最强的前端面试场景题,已帮助512人成功拿到offer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值