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

Posted

技术标签:

【中文标题】useRouter/withRouter 在第一次渲染时收到未定义的查询【英文标题】:useRouter/withRouter receive undefined on query in first render 【发布时间】:2020-07-17 07:53:57 【问题描述】:

我的动态路线有问题。它看起来像这样

[lang]/abc

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

【问题讨论】:

【参考方案1】:

我发现了一些东西:

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]);

【讨论】:

这应该是公认的答案。这正是引入 isReady 的目的。 与其他可能会产生奇怪副作用的棘手解决方案相比,这就像一个魅力!谢谢 刚刚通过控制台记录 useRouter 函数发现了这一点。这样解决了很多资源,而不是做s-s-r!我强烈推荐这个 s-s-r 渲染只是路径参数。【参考方案2】:

在第一次渲染时无法获取查询值。

静态优化页面在没有提供路由参数的情况下进行水合。例如。 query 是一个空对象 ()。

水化后,Next.js 将填充查询对象。

Next.js 10.0.5 及更高版本

useEffect 钩子中使用 router.isReady 来检查参数是否准备好。示例见@dy063 的回答。

在 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 Issue - Add a "ready" to Router returned by "useRouter"

【讨论】:

tnx 很多 :))))) 你应该小心这种方法,因为它有问题,因为asPathroute 可以相等,这并不能反映路由器的实际准备情况。只有当您的网址有始终查询时,您才可以依赖它。【参考方案3】:

在 NextJS 9+ 中,确保路由参数立即可用于页面组件的一种方法是从传递给 getServerSideProps()context arg 获取它们并作为 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>;
;

【讨论】:

在下一个 9.4.4 上尝试过这个,我收到一个错误,因为 context.propsundefined,不能序列化为 JSON。希望它会包含查询。 context.props 上下文没有props,看看这个例子更好。你的页面道具会收到你从getServerSideProps返回的 props: 嗨@Mycolaos,答案实际上是利用context.params - 而不是context.props。刚刚在 Next 10 上重新验证了该方法。参考:nextjs.org/docs/basic-features/… 嗨@HunterMedney,我的评论是针对马特哈金斯(和八,因为他似乎同意他)。您可以看到我实际上是在建议您更好地查看您的示例。很抱歉造成混乱。 使用getServerSideProps 指示Next.js 在每次请求时在服务器端预渲染一个页面。意味着它不是静态优化和缓存的。仅当您愿意牺牲性能时才使用它。例如。如果您必须在第一次渲染之前获取数据。【参考方案4】:

这是一个很好的问题,我花了几天时间才弄清楚最好的方法是什么。

我个人找到了三种可行的解决方案来解决验证动态路由路径参数甚至只是一般的路由路径参数的问题。

三个解决方案是

    s-s-r(不推荐)[Next >= 10] 使用路由器 中间件 [需要接下来的 12 个]

在我的示例中,a 将使用需要重置令牌的路由,否则它应该被重定向。

s-s-r 首先使用getServerSideProps 进行服务器端渲染。 Vercel 建议使用 s-s-r 作为最后的手段,我强烈建议在可能的情况下不要使用 s-s-r(字节时间和成本)。

我们建议尝试增量静态生成或客户端获取,看看它们是否符合您的需求。 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 对此也有很好的回答,希望我不建议使用像他的示例中那样的钩子,而是使用 isReady。 https://***.com/a/58182678/4918639

【讨论】:

【参考方案5】:

对于 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]);


【讨论】:

【参考方案6】:

我解决了我在 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;

【讨论】:

【参考方案7】:

✅ 类组件爱好者

更好的方法是使用 this.props.router.events.on 方法监听这个 routeChangeComplete 的专用事件,如果您使用类组件,则在 componentDidMount 内 -

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 变成truerouter.query 对象有数据时。

【讨论】:

【参考方案8】:

Next.js

这是一个很好的解决方法,我从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]);

【讨论】:

以上是关于useRouter/withRouter 在第一次渲染时收到未定义的查询的主要内容,如果未能解决你的问题,请参考以下文章

在第一次出现时拆分

$window.addEventListener 仅在第一次工作,但下一次在病房中无法通过 $watch 工作

UICollisionBehavior 仅在第一次添加后有效

ViewController 在第一次使用后没有改变

MediaPlayer 在第一次运行时不工作

Swift:UITextField 在第一次被点击时被立即关闭