乐闻世界logo
搜索文章和话题

Nextjs 如何在加载页面时添加 loading ?

5 个月前提问
21 天前修改
浏览次数274

6个答案

1
2
3
4
5
6

在 Next.js 中,当页面切换时添加加载 loading 指示器可以使用几种不同的方法。以下是几个步骤和示例,说明如何实现这一功能:

使用路由事件监听

Next.js 的 Router 对象提供了路由事件,可以在路由开始和结束时触发函数。我们可以利用这些事件来展示和隐藏一个加载指示器。

jsx
import { useState, useEffect } from 'react'; import Router from 'next/router'; import NProgress from 'nprogress'; // 这是一个轻量级的进度条库 // 在组件内部可以这样使用: function MyApp({ Component, pageProps }) { const [loading, setLoading] = useState(false); useEffect(() => { const handleStart = () => setLoading(true); // 开始导航时设置加载状态 const handleComplete = () => setLoading(false); // 完成导航时解除加载状态 Router.events.on('routeChangeStart', handleStart); Router.events.on('routeChangeComplete', handleComplete); Router.events.on('routeChangeError', handleComplete); // 加载发生错误时也解除加载状态 return () => { Router.events.off('routeChangeStart', handleStart); Router.events.off('routeChangeComplete', handleComplete); Router.events.off('routeChangeError', handleComplete); }; }, []); return ( <div> {loading && ( <div className="loader">Loading...</div> // 这里可以自定义加载指示器的样式 )} <Component {...pageProps} /> </div> ); }

使用 nprogress 库可以使得加载指示器看起来更加专业和平滑。它会在页面顶部显示一个细长的进度条。使用时,你需要引入 nprogress 的样式文件,这通常在你的 styles.css 文件或者是 App 组件中完成。

jsx
// 在 _app.js 或者其他全局组件中: import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; // 这是 nprogress 的默认样式 import Router from 'next/router'; NProgress.configure({ showSpinner: false }); // 你可以选择关闭加载旋转器 Router.events.on('routeChangeStart', () => NProgress.start()); Router.events.on('routeChangeComplete', () => NProgress.done()); Router.events.on('routeChangeError', () => NProgress.done());

使用自定义的 App 组件

自定义的 App 组件能够在路由变化时增加一个全局的加载状态。这里的原理和使用路由事件监听类似,但是它让你能够更容易地管理状态和传递 props。

jsx
import App from 'next/app'; import NProgress from 'nprogress'; import Router from 'next/router'; class MyApp extends App { constructor(props) { super(props); this.state = { loading: false }; Router.events.on('routeChangeStart', () => { this.setState({ loading: true }); NProgress.start(); }); Router.events.on('routeChangeComplete', () => { this.setState({ loading: false }); NProgress.done(); }); Router.events.on('routeChangeError', () => { this.setState({ loading: false }); NProgress.done(); }); } render() { const { Component, pageProps } = this.props; return ( <> {this.state.loading && <div className="loader">Loading...</div>} <Component {...pageProps} /> </> ); } } export default MyApp;

在上面的例子中,我们设置了一个 loading 状态,并在路由事件中相应地更新这个状态。然后,我们可以在渲染方法中使用这个状态来决定是否显示加载指示器。此外,我们还引入了 NProgress 来显示一个平滑的进度条。

结论

通过监听 Next.js 的路由事件,我们可以在页面切换时轻松地添加加载指示器。你可以使用简单的状态和条件渲染来实现,也可以使用如 NProgress 这样的库来展示一个更为专业的加载效果。在任何情况下

2024年6月29日 12:07 回复

使用新的 hook api,我就是这样做的。

shell
function Loading() { const router = useRouter(); const [loading, setLoading] = useState(false); useEffect(() => { const handleStart = (url) => (url !== router.asPath) && setLoading(true); const handleComplete = (url) => (url === router.asPath) && setLoading(false); router.events.on('routeChangeStart', handleStart) router.events.on('routeChangeComplete', handleComplete) router.events.on('routeChangeError', handleComplete) return () => { router.events.off('routeChangeStart', handleStart) router.events.off('routeChangeComplete', handleComplete) router.events.off('routeChangeError', handleComplete) } }) return loading && (<div>Loading....{/*I have an animation here*/}</div>); }

现在<Loading/>每当路线发生变化时就会显示...我使用react-spring为其设置动画,但您可以使用您喜欢的任何库来执行此操作。

您甚至可以更进一步,通过修改获取 的handleStart和方法来修改组件何时显示。handleComplete``url

2024年6月29日 12:07 回复

nextjs-progressbar包使这变得超级简单。在 Next.js _app.js(或 .jsx.tsx等)中,只需在默认导出中的任意位置添加`,如下所示:

shell
import NextNProgress from 'nextjs-progressbar'; export default function App({ Component, pageProps }) { return ( <> <NextNProgress /> <Component {...pageProps} />; </> ); }
2024年6月29日 12:07 回复

为什么不使用nprogress如下_app.js

shell
import React from 'react'; import Router from 'next/router'; import App, { Container } from 'next/app'; import NProgress from 'nprogress'; NProgress.configure({ showSpinner: publicRuntimeConfig.NProgressShowSpinner }); Router.onRouteChangeStart = () => { // console.log('onRouteChangeStart triggered'); NProgress.start(); }; Router.onRouteChangeComplete = () => { // console.log('onRouteChangeComplete triggered'); NProgress.done(); }; Router.onRouteChangeError = () => { // console.log('onRouteChangeError triggered'); NProgress.done(); }; export default class MyApp extends App { ... }

链接到nprogress

您还需要包含样式文件。如果将 css 文件放在static目录中,则可以按如下方式访问样式:

shell
<link rel="stylesheet" type="text/css" href="/static/css/nprogress.css" />

确保 CSS 在所有页面中都可用...

它将适用于您所有更改的路线。

2024年6月29日 12:07 回复

NProgress 的新更新:

shell
import Router from 'next/router' import Link from 'next/link' import Head from 'next/head' import NProgress from 'nprogress' Router.events.on('routeChangeStart', (url) => { console.log(`Loading: ${url}`) NProgress.start() }) Router.events.on('routeChangeComplete', () => NProgress.done()) Router.events.on('routeChangeError', () => NProgress.done()) export default function App({ Component, pageProps }) { return ( <> <Head> {/* Import CSS for nprogress */} <link rel="stylesheet" type="text/css" href="/nprogress.css" /> </Head> <Component {...pageProps} /> </> ) }

如果您使用 Tailwind CSS,请从此处复制代码: https: //unpkg.com/nprogress@0.2.0/nprogress.css并将代码粘贴到全局 CSS 文件

如果您想禁用微调器,请在文件中添加以下代码_app.tsx/jsx并从 CSS 中删除微调器样式。

shell
NProgress.configure({ showSpinner: false });

来源链接:

2024年6月29日 12:07 回复

像 NProgress 这样的进度条有 90 行代码(而 NProgress v0.2.0 是 470 行 .js + 70 行 .css):

shell
import { useEffect, useReducer, useRef } from 'react'; import { assert } from './assert'; import { wait } from './wait'; import { getRandomInt } from './getRandomNumber'; let waitController: AbortController | undefined; // https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047 export function useProgressBar({ trickleMaxWidth = 94, trickleIncrementMin = 1, trickleIncrementMax = 5, dropMinSpeed = 50, dropMaxSpeed = 150, transitionSpeed = 600 } = {}) { // https://stackoverflow.com/a/66436476 const [, forceUpdate] = useReducer(x => x + 1, 0); // https://github.com/facebook/react/issues/14010#issuecomment-433788147 const widthRef = useRef(0); function setWidth(value: number) { widthRef.current = value; forceUpdate(); } async function trickle() { if (widthRef.current < trickleMaxWidth) { const inc = widthRef.current + getRandomInt(trickleIncrementMin, trickleIncrementMax); // ~3 setWidth(inc); try { await wait(getRandomInt(dropMinSpeed, dropMaxSpeed) /* ~100 ms */, { signal: waitController!.signal }); await trickle(); } catch { // Current loop aborted: a new route has been started } } } async function start() { // Abort current loops if any: a new route has been started waitController?.abort(); waitController = new AbortController(); // Force the show the JSX setWidth(1); await wait(0); await trickle(); } async function complete() { assert( waitController !== undefined, 'Make sure start() is called before calling complete()' ); setWidth(100); try { await wait(transitionSpeed, { signal: waitController.signal }); setWidth(0); } catch { // Current loop aborted: a new route has been started } } function reset() { // Abort current loops if any waitController?.abort(); setWidth(0); } useEffect(() => { return () => { // Abort current loops if any waitController?.abort(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { start, complete, reset, width: widthRef.current }; } import { useRouter } from 'next/router'; import { useEffect } from 'react'; import { useProgressBar } from './useProgressBar'; const transitionSpeed = 600; // https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047 export function RouterProgressBar( props?: Parameters<typeof useProgressBar>[0] ) { const { events } = useRouter(); const { width, start, complete, reset } = useProgressBar({ transitionSpeed, ...props }); useEffect(() => { events.on('routeChangeStart', start); events.on('routeChangeComplete', complete); events.on('routeChangeError', reset); // Typical case: "Route Cancelled" return () => { events.off('routeChangeStart', start); events.off('routeChangeComplete', complete); events.off('routeChangeError', reset); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return width > 0 ? ( // Use Bootstrap, Material UI, Tailwind CSS... to style the progress bar <div className="progress fixed-top bg-transparent rounded-0" style={{ height: 3, // GitHub turbo-progress-bar height is 3px zIndex: 1091 // $zindex-toast + 1 => always visible }} > <div className="progress-bar" style={{ width: `${width}%`, //transition: 'none', transition: `width ${width > 1 ? transitionSpeed : 0}ms ease` }} /> </div> ) : null; }

如何使用:

shell
// pages/_app.tsx import { AppProps } from 'next/app'; import Head from 'next/head'; import { RouterProgressBar } from './RouterProgressBar'; export default function App({ Component, pageProps }: AppProps) { return ( <> <Head> <title>My title</title> <meta name="description" content="My description" /> </Head> <RouterProgressBar /> <Component {...pageProps} /> </> ); }

更多信息:https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047

2024年6月29日 12:07 回复

你的答案