NextJS 如何使 router.push 不是浅层路由

时间:2021-05-12 02:05:14

标签: reactjs next.js

我有一个搜索页面,当用户搜索某物(例如椅子)时,它会使用 /search-page/chairs

推送到路线 router.push("/search-page/" + searchQuery);

然而,出现的一个问题是,当用户在搜索页面内进行另一次搜索时,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 中的查询,然后强制刷新页面,就像回调函数一样。 谢谢

2 个答案:

答案 0 :(得分:1)

如果有人想知道,我设法通过使用 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,并调用功能根据用户输入更新产品。

答案 1 :(得分:0)

我使用的一种方法是将返回的 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 填充任何给定 subreddit 的内容。

如果您不得不在打字稿环境中使用 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
    }));
}