前言
用户在编辑表单时,经常编辑一半中途切换页面,我们需要进行路由拦截,对用户进行询问,是否需要保存当前信息。
React
中实现路由拦截使用useBlocker
就可以了。本文将使用高阶组件对路由拦截进行封装来扩展功能。
- 最基本的路由拦截,默认拦截弹窗
- 自定义控制是否需要拦截
- 自定义弹窗取消和确定事件
- 自定义弹窗样式
后面有代码仓库地址
正文
基本的路由拦截
编写一个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);
结语
感兴趣的可以去试试,有更好的写法可以在评论区说说