前言
Next.js13推出了一个新的模式:App Router
而之前的模式:Pages Router
下的shallow
浅路由和router.events.on
路由事件监听都移除了,官方也给出了解决方案,不过好像社区对其意见也挺大的,非常不满意,于是我自己根据官方方案改写来实现,希望能帮助大家。
实现浅路由
这是pages router
的实现:
router.push(`/url`, undefined, {shallow: true})
在app router
模式下想实现shallow
浅路由,可以使用原生js的方法,如下所示:
const params = new URLSearchParams(routerSearchParams.toString())
params.set('id', your-id)
window.history.pushState(null, '', `?${params)}`)
实现顶部ProcessBar功能
之前我在Pages Router
下我也写过一篇文章教大家如何实现,这次我们来看看App Router
模式下如何实现。
首先,根据上次的文章,我们来改写一下。
改写之前我们来想两个问题:
1、router.push('url')时就应该触发loading开始事件
2、页面url变化后,loading结束
如何解决这两个问题呢?
我的想法是:
1)我们添加一个全局的HOC组件,这样我们可以在HOC组件中加上我们router.push
的代码,这样在你想要跳转的页面应用这个HOC组件就可以实现跳转前的监听了
2)我们监听pathname和params的变化,当变化之后就可以结束loading了
下面就是具体的实现了
1、添加依赖包
"@tanem/react-nprogress": "^5.0.51",
2、添加globalLoading.js文件
代码如下所示:
import React from 'react';
import { useNProgress } from '@tanem/react-nprogress';
const GlobalLoading = ({ isRouteChanging, }) => {
const { animationDuration, isFinished, progress } = useNProgress({
isAnimating: isRouteChanging,
})
return (
<>
<style jsx>{`
.container {
opacity: ${isFinished ? 0 : 1};
pointerevents: none;
transition: opacity ${animationDuration}ms linear;
}
.bar {
background: #2BC3D5;
height: 2px;
left: 0;
margin-left: ${(-1 + progress) * 100}%;
position: fixed;
top: 0;
transition: margin-left ${animationDuration}ms linear;
width: 100%;
z-index: 1031;
}
.spinner {
box-shadow: 0 0 10px #2BC3D5, 0 0 5px #2BC3D5;
display: block;
height: 100%;
opacity: 1;
position: absolute;
right: 0;
transform: rotate(3deg) translate(0px, -4px);
width: 100px;
}
`}</style>
<div className="container">
<div className="bar">
<div className="spinner" />
</div>
</div>
</>
)
};
export default GlobalLoading;
3、在layout.js文件中引入global.js文件
代码如下所示:
import GlobalLoading from "@/components/globalLoading";
return <div>
<GlobalLoading isRouteChanging={isRouteChanging} key={loadingKey}/>
...其它代码
</div>
4、在redux中设置两个属性:isRouteChanging和loadingKey
const initialState = {
isRouteChanging: false,
loadingKey: 0
}
reducers: {
setProgress: (state, action) => {
state.isRouteChanging = action.payload.isRouteChanging
if (action.payload.loadingKey){
state.loadingKey = action.payload.loadingKey
}
}
}
5、添加withRouter高阶组件
// HOC 示例
import {useDispatch, useSelector} from "react-redux";
import {useRouter} from "next/navigation";
import {setProgress} from "@/lib/slices/systemSlice";
export function withRouter(WrappedComponent) {
// 新组件,包含增强功能
return function EnhancedComponent(props) {
const dispatch = useDispatch()
const router = useRouter()
const pathname = usePathname()
const {loadingKey} = useSelector(state => state.system)
// 在此可以添加额外的逻辑,比如处理props、订阅等
const newProps = {
...props,
// 添加自定义属性
routerPush: (url) => {
// 相同的地址不需要跳转
if (url !== pathname){
dispatch(setProgress({
isRouteChanging: true,
loadingKey: loadingKey ^ 1
}))
router.push(url)
}
}
};
// 渲染传入的组件,并传递新的props
return <WrappedComponent {...newProps} />;
};
}
这里我们添加了routerPush
方法,里面实现了修改redux属性值,当isRouteChanging=true
时loadingBar就会开始加载,并且使用router.push
实现路由跳转
6、添加路由加载结束的监听事件
useEffect(() => {
// 监听路由变化,如果变化了则执行此代码,停止加载process bar
dispatch(setProgress({
isRouteChanging: false
}))
}, [pathname, query])
7、在页面A中演示
const A = ({routerPush}) => {
...
routerPush(url)
...
}
export default withRouter(A)
这样就完成了全部工作,app router
就能像之前pages router
一样拥有自己的ProcessBar(LoadingBar)。
总结
1、为什么我把router.push
放在HOC组件?因为HOC组件相当于全局定义了routerPush
方法,这样不用再在每个组件中定义了,比较方便!
2、注意loadingKey
的使用,当路由开始加载的时候我们设置新的loadingKey
,而当结束的时候不要变,因为当前加载和结束的bar就是当前这个loadingKey
,如果你结束的时候也变化了,那么你就会发现loadingbar
不会全部加载完全。
引用
using-the-native-history-api
48110
next/navigation Next 13: useRouter events? #41934
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。