跳到主要内容

3 篇博文 含有标签「react-router-dom」

查看所有标签

react-router 6.8.0离开路由确认自定义弹窗

· 阅读需 2 分钟
TransonQ
Front-end Engineer

需求​

自从上一篇博客react-router-dom v6.3 切换路由前确认提到的6版本破坏性更新和暂时废除了相关的api, 目前已经可以升级到 6.8.0 版本了,在此可以使用其提供的新暂定api解决自定义离开路由时候询问用户是否离开。

核心 : unstable_useBlocker​

在react-router-dom 6.8.0的源码里面unstable_useBlocker类型如下:

export declare function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker;

深入查看unstable_useBlocker的返回值类型:

type Blocker = BlockerUnblocked | BlockerBlocked | BlockerProceeding;

// 锁定路由时
interface BlockerBlocked {
state: "blocked";
reset(): void;
proceed(): void;
location: Location;
}
// 未锁定路由时
interface BlockerUnblocked {
state: "unblocked";
reset: undefined;
proceed: undefined;
location: undefined;
}
// 离开路由的过程中
interface BlockerProceeding {
state: "proceeding";
reset: undefined;
proceed: undefined;
location: Location;
}

要点

  • 传入接受一个布尔值
  • 返回值根据三种状态分为三个不同的类型
  • 主要关注在BlockerBlocked时候, reset 和 proceed 是一个明确的函数

组件设计​

import { Modal } from '@shopify/polaris'
import { useEffect } from 'react'
import { unstable_useBlocker } from 'react-router-dom'
import { useToggle } from 'react-use'

type NavBlockerProps = {
isBlocked: boolean
}

export const NavBlocker = ({ isBlocked }: NavBlockerProps) => {
const blocker = unstable_useBlocker(isBlocked)

const [active, toggleActive] = useToggle(false)

const confirmLeave = () => {
toggleActive(false)
blocker.proceed?.()
}
const cancel = () => {
toggleActive(false)
blocker.reset?.()
}

useEffect(() => {
if (blocker.state === 'blocked' || blocker.state === 'proceeding') {
toggleActive()
}
}, [blocker, toggleActive])

useEffect(() => {
if (blocker.state === 'blocked' && !isBlocked) {
toggleActive()
blocker.reset()
}
}, [blocker, isBlocked, toggleActive])

return (
<Modal
title="确认离开"
open={active}
onClose={cancel}
primaryAction={{
content: '确认离开',
destructive: true,
onAction: confirmLeave,
}}
secondaryActions={[{ content: '取消', onAction: cancel }]}
>
<Modal.Section>确认离开当前页面?</Modal.Section>
</Modal>
)
}

react-router-6.5支持可选路由段

· 阅读需 1 分钟
TransonQ
Front-end Engineer

可选路径参数​

在 path 结尾添加?可以使得整个路由参数变为可选的,比如:

<Route path=":lang?/about"> 

将会匹配为:

  • /:lang/about
  • /about
<Route path="/multistep/:widget1?/widget2?/widget3?">

将会匹配为:

  • /multistep
  • /multistep/:widget1
  • /multistep/:widget1/:widget2
  • /multistep/:widget1/:widget2/:widget3

可选的静态路径参数​

<Route path="/home?">

将会匹配为:

  • /
  • /home
<Route path="/fr?/about">
  • /about
  • /fr/about

react-router-dom v6.3 切换路由前确认

· 阅读需 2 分钟
TransonQ
Front-end Engineer

背景​

时间: 2022-09-21

截止此篇博客日期, react-router-dom v6.2 版本里面没有了 <Prompt> , 这里查看官方文档迁移说明

有两个解决方式:

  • 降级 v5 / 6.0.0-alpha.5(有 usePrompt 和 useBlocker)
  • 自定义 hooks

自定义 hooks​

/src/hooks//useBlocker.js
import React from 'react'
import { UNSAFE_NavigationContext } from 'react-router-dom'

export function useBlocker(blocker, when = true) {
const navigator = React.useContext(UNSAFE_NavigationContext).navigator

React.useEffect(() => {
if (!when) return

const unblock = navigator.block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock()
tx.retry()
},
}

blocker(autoUnblockingTx)
})

return unblock
}, [navigator, blocker, when])
}
/src/hooks/useCallbackPrompt.js
import { useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useBlocker } from './useBlocker'

export function useCallbackPrompt(when) {
const navigate = useNavigate()
const location = useLocation()
const [showPrompt, setShowPrompt] = useState(false)
const [lastLocation, setLastLocation] = useState(null)
const [confirmedNavigation, setConfirmedNavigation] = useState(false)

const cancelNavigation = useCallback(() => {
setShowPrompt(false)
}, [])

const handleBlockedNavigation = useCallback(
(nextLocation) => {
if (
!confirmedNavigation &&
nextLocation.location.pathname !== location.pathname
) {
setShowPrompt(true)
setLastLocation(nextLocation)
return false
}
return true
},
// added location to the handleBlockedNavigation hook. The location object changes and the location.pathname value remains outdated.
[confirmedNavigation, location]
)

const confirmNavigation = useCallback(() => {
setShowPrompt(false)
setConfirmedNavigation(true)
}, [])

useEffect(() => {
if (confirmedNavigation && lastLocation) {
navigate(lastLocation.location.pathname)

// Clean-up state on confirmed navigation
setConfirmedNavigation(false)
}
}, [confirmedNavigation, lastLocation])

useBlocker(handleBlockedNavigation, when)

return [showPrompt, confirmNavigation, cancelNavigation]
}

使用方式​

const [isEditing, setIsEditing] = usestate(false)

// 业务逻辑,

const [showPrompt, confirmNavigation, cancelNavigation] = useCallbackPrompt(isEditing)

// 自行封装一个离开当前路有前的 modal , 假设为 BeforeLeaveModal

<BeforeLeaveModal
showDialog={showPrompt} // boolean 此 modal 是否显示
cancelNavigation={cancelNavigation} // function 点击取消离开路由回调
confirmNavigation={confirmNavigation} // function 点击确认离开路由的回调
/>

续更​

2022-10-31

由于在 react-router-dom v6.4 更新后 UNSAFE_NavigationContext被废弃, 暂时无法使用上述的钩子. 如果想继续停留在 v6 版本, 建议先让版本保持在 v6.3.0