javascript Next.js getServerSideProps显示加载

kr98yfug  于 2023-02-07  发布在  Java
关注(0)|答案(7)|浏览(167)

我在pages/post/index.js中使用getServerSideProps:

import React from "react";
import Layout from "../../components/Layout";

function Post({ post }) {
  console.log("in render", post);
  return (
    <Layout title={post.name}>
      <pre>{JSON.stringify(post, undefined, 2)}</pre>
    </Layout>
  );
}

export async function getServerSideProps({ query }) {
  return fetch(
    `${process.env.API_URL}/api/post?id=${query.id}`
  )
    .then(result => result.json())
    .then(post => ({ props: { post } }));
}

export default Post;

当我直接加载/post/2时,它按预期工作,但当我通过单击链接从/posts转到/post/2时:

<Link
  as={`/post/${post.id}`}
  href={`/post?id=${post.id}`}
>

看起来2秒钟内什么都没有发生(API延迟),然后内容显示。我可以在网络选项卡中看到_next/data/development/post/9.json正在被fetchNextData加载。
当我使用next/Link从一条路线移动到另一条路线时,我想显示一个加载微调器,但是我在getServerSideProps上找不到任何允许我这样做的文档。
当我直接转到/post/:id时,我希望从服务器端获取数据,并得到一个完全呈现的页面(工作),但当我转到另一个路由时,数据应该从客户端获取(工作)。我想有一个加载指示器,而不是有UI冻结了数据请求的持续时间。

cig3rfwq

cig3rfwq1#

这是一个使用钩子的example
pages/_app.js

import Router from "next/router";

    export default function App({ Component, pageProps }) {
      const [loading, setLoading] = React.useState(false);
      React.useEffect(() => {
        const start = () => {
          console.log("start");
          setLoading(true);
        };
        const end = () => {
          console.log("finished");
          setLoading(false);
        };
        Router.events.on("routeChangeStart", start);
        Router.events.on("routeChangeComplete", end);
        Router.events.on("routeChangeError", end);
        return () => {
          Router.events.off("routeChangeStart", start);
          Router.events.off("routeChangeComplete", end);
          Router.events.off("routeChangeError", end);
        };
      }, []);
      return (
        <>
          {loading ? (
            <h1>Loading...</h1>
          ) : (
            <Component {...pageProps} />
          )}
        </>
      );
    }
bmp9r5qi

bmp9r5qi2#

您可以在_app.js中使用nprogress

import NProgress from 'nprogress';
import "nprogress/nprogress.css";
import Router from 'next/router';

NProgress.configure({
  minimum: 0.3,
  easing: 'ease',
  speed: 800,
  showSpinner: false,
});

Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());

或动态导入到_app.js以减少捆绑包大小
ProgessBar.js

import Router from 'next/router';
import NProgress from 'nprogress';
import "nprogress/nprogress.css";

NProgress.configure({
    minimum: 0.3,
    easing: 'ease',
    speed: 500,
    showSpinner: false,
});

Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());

export default function () {
    return null;
}

_app.js

import dynamic from 'next/dynamic';
const ProgressBar = dynamic(() => import('components/atoms/ProgressBar'), { ssr: false });

const App = () => {
   ...
   return <>
       ...
       <ProgressBar />
   </>
}

Ps:如果你想改变进度条的颜色,你可以在全局css中覆盖,类似这样的东西

#nprogress .bar {
    background: #6170F7 !important;
    height: 3px !important;
}
iibxawm4

iibxawm43#

可以创建自定义挂接:
usePageLoading.ts

import Router from 'next/router';
import { useEffect, useState } from 'react';

export const usePageLoading = () => {
  const [isPageLoading, setIsPageLoading] = useState(false);

  useEffect(() => {
    const routeEventStart = () => {
      setIsPageLoading(true);
    };
    const routeEventEnd = () => {
      setIsPageLoading(false);
    };

    Router.events.on('routeChangeStart', routeEventStart);
    Router.events.on('routeChangeComplete', routeEventEnd);
    Router.events.on('routeChangeError', routeEventEnd);
    return () => {
      Router.events.off('routeChangeStart', routeEventStart);
      Router.events.off('routeChangeComplete', routeEventEnd);
      Router.events.off('routeChangeError', routeEventEnd);
    };
  }, []);

  return { isPageLoading };
};

然后在App组件中使用它:_app.js

import Router from "next/router";
import { usePageLoading } from './usePageLoading';

export default function App({ Component, pageProps }) {
  const { isPageLoading } = usePageLoading();

  return (
    <>
      {isPageLoading ? (
        <h1>Loading...</h1>
      ) : (
        <Component {...pageProps} />
      )}
    </>
   );
}
0sgqnhkj

0sgqnhkj4#

简单地向Post添加一个组件级加载状态如何(相对于为每个路由更改添加一个应用级加载器,因为一些路由更改可能不需要服务器端呈现)。
当相关的查询参数改变时,将isLoading状态设置为true,在本例中是post id,而当props(在本例中是post数据)更新时,将状态设置为false。
按照以下方针:
页数/帖子/index.js:

import React from "react";
import Layout from "../../components/Layout";
import { useRouter } from 'next/router';

function Post({ post }) {
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);
  
  // loading new post
  useEffect(()=> {
   setIsLoading(true);
  }, [router.query?.id]);
  
  // new post loaded
  useEffect(()=> {
   setIsLoading(false)
  }, [post]);

  return (
    <>
    {isLoading ? (
      <h1>Loading...</h1>
     ) : (
      <Layout title={post.name}>
       <pre>{JSON.stringify(post, undefined, 2)}</pre>
      </Layout>
    )}
    </> 
  );
}

export async function getServerSideProps({ query }) {
  return fetch(
    `${process.env.API_URL}/api/post?id=${query.id}`
  )
    .then(result => result.json())
    .then(post => ({ props: { post } }));
}

export default Post;
368yc8dk

368yc8dk5#

**Here is how I did it in NextJs with Material UI and nprogress**

import '../styles/globals.css';
import { useEffect, useState } from 'react';
import Router from 'next/router';
import NProgress from 'nprogress';

import { useStyles } from '../src/utils';
import { CircularProgress } from '@material-ui/core';

NProgress.configure({ showSpinner: false });

function MyApp({
  Component,
  pageProps
 
}) {
  const classes = useStyles();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) jssStyles.parentElement.removeChild(jssStyles);

    const start = () => {
      console.log('start');
      NProgress.start();
      setLoading(true);
    };
    const end = () => {
      console.log('findished');
      NProgress.done();
      setLoading(false);
    };

    Router.events.on('routeChangeStart', start);
    Router.events.on('routeChangeComplete', end);
    Router.events.on('routeChangeError', end);
    return () => {
      Router.events.off('routeChangeStart', start);
      Router.events.off('routeChangeComplete', end);
      Router.events.off('routeChangeError', end);
    };
  }, []);
  return (
    <>
       {loading ? (
          <div className={classes.centered}>
            <CircularProgress size={25} color='primary' />
          </div>
        ) : (
        <Component {...pageProps} />
       )}
    </>
  );
}

export default MyApp;

结果:

pkmbmrz7

pkmbmrz76#

除了前面的答案之外,您还可以在事件处理程序中接收一个url参数,并使用这些参数来筛选出您想要加载状态的路由和不想要的路由。

function MyApp({ Component, pageProps: { ...pageProps } }: AppProps) {
    const router = useRouter();

    const [isLoading, setIsLoading] = React.useState(false);

    React.useEffect(() => {
        const handleChangeStart = (url: string) => {
            if (url === "<root_to_show_loading>") {
                setIsLoading(true);
            }
        };

        const handleChangeEnd = (url: string) => {
            if (url === "<root_to_show_loading") {
                setIsLoading(false);
            }
        };

        router.events.on("routeChangeStart", handleChangeStart);
        router.events.on("routeChangeComplete", handleChangeEnd);
        router.events.on("routeChangeError", handleChangeEnd);
    }, []);

    return (
        <main>
            {isLoading ? <LoadingSpinner /> : <Component {...pageProps} />}
        </main>
    );

}

export default MyApp;
t9aqgxwy

t9aqgxwy7#

进度条,如NProgress,90行代码(与NProgress v0.2.0相比,为470行. js +70行. css):

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 = 400
} = {}) {
  // 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,
      'You need to ensure 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 = 400;

// 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: 2,
        zIndex: 1091 // $zindex-toast + 1 => always visible
      }}
    >
      <div
        className="progress-bar"
        style={{
          width: `${width}%`,
          //transition: 'none',
          transition: `width ${transitionSpeed}ms ease`
        }}
      />
    </div>
  ) : null;
}

使用方法:

// 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

相关问题