Next.js - 动态路由的浅层路由
Next.js - Shallow routing with dynamic routes
在 Next.js 中尝试使用动态路由进行浅层路由时,页面会刷新并被浅层忽略。好像很多人对此很困惑。
假设我们从下一页开始
router.push(
'/post/[...slug]',
'/post/2020/01/01/hello-world',
{ shallow: true }
);
然后我们转到另一个博客post:
router.push(
'/post/[...slug]',
'/post/2020/01/01/foo-bar',
{ shallow: true }
);
这个不会触发浅路由,浏览器刷新,为什么?
在代码库中很清楚这是一个功能:
// If asked to change the current URL we should reload the current page
// (not location.reload() but reload getInitialProps and other Next.js stuffs)
// We also need to set the method = replaceState always
// as this should not go into the history (That's how browsers work)
// We should compare the new asPath to the current asPath, not the url
if (!this.urlIsNew(as)) {
method = 'replaceState'
}
我可以使用 window.history.pushState()
手动实现同样的效果,尽管这当然不是一个好主意:
window.history.pushState({
as: '/post/2020/01/01/foo-bar',
url: '/post/[...slug]',
options: { shallow: true }
}, '', '/post/2020/01/01/foo-bar');
由于 Next.JS 的内部 API 随时可能发生变化...我可能会遗漏一些东西...但为什么 shallow 在这种情况下被忽略了?好像很奇怪。
好的,所以这个答案基于我的第一个答案(和问题澄清):
#1 问题是: This does not trigger shallow routing, the browser refreshes, why?
=> 检查我的最后一个答案以了解 next.js
如何处理文件结构。
#2 如果你想处理无限制的 URL 参数:
您必须遵循以下文件结构:
.
├── pages
│ ├── post
│ │ └──[...slug].js // or [...slug]/index.js
│ ├── about.js
│ └── index.js
├── public
│ └── ...
├── .gitignore
├── package.json
├── package-lock.json
└── README.md
[...slug].js:
export default function Post({ post }) {
return (
<div>
<h1>Post is here{}</h1>
{JSON.stringify(post)}
</div>
);
}
export async function getStaticPaths() {
return {
paths: [
{ params: { slug: ["*"] } },
],
fallback: true
};
}
export async function getStaticProps(context) {
console.log("SLUG: ", context);
const { params = [] } = context || {};
const res = await fetch(`https://api.icndb.com/jokes/${params && params.slug[0] || 3}`);
const post = await res.json()
return { props: { post } }
}
./index.js:
import Link from 'next/link';
import Router from 'next/router'
export default function Home() {
const handleClick = e => {
e.preventDefault();
Router.push(
'/post/[...slug]',
'/post/2/2/4',
{ shallow: true }
);
}
return (
<div className="container">
<div>
<Link href="/about">
<a>About</a>
</Link>
</div>
<hr />
<div>
<Link
href={`/post/[...slug]?slug=`}
as={`/post/`}
>
<a>
<span>
Post:
</span>
</a>
</Link>
<hr />
<div>
<button onClick={handleClick}>
Push me
</button>
</div>
</div>
</div>
)
}
任何深度/post/any/url/long/will_return_slugfile
再次!!! 在next.js
你必须注意路由中的文件系统结构。 (正如我在上一个回答中提到的)
pages/post/[[...slug]].js will match /post, /post/a, /post/a/b, and so on.
我认为这是预期的行为,因为您正在路由到新页面。如果您只是更改查询参数,浅层路由应该可以工作,例如:
router.push('/?counter=10', undefined, { shallow: true })
但是你正在使用路由参数
router.push(
'/post/[...slug]',
'/post/2020/01/01/hello-world',
{ shallow: true }
);
这表明您正在路由到一个新页面,它会卸载当前页面,加载新页面,并等待数据获取,即使我们要求进行浅层路由,这在此处的文档中提到Shallow routing caveats.
顺便说一下,您说“页面已刷新”,但 router.push
即使在没有 shallow: true
的情况下使用也不会刷新页面。毕竟这是一个单页应用程序。它只是呈现新页面并运行 getStaticProps
、getServerSideProps
或 getInitialProps
.
浅路由使您能够在不丢失状态的情况下更新路径名或查询参数,即仅更改路由状态。但条件是,您必须在同一页面上(如文档警告图片所示).
为此,您必须将第二个参数作为未定义的传递给 router.push 或 Router.push。否则,将在卸载第一页后加载新页面,您将无法获得预期的行为。
我的意思是浅层路由在路径名更改方面将不再起作用,这是因为我们选择加载新页面而不仅仅是 url。希望这有帮助
例子
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// Always do navigations after the first render
router.push('/post/[...slug]', undefined, { shallow: true })
}, [])
useEffect(() => {
// The pathname changed!
}, [router.pathname ])
}
export default Page
实际上,根据文档描述,我认为您错误地使用了这个 push
函数。请参阅以下来自 docs 的代码:
import Router from 'next/router'
Router.push(url, as, options)
文档说:
url
- The URL to navigate to. This is usually the name of a page
as
- Optional decorator for the URL that will be shown in the browser. Defaults to url
options
- Optional object with the following configuration options:
shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false
这意味着你应该将确切的 URL 作为第一个参数传递,如果你想将它显示为修饰名称,则传递第二个参数,第三个只需传递选项,因此你应该像下面这样写:
router.push(
'/post/2020/01/01/hello-world',
undefined,
undefined
);
对于 shallow routing 你应该使用确切的例子:
router.push('/?counter=10', undefined, { shallow: true });
其实用你的代码,你创建了一个新的路由,刷新是不可避免的。
在 Next.js 中尝试使用动态路由进行浅层路由时,页面会刷新并被浅层忽略。好像很多人对此很困惑。
假设我们从下一页开始
router.push(
'/post/[...slug]',
'/post/2020/01/01/hello-world',
{ shallow: true }
);
然后我们转到另一个博客post:
router.push(
'/post/[...slug]',
'/post/2020/01/01/foo-bar',
{ shallow: true }
);
这个不会触发浅路由,浏览器刷新,为什么?
在代码库中很清楚这是一个功能:
// If asked to change the current URL we should reload the current page
// (not location.reload() but reload getInitialProps and other Next.js stuffs)
// We also need to set the method = replaceState always
// as this should not go into the history (That's how browsers work)
// We should compare the new asPath to the current asPath, not the url
if (!this.urlIsNew(as)) {
method = 'replaceState'
}
我可以使用 window.history.pushState()
手动实现同样的效果,尽管这当然不是一个好主意:
window.history.pushState({
as: '/post/2020/01/01/foo-bar',
url: '/post/[...slug]',
options: { shallow: true }
}, '', '/post/2020/01/01/foo-bar');
由于 Next.JS 的内部 API 随时可能发生变化...我可能会遗漏一些东西...但为什么 shallow 在这种情况下被忽略了?好像很奇怪。
好的,所以这个答案基于我的第一个答案(和问题澄清):
#1 问题是: This does not trigger shallow routing, the browser refreshes, why?
=> 检查我的最后一个答案以了解 next.js
如何处理文件结构。
#2 如果你想处理无限制的 URL 参数:
您必须遵循以下文件结构:
.
├── pages
│ ├── post
│ │ └──[...slug].js // or [...slug]/index.js
│ ├── about.js
│ └── index.js
├── public
│ └── ...
├── .gitignore
├── package.json
├── package-lock.json
└── README.md
[...slug].js:
export default function Post({ post }) {
return (
<div>
<h1>Post is here{}</h1>
{JSON.stringify(post)}
</div>
);
}
export async function getStaticPaths() {
return {
paths: [
{ params: { slug: ["*"] } },
],
fallback: true
};
}
export async function getStaticProps(context) {
console.log("SLUG: ", context);
const { params = [] } = context || {};
const res = await fetch(`https://api.icndb.com/jokes/${params && params.slug[0] || 3}`);
const post = await res.json()
return { props: { post } }
}
./index.js:
import Link from 'next/link';
import Router from 'next/router'
export default function Home() {
const handleClick = e => {
e.preventDefault();
Router.push(
'/post/[...slug]',
'/post/2/2/4',
{ shallow: true }
);
}
return (
<div className="container">
<div>
<Link href="/about">
<a>About</a>
</Link>
</div>
<hr />
<div>
<Link
href={`/post/[...slug]?slug=`}
as={`/post/`}
>
<a>
<span>
Post:
</span>
</a>
</Link>
<hr />
<div>
<button onClick={handleClick}>
Push me
</button>
</div>
</div>
</div>
)
}
任何深度/post/any/url/long/will_return_slugfile
再次!!! 在next.js
你必须注意路由中的文件系统结构。 (正如我在上一个回答中提到的)
pages/post/[[...slug]].js will match /post, /post/a, /post/a/b, and so on.
我认为这是预期的行为,因为您正在路由到新页面。如果您只是更改查询参数,浅层路由应该可以工作,例如:
router.push('/?counter=10', undefined, { shallow: true })
但是你正在使用路由参数
router.push(
'/post/[...slug]',
'/post/2020/01/01/hello-world',
{ shallow: true }
);
这表明您正在路由到一个新页面,它会卸载当前页面,加载新页面,并等待数据获取,即使我们要求进行浅层路由,这在此处的文档中提到Shallow routing caveats.
顺便说一下,您说“页面已刷新”,但 router.push
即使在没有 shallow: true
的情况下使用也不会刷新页面。毕竟这是一个单页应用程序。它只是呈现新页面并运行 getStaticProps
、getServerSideProps
或 getInitialProps
.
浅路由使您能够在不丢失状态的情况下更新路径名或查询参数,即仅更改路由状态。但条件是,您必须在同一页面上(如文档警告图片所示).
为此,您必须将第二个参数作为未定义的传递给 router.push 或 Router.push。否则,将在卸载第一页后加载新页面,您将无法获得预期的行为。
我的意思是浅层路由在路径名更改方面将不再起作用,这是因为我们选择加载新页面而不仅仅是 url。希望这有帮助
例子
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// Always do navigations after the first render
router.push('/post/[...slug]', undefined, { shallow: true })
}, [])
useEffect(() => {
// The pathname changed!
}, [router.pathname ])
}
export default Page
实际上,根据文档描述,我认为您错误地使用了这个 push
函数。请参阅以下来自 docs 的代码:
import Router from 'next/router'
Router.push(url, as, options)
文档说:
url
- The URL to navigate to. This is usually the name of a pageas
- Optional decorator for the URL that will be shown in the browser. Defaults tourl
options
- Optional object with the following configuration options: shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false
这意味着你应该将确切的 URL 作为第一个参数传递,如果你想将它显示为修饰名称,则传递第二个参数,第三个只需传递选项,因此你应该像下面这样写:
router.push(
'/post/2020/01/01/hello-world',
undefined,
undefined
);
对于 shallow routing 你应该使用确切的例子:
router.push('/?counter=10', undefined, { shallow: true });
其实用你的代码,你创建了一个新的路由,刷新是不可避免的。