【问题标题】:NextJS how to make router.push not a shallow routeNextJS 如何让 router.push 不是浅路由
【发布时间】:2021-08-02 09:25:33
【问题描述】:

我有一个搜索页面,当用户搜索某物时,例如椅子,它会使用router.push("/search-page/" + searchQuery); 推送到路由/search-page/chairs

但是,出现的一个问题是,当用户在搜索页面内进行另一次搜索时,URL 中的查询、地址栏中的查询会更新,但页面不会刷新,因此不会更新产品。 我试过router.push("/search-page/" + searchQuery, undefined, {shallow: false}); 试图强制router.push 不浅,但这没有用。 我也试过了

componentDidUpdate(prevProps){
  if(this.state.router.asPath != prevProps.router.asPath){
   updateProducts()
 }
}

检查组件更新时是否之前的 URL 不等于当前的 URL。 但是,这个 if 语句似乎也不起作用。某些内容未正确更新。 由于我使用的是 NextJS,也许我可以对 getServerSideProps 做些什么?但我不太熟悉 getServerSideProps 或 getIntialProps 的工作原理。 或者也许有一种方法可以更新 URL 中的查询,然后在之后强制刷新页面,就像回调函数一样。 谢谢

【问题讨论】:

标签: reactjs next.js


【解决方案1】:

我使用的一种方法是将返回的search query JSX 包装在一个备忘录中,或者将其记忆以在更改时更新状态。这是我几个月前建立的一个 subreddit 搜索项目的代码

@/components/Searchbar.tsx
import { FC, useEffect, useMemo, useState } from 'react';
import cn from 'classnames';
import css from './Searchbar.module.css';
import { useRouter } from 'next/router';
import { Input } from '../UI';
import { filterQuery } from '@/lib/helpers';

interface Props {
    className?: string;
    id?: string;
}

const Searchbar: FC<Props> = ({ className, id = 'r/' }) => {
    const router = useRouter();
    const [value, setValue] = useState('');
    useEffect(() => {
        // router.prefetch(url, as)
        router.prefetch('/r/[display_name]', `/r/${router.query}`, {
            priority: true
        });
    }, [value]);

    return useMemo(
        () => (
            <div
                className={cn(
                    'relative bg-accents-1 text-base w-full transition-colors duration-150',
                    className
                )}
            >
                <label className='sr-only' htmlFor={id}>
                    /r/ - search by subreddit name
                </label>
                <div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
                    <span className='text-gray-100 font-semibold sm:text-base'>
                        /r/
                    </span>
                </div>
                <Input
                    id={id}
                    name={id}
                    onChange={setValue}
                    className={css.input}
                    defaultValue={
                        router && router.query ? (router.query.q as string) : ''
                    }
                    onKeyUp={e => {
                        e.preventDefault();

                        if (e.key === 'Enter') {
                            const q = e.currentTarget.value;

                            router.push(
                                {
                                    pathname: `/r/${q}`,
                                    query: q ? filterQuery({ q }) : {}
                                },
                                undefined,
                                { shallow: true }
                            );
                        }
                    }}
                />
                <div className={css.iconContainer}>
                    <svg
                        className={css.icon}
                        fill='rgb(229, 231, 235)'
                        viewBox='0 0 20 20'
                    >
                        <path
                            fillRule='evenodd'
                            clipRule='evenodd'
                            d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'
                        />
                    </svg>
                </div>
            </div>
        ),
        []
    );
};

export default Searchbar;
对应@/components/Searchbar.module.css文件
.input {
    @apply bg-redditSearch px-3 pl-7 py-2 appearance-none w-full transition duration-150 ease-in-out pr-10 text-gray-100 font-semibold;

    @screen sm {
        min-width: 300px;
        @apply text-lg;
    }
    @screen md {
        min-width: 600px;
        @apply text-lg;
    }
}

.input:focus {
    @apply outline-none text-gray-100;
}

.iconContainer {
    @apply absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none;
}

.icon {
    @apply h-5 w-5;
}

我在 useEffect 挂钩中预取目标路由,以增强生产环境中的用户体验;它也会增强开发中的用户体验,但预取目前仅适用于产品。

然后,有一个@/pages/api 路由处理用户输入的值,如下所示:

@/pages/api/snoosearch.ts
import { Subreddit, Listing } from 'snoowrap';
import { NextApiRequest, NextApiResponse } from 'next';
import { r } from '@/lib/snoo-config';

export type SearchSubreddits = {
    subreddit: Listing<Subreddit> | never[];
    found: boolean;
};

export default async function (
    req: NextApiRequest,
    res: NextApiResponse<SearchSubreddits>
) {
    const { q } = req.query;
    console.log(q);
    const data = q
        ? await r.searchSubreddits({
                query: (q as string) ?? 'snowboarding',
                count: 10,
                limit: 3
          })
        : [];
    res.statusCode = 200;
    res.setHeader(
        'Cache-Control',
        'public, s-maxage=1200, stale-while-revalidate=600'
    );

    return res.status(200).json({
        subreddit: data,
        found: true
    });
};

因此,这个 lambda 注入一个动态子目录的 getStaticPaths 来处理实时静态路径生成(以及如果使用国际化路由,还可以选择本地化)并通过 ISR 填充任何给定子目录的内容。

如果您不得不在 typescript 环境中使用 Jest 在 Nextjs 中编写测试,您可能熟悉必须通过在测试环境中复制下一个路由器来模拟它。这帮助我了解了很多关于路由器内部的知识:

TLDR -- 提交用户搜索的搜索栏的记忆更新状态,这会强制进行非浅层渲染,这应该可以解决您的问题

模拟 Nextjs 路由器
// Mocks useRouter
type PrefetchOptions = {
    priority?: boolean;
    locale?: string | false;
};

const useRouter = jest.spyOn(
    require('next/router'),
    'useRouter'
);

/**
 * mockNextUseRouter
 * Mocks the useRouter React hook from Next.js on a test-case by test-case basis
 */
export function mockNextUseRouter(props: {
    route: string;
    prefetch(
        url: string,
        asPath?: string,
        options?: PrefetchOptions
    ): Promise<void>;
    pathname: string;
    query: string;
    asPath: string;
}) {
    useRouter.mockImplementationOnce(() => ({
        route: props.route,
        prefetch: props.prefetch,
        pathname: props.pathname,
        query: props.query,
        asPath: props.asPath
    }));
}

【讨论】:

    【解决方案2】:

    如果有人想知道,我设法通过使用 window.location 弄清楚

    componentDidUpdate() {
    
      var str = window.location.pathname;
      var n = str.lastIndexOf('/');
      var result = str.substring(n + 1);
      if(result != this.state.searchQuery){//if the current URL doesn't match the URL stored in the state (which is the previous url before making a new search)
        //grab the query from the current URL, and update the searchQuery state with that query from the current URL
        this.updateProducts(result);
      }
    }
    

    在下面的代码中,当组件更新时,它使用子字符串从 window.location.pathname 中获取查询参数。然后它将 window.location 中的查询参数与this.state.searchQuery 中的查询参数进行比较,如果它们不相同,则使用从 window.location.pathname 中提取的参数更新this.state.searchQuery,并调用函数来更新基于用户输入的产品。

    【讨论】:

      猜你喜欢
      • 2021-07-05
      • 2022-11-04
      • 2021-04-01
      • 1970-01-01
      • 2021-10-17
      • 1970-01-01
      • 2021-04-06
      • 1970-01-01
      • 2021-12-06
      相关资源
      最近更新 更多