useRouter/withRouter在第一次渲染时未接收到查询的定义

86

我遇到了动态路由的问题。它看起来像这样:

[lang]/abc

我尝试从 [lang] 获取查询值,但当我使用 useRouter/withRouter 时,我在页面渲染2-3次期间得到查询(第一次我得到 query.lang = undefined)。有可能在1次渲染中获得或使用任何技术吗?

输入图像描述

10个回答

168

我发现了一些东西:

isReady: boolean - 路由字段是否已在客户端更新并准备就绪,仅应在useEffect方法内使用,不用于在服务器上进行有条件的呈现。 https://nextjs.org/docs/api-reference/next/router#router-object

代码如下:

const router = useRouter();
useEffect(()=>{
    if(!router.isReady) return;

    // codes using router.query

}, [router.isReady]);

18
这应该是被接受的答案。这正是isReady被引入的原因所在。 - Marnix.hoh
1
与其他可能会产生奇怪副作用的棘手解决方案相比,这个方法非常有效!谢谢。 - Diego Isco
刚刚通过控制台记录useRouter函数发现了这个。 这解决了很多资源问题,而不是使用SSR! 我强烈建议仅对路径参数进行SSR渲染。 - Alex Dunlop
它说“应该只在useEffect方法内使用”,这是否意味着我们不能在componentDidUpdate中使用它?我无法使其在基于类的组件中工作。 - nomadus

65

在初始渲染期间无法获取query值。

静态优化页面在没有路由参数的情况下被注入,因此query是一个空对象({})。

页面被注入后,Next.js将填充query

Next.js 10.0.5 及更高版本

要确定路由参数是否准备就绪,您可以在useEffect钩子内使用router.isReady。有关示例,请参见@doxylee提供的答案。

在 Next.js 10.0.5 之前

在动态路由的初始呈现中,router.asPathrouter.route相等。一旦query对象可用,router.asPath就会反映它。

asPath被更改后,您可以在useEffect钩子中依赖于查询值。

const router = useRouter();

useEffect(() => {
  if (router.asPath !== router.route) {
    // router.query.lang is defined
  }
}, [router])

GitHub问题 - 在“useRouter”返回的路由器中添加一个“ready”


1
你应该小心使用这种方法,因为它存在漏洞,因为asPathroute可能相等,这并不反映路由器的实际准备情况。只有当你的URL始终具有查询时,才可以依赖它。 - Mycolaos
1
有关自动静态优化和查询对象的相关文档:https://nextjs.org/docs/advanced-features/automatic-static-optimization#how-it-works。 - juliomalves

15
在NextJS 9+中,确保路由参数立即可用于页面组件的一种方法是从传递给getServerSideProps()context参数获取它们,并将其作为props传递给组件。

对于像[id].js这样的页面,

export function getServerSideProps(context) {
  return {
    props: {params: context.params}
  };
}

export default ({params}) => {
  const {id} = params;
  return <div>You opened page with {id}</div>;
};

context.props 上下文没有 props,请查看示例。您的页面属性将从 getServerSideProps 返回您返回的 { props: {} } - Mycolaos
嗨@Mycolaos,答案实际上是利用context.params而不是context.props。我刚在Next 10上重新验证了这种方法。参考:https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering - Hunter Medney
嗨@Mycolaos,我的错 - 非常感谢您的澄清! - Hunter Medney
5
使用getServerSideProps指令可以让Next.js在每个请求时在服务器端预渲染页面。这意味着它没有进行静态优化和缓存。只有在必须在首次渲染之前获取数据时才使用它,因为这会牺牲性能。 - Nikolai Kiselev
1
我不建议仅用于检查参数,这会导致SSR,而Vercel建议谨慎使用。 请使用useRouter:isReady布尔值。 - Alex Dunlop
显示剩余2条评论

9

这是一个非常好的问题,让我花了几天时间才找到最佳方法。

我个人发现解决动态路由路径参数验证问题或仅仅是路由路径参数的三种可行方案。

这三种解决方案是:

  1. SSR(不建议)[Next >= 10]
  2. useRouter
  3. 中间件 [Next 12必需]

在我的示例中,我将使用需要重置令牌或应该重定向的路线。

SSR 首先是使用getServerSideProps进行服务器端渲染。 Vercel建议只在万不得已时使用SSR,强烈建议在可以使用时不要使用SSR(字节时间和成本)。

我们建议尝试增量静态生成或客户端获取并查看它们是否符合您的需求。 https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation

但是,在您需要时,比如说有一些服务器端API验证调用,您需要验证查询参数。

export const getServerSideProps = async (context) => {
  const { token } = context.query;

  if (!token) {
    return {
      redirect: {
        permanent: false,
        destination: "/",
      }
    }
  }

  return {
    props: {}
    // props: { token }
    // You could do this either with useRouter or passing props
  }
}

useRouter 其次是最简单的 useRouter。当我第一次使用它时,我遇到了一个问题,即在 nextjs/react 静态生成时,查询参数会为空。幸运的是,useRouter 有isReady属性!

import Router, { useRouter } from "next/router";

const { query, isReady } = useRouter();

useEffect(() => {
  if (!isReady) return;
  if (!query.token) {
    Router.push("/")
  }
}, [isReady])

中间件,在我看来,这是一种以清晰的方式分离功能的方法。我发现这个方法基于vercel的一个示例。强烈推荐阅读一些示例以找到最佳实践。 https://github.com/vercel/examples/

import { NextResponse, NextRequest } from 'next/server'

export async function middleware(req) {
    const { pathname, searchParams } = req.nextUrl

    if (pathname == '/reset-token') {
        const index = searchParams.findIndex(x => x.key === "token")
        // You could also add token validation here.
        if (!index) {
          return NextResponse.redirect('/')
        }
    }
    return NextResponse.next()
}

这里是一个仓库,其中包含一些很酷的查询参数过滤。 这是一种更加柔和的方法,而不是强制重定向。 https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter

Nico也对此有一个很好的回答,但我不建议像他的示例中那样使用钩子,而是使用isReadyhttps://dev59.com/alMH5IYBdhLWcg3w0DO9#58182678


2

针对类组件爱好者

更好的方法是使用this.props.router.events.on方法,在生命周期函数componentDidMount中监听专门的事件routeChangeComplete,如果你正在使用类组件 -

routeChangeComplete = () => {
    // this WILL have valid query data not empty {}
    console.log(this.props.router.query);
};
componentDidMount() {
    this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
    this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}

参考:https://nextjs.org/docs/api-reference/next/router#routerevents

routeChangeComplete: 当路由完全变化时触发。

实际上,当 isReady 变为 true 或者 router.query 对象有数据时会触发该事件。


1
我试过了,它不起作用! - Nima
@Nima,你能分享一些可重现的代码吗? - Shubham P
1
我刚刚复制了你的代码,但是它记录为空!(我使用了withRouter进行导出) - Nima
我自己找到了解决方案。我会发布一个答案。 - Nima
@Nima 最后你找到解决办法了吗?对我来说,这在 Next.js 12 中不起作用。this.props.router.query 是一个空对象,如果等待路由准备好(router.isReady),它才能正常工作。 - Ray_Poly
@Nima 最终你找到解决办法了吗?对我来说,这在 Next.js 12 上不起作用,this.props.router.query 是一个空对象,如果等到路由准备好了再执行(router.isReady),它就能正常工作。 - undefined

2

对于NextJS版本12.0.8:

如果您从页面导出名为getServerSideProps(服务器端渲染)的函数,则Next.js将使用由getServerSideProps返回的数据在每个请求上预渲染此页面。

=异步函数

参考:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops

只需将该异步函数放在页面上即可通知NextJS其存在。在组件的预渲染阶段,路由器的查询对象将为空。

isReady: boolean - 路由器字段是否已在客户端更新并准备好使用。仅应在useEffect方法内部使用,而不用于在服务器上有条件地呈现。

参考:https://nextjs.org/docs/api-reference/next/router

解决方案:

import { useRouter } from 'next/router';

const Fn = () =>{

const router = useRouter();
const { param } = router.query;

const fetchData = async () => {
   await fetch();
} 

 useEffect(() => {
   fetchCat();
}, [router.isReady]);

}

在获取之前,我会考虑检查 router.isReady 的值是否为 true。 - Ray_Poly
在进行获取操作之前,我建议先检查一下路由器的状态是否已经准备就绪。 - undefined

1
我解决了需要在HOC组件中使用的问题。 我使用withRouter(withLocale(Comp))进行包装,并在HOC中创建条件。
export default function withLocale(WrappedPage) {
    const WithLocale = ({ router, ...props }) => {
        const { lang } = router.query;
        if (!lang || !isLocale(lang)) {
            return <Error statusCode={404} />;
        }
        return (
            <LocaleProvider lang={lang}>
                <WrappedPage {...props} />
            </LocaleProvider>
        );
    };
   return WithLocale;
}

1
在Next 13中,router.query已被移除(由于新的应用程序路由器),并被useSearchParams();钩子替代,因此您可以像这样使用它:
import { useSearchParams } from "next/navigation";
const query = useSearchParams();

0

Next.js <= 10.0.5

这是一个好的解决方法,我从this comment中发现了它。

添加useQuery.ts辅助文件

// useQuery.js
import { useRouter } from 'next/router';

// Resolves query or returns null
export default function useQuery() {
  const router = useRouter();
  const ready = router.asPath !== router.route;
  if (!ready) return null;
  return router.query;
}

使用方法

// In your components (instead of useRouter)
const query = useQuery();

useEffect(() => {
  if (!query) {
    return;
  }
  console.log('my query exists!!', query);
}, [query]);

0

类组件 | 2022年12月16日 | React JS 18.2.0 | Next JS 13.0.6

我找到了那些想要使用类组件的人的答案。这实际上是无处可寻的!享受吧!

您将在render()中添加if(this.props.router.isReady)并在条件中包含return

.
.
import { withRouter } from 'next/router';
import { Component } from 'react';

class User extends Component {

    ...
    
    render() { 
        if(this.props.router.isReady){ // Add this condition and include return ()

            // Do anything

            console.log(this.props.router.query) // Log 'query' on first render

            return (
                <div>
                    <SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
                </div>
            );
            
        }
    }
}

export default withRouter(User);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接