前言

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)。
image.png

总结

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


Awbeci
3.1k 声望212 粉丝

Awbeci