0%

Next.js快速入门

一、简介

Next.js 是一个用于 React 应用的极简的服务端渲染框架。框架中集成了 Webpack,Babel 等一系列 React 相关的工具并进行了默认的配置。因此省去了复杂的配置过程,实现了一键搭建开发环境和打包构建。同时提供了自定义配置接口,可以在默认配置的基础上对工具进行自定义配置,满足个性化需求。

二、基本用法

安装

使用 npm 安装: npm install next –save

为了方便的使用 next 提供的命令,把命令写在 package.json 文件的 scripts 中:

1
2
3
4
5
6
7
8
{
"scripts": {
"dev": "next", // 运行开发服务器,并监控源代码,具备hod reload功能
"build": "next build", // 以生产模式打包代码
"start": "next start" // 启动Next服务器,可以自定义服务器和端口
"init": "next init" // 初始化项目,创建基础的文件夹和index页面文件
}
}

之后,在项目的根目录下创建 pages 文件夹和 static 文件夹,分别用来放对应的页面资源和静态资源。

Note:也可以使用 npm run init 命令自动生成。

运行

如果使用 npm run init 命令的话,现在 pages 文件夹下已经有了 index.js 文件,如果是手动创建 pages 文件夹的话,现在在该文件下创建一个 index.js 文件,内容为:

1
export default () => <p>Hello, world</p>

接着执行 npm run dev 命令并在浏览器中打开 http://localhost:3000。

现在,就得到了一个采用服务端渲染的极简 React 应用,这个应用还实现了自动代码分割,保证每个页面只会加载自身的依赖,不会有依赖冗余。

Next 的核心就是 pages 和 static 文件夹。其中 pages 文件夹用于存放每个页面的顶层组件,static 用于存放项目中的静态资源。

Next 会将 pages 中的文件结构自动映射为对应的路由结构,例如现在该文件夹下有两个文件:pages/index.js 和 pages/about.js。则对应的路由分别为/和/about。并且支持多级目录,例如 page/foo/bar.js 对应的路由为/foo/bar。

static 文件夹用来存放静态文件,例如现在有一个图片文件 static/image.png,使用的时候引用/static/image.png 就可以了:

1
2
3
export default () => (
<img src="/static/image.png" />
)

打包完成后,Next 会在项目根目录生成一个.next 文件夹,其中的两个文件夹 dist 和 bundles,dist 文件夹中存放着编译后的源代码,用于服务端渲染。bunldes 文件夹中存放着 pages 中每个页面打包后的整体代码的 JSON 格式。在应用的初始页面,会使用 dist 文件夹中的代码进行服务端渲染,而其他使用路由到达的页面,则将 bundles 文件夹中的对应 JSON 格式的代码返回客户端执行渲染。

Next 的出现大大简化了 React 应用开发的配置和构建工作,使开发者能够专注于组件的开发,而不需要在 Webpack,Babel 等工具上花费过多的精力。基于简单的文件系统,就可以创建包含路由功能和服务端渲染的 React 应用。需要注意的是:创建的应用中只有初始页面采用服务端渲染,其他通过路由操作到达的页面均为客户端渲染。

组件

Next 对 React 组件的 getInitialProps 生命周期方法做了改造,传入一个上下文对象,该对象在服务端渲染和客户端渲染时,具有不同的属性:

  • req: HTTP 请求对象(服务端渲染独有)
  • res: HTTP 响应对象(服务端渲染独有)
  • pathname: URL 中的路径部分
  • query:URL 中的查询字符串部分解析出的对象
  • err:错误对象,如果在渲染时发生了错误
  • xhr:XMLHttpRequest 对象(客户端渲染独有)

因此,可以在组件的 getInitialProps 方法中处理上下文对象,控制传入组件的 props 数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
export default class extends React.Component {
static async getInitialProps ({ req }) {
return req
? { userAgent: req.headers['user-agent'] }
: { userAgent: navigator.userAgent }
}
render () {
return <div>
Hello World {this.props.userAgent}
</div>
}
}

上面的例子根据是否有 req 对象来判断是服务端渲染还是客户端渲染,然后采用对应的方式取得用户代码数据并传入组件的 props 中。

获取数据

组件的 getInitialProps 还可以用来获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { Component } from 'react';
import 'isomorphic-fetch';

export default class extends Component {
static async getInitialProps() {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
const json = await res.json();
return {
stars: json.stargazers_count
};

}

render() {
return <div>{this.props.stars}</div>
}
}

需要注意的一点是,getInitialProps 方法执行完毕之后,才会执行组件的 render 方法。这也就导致了如果网络状况不佳的情况下,会出现长时间的等待。并且只有每个页面的顶层组件的 getInitialProps 会被执行,所以想在子组件中获取数据的话只能在其他生命周期函数例如 componentDidMount 配合组件的 state 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default class extends Component {
constructor(props) {
super(props);
this.state = {
stars: 0
}
}

async componentDidMount() {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
const json = await res.json();
this.setState({
stars: json.stargazers_count
});
}

render() {
return <div>{this.state.stars}</div>
}
}

三、CSS

NEXT 组件中声明 CSS,目前主要有两种方式:

  1. 内嵌 CSS
  2. CSS-in-JS

内嵌(Built-in)CSS

Next 采用的内嵌 CSS 方案是 styled-jsx 库,也是 Next 所推荐的 CSS 声明方式。优点是具有组件级的独立作用域,避免了样式污染问题。并且支持完整的 CSS 功能,如:hover 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react'

export default () => (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
div:hover {
background: blue;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
</div>
)

CSS-in-JS

Next 支持多种 CSS-in-JS 方案,例如基本的在组件 style 属性中写样式:

1
2
3
4
5
6
7
8
import React from 'react'

export default () => (
<div style={{color: red}}>
Hello world
</div>
)

还有其他的 CSS-in-JS 库,可以根据自己的需要和喜好灵活选择。

四、路由系统

Next 中提供了一个组件,用来实现路由功能。例如,我们的应用有两个页面:pages/index.js 和 pages/about.js,想要实现页面跳转,只需要:

1
2
3
4
5
// pages/index.js
import Link from 'next/link'
export default () => (
<div>Click <Link href="/about"><a>here</a></Link> to read more</div>
)
1
2
3
4
// pages/about.js
export default () => (
<p>Welcome to About!</p>
)
组件的工作流程和浏览器很相似:
  1. 获取新的组件
  2. 如果新组件定义了 getInitialProps,则获取数据,如果发生错误,则渲染_error.js
  3. 步骤 1,2 完成之后,执行 pushState 并渲染新组件
    每个顶层组件中还会传入一个 url 对象,提供了几个路由相关的方法:
  • pathname:String-当前 URL 不包括查询字符串的 path 部分
  • query:Object-当前 URL 中查询字符串解析成的对象
  • back-后退
  • push(url, as=url)-使用传入的 url(字符串)执行 pushState 操作
  • replace(url, as=url)-使用传入的 url(字符串)执行 replaceState 操作 注意:push 和 replace 方法中的第二个参数 as 为可选项,只有在服务端配置了自定义路由才有作用。

Router 对象

除了使用组件之外,Next 还提供了一个 Router 对象满足命令式写法的需要:

1
2
3
4
5
import Router from 'next/router'

export default () => (
<div>Click <span onClick={() => Router.push('/about')}>here</span> to read more</div>
)

与 url 对象相比,Router 对象多了一个 route 属性,值为当前的路由。 需要注意的是,Router 对象中的属性和方法仅可以在客户端部分使用,服务端渲染的页面无法使用,否则会报错。

路由事件

Router 对象还提供了三个路由事件方法:

  • routeChangeStart(url) - 路由变化开始时触发
  • routeChangeComplete(url) - 路由变化完成时触发
  • routeChangeError(err, url) - 路由变化发生错误时触发 如果使用 Router.push(url, as)或相似的方法并传入了 as 参数,则路由事件方法中的 url 参数值为 as 的值,否则,url 参数的值是路由跳转目标的 URL

注意:与 Router 对象中其他的属性和方法不同的是,这三个路由事件方法可以在服务端渲染的页面使用。

监听路由变化:

1
2
3
Router.onRouteChangeStart = (url) => {
console.log('App is changing to: ', url)
}

取消监听:

1
Router.onRouteChangeStart = null;

如果路由加载取消了(连续快速点击两个链接),就会触发 routeChangeError 的回调,传入的 err 参数中将包含一个 cancelled 属性,值为 true。

1
2
3
4
5
Router.onRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
}

五、预获取页面

Next 提供了一个基于 ServiceWorker 实现的,具有预获取页面功能的模块:next/prefetch。 使用预获取功能,可以使 APP 预加载那些可能到达的页面,提升网站的使用体验和性能。当然,前提是你的浏览器必须支持 ServiceWorker。并且预获取功能只支持应用内的页面,不支持外部链接。

组件

next/prefetch 模块也提供了一个具有预获取功能的组件,代替路由系统中的组件,使用方法一致:

1
2
3
4
5
6
7
8
9
10
11
import Link from 'next/prefetch'

export default () => (
<nav>
<ul>
<li><Link href='/'><a>Home</a></Link></li>
<li><Link href='/about'><a>About</a></Link></li>
<li><Link href='/contact'><a>Contact</a></Link></li>
</ul>
</nav>
)

此外预获取功能可以精确控制到每个标签,使用 prefetch 属性来控制开关:

1
<Link href='/contact' prefetch={false}><a>Home</a></Link>

prefetch 方法

和路由器一样,预获取模块也提供了一个 prefetch 方法,用来方便命令式的写法:

1
2
3
4
5
6
7
8
9
10
11
12
import { prefetch } from 'next/prefetch'
export default ({ url }) => (
<div>
<a onClick={ () => setTimeout(() => url.pushTo('/dynamic'), 100) }>
100ms后执行路由跳转
</a>
{
预获取页面
prefetch('/dynamic')
}
</div>
)

自定义配置

如果默认的配置无法满足需要的话,Next 还提供了诸多的自定义配置接口,可以根据自己的需求灵活配置。

自定义服务器和路由

默认的服务器和路由系统可能无法满足需要,比如,我需要把/a 的路由解析到 pages/b.js,把/b 的路由解析到 pages/a.js,此时,就需要通过自定义,手动控制页面渲染来实现,在项目根目录下创建 server.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// server.js

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl

if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
})
.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})

你可以选择自己喜欢的服务端框架,express 或者 koa 等,进行自定义。

自定义

Next 提供了组件,可以自定义页面标签中的内容。每个组件都可以在内部自定义的内容:

1
2
3
4
5
6
7
8
9
10
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)

每个页面组件只需要定义本页面需要的内容,并且对于相同的标签,例如。会按照组件渲染的顺序,后定义的覆盖先定义的内容。</p> <h3 id="自定义-1"><a href="#自定义-1" class="headerlink" title="自定义"></a>自定义<Document></h3><p>在前面的例子中,服务端渲染时,所有的页面我们只需要写内容组件,这是因为使用了默认的<Document>模板。当然,可以自定义自己的服务端渲染模板。首先,创建 pages/_document.js 文件,写上内容:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">// pages/_document.js</span><br><span class="line">import Document, { Head, Main, NextScript } from 'next/document'</span><br><span class="line"></span><br><span class="line">export default class MyDocument extends Document {</span><br><span class="line"> static async getInitialProps (ctx) {</span><br><span class="line"> const props = await Document.getInitialProps(ctx)</span><br><span class="line"> return { ...props, customValue: 'hi there!' }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render () {</span><br><span class="line"> return (</span><br><span class="line"> <html></span><br><span class="line"> <Head></span><br><span class="line"> <style>{`body { margin: 0 } /* custom! */`}</style></span><br><span class="line"> </Head></span><br><span class="line"> <body className="custom_class"></span><br><span class="line"> {this.props.customValue}</span><br><span class="line"> <Main /></span><br><span class="line"> <NextScript /></span><br><span class="line"> </body></span><br><span class="line"> </html></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>其中的 ctx 对象与其他组件中的 getInitialProps 方法中收到的参数一样,只不过多了一个额外的方法:renderPage()。</p> <h3 id="自定义错误处理"><a href="#自定义错误处理" class="headerlink" title="自定义错误处理"></a>自定义错误处理</h3><p>Next 中,有一个默认组件 error.js,负责处理 404 或者 500 这种错误。当然,你也可以自定义一个_error.js 组件覆盖默认的错误处理组件:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">// _error.js</span><br><span class="line"></span><br><span class="line">import React from 'react'</span><br><span class="line">export default class Error extends React.Component {</span><br><span class="line"> static getInitialProps ({ res, xhr }) {</span><br><span class="line"> const statusCode = res ? res.statusCode : (xhr ? xhr.status : null)</span><br><span class="line"> return { statusCode }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render () {</span><br><span class="line"> return (</span><br><span class="line"> <p>{</span><br><span class="line"> this.props.statusCode</span><br><span class="line"> ? `An error ${this.props.statusCode} occurred on server`</span><br><span class="line"> : 'An error occurred on client'</span><br><span class="line"> }</p></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="自定义配置-1"><a href="#自定义配置-1" class="headerlink" title="自定义配置"></a>自定义配置</h3><p>相对 Next 进行自定义配置的话,可以在项目根目录下创建一个 next.config.js</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// next.config.js</span><br><span class="line"></span><br><span class="line">module.exports = {</span><br><span class="line"> /* 自定义配置 */</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="自定义-Webpack-配置"><a href="#自定义-Webpack-配置" class="headerlink" title="自定义 Webpack 配置"></a>自定义 Webpack 配置</h3><p>在创建好的 next.config.js 文件中,可以扩展 Webpack 配置:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">module.exports = {</span><br><span class="line"> webpack: (config, { dev }) => {</span><br><span class="line"></span><br><span class="line"> // 修改config对象</span><br><span class="line"></span><br><span class="line"> return config</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>该函数接收默认的 Webpack config 对象作为参数,返回修改后的 config 对象。需要注意的是,next.config.js 文件会被直接执行,因为只能使用本机安装的 Node.js 所支持的 JS 语法。</p> <p><strong>警告:不建议</strong>在自定义 Webpack 配置中添加 loader 以支持新的文件类型!因为只有客户端渲染的代码会经过打包,而服务端执行的是源代码,并没有经过 Webpack 处理,因此新的 loader 对服务端渲染不起作用。所以最好是使用 Babel 插件来处理新的文件类型,因为无论是客户端还是服务端渲染的代码,都会经过 Babel 处理。</p> <h3 id="自定义-Babel-配置"><a href="#自定义-Babel-配置" class="headerlink" title="自定义 Babel 配置"></a>自定义 Babel 配置</h3><p>自定义 Babel 配置,只需要在项目根目录下创建.babelrc 文件,因为自定义配置会覆盖默认配置,而不是扩展默认配置。因此需要把 next preset 写到.babelrc 中。例如:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "presets": [</span><br><span class="line"> "next/babel", // Next默认配置</span><br><span class="line"> "stage-0"</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><p>生产模式下,需要先使用生产模式构建代码,再启动服务器。因此,需要两条命令:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">next build</span><br><span class="line">next start</span><br></pre></td></tr></table></figure> <p>Next 官方推荐使用<a target="_blank" rel="noopener" href="https://zeit.co/now">now</a>作为部署工具,只要在 package.json 文件中写入:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "my-app",</span><br><span class="line"> "dependencies": {</span><br><span class="line"> "next": "latest"</span><br><span class="line"> },</span><br><span class="line"> "scripts": {</span><br><span class="line"> "dev": "next",</span><br><span class="line"> "build": "next build",</span><br><span class="line"> "start": "next start"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>接着运行 now 命令,就可以实现一键部署。</p> </div> <footer class="post-footer"> <div class="post-tags"> <a href="/tags/javascript/" rel="tag"># javascript</a> </div> <div class="post-nav"> <div class="post-nav-item"> <a href="/2022/12/16/React-%E5%BC%80%E5%8F%91%E4%B9%8BUmi%E4%B9%8C%E7%B1%B3%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8/" rel="prev" title="React 开发之Umi乌米框架使用"> <i class="fa fa-chevron-left"></i> React 开发之Umi乌米框架使用 </a></div> <div class="post-nav-item"> <a href="/2022/12/16/Nuxt-js%E7%9A%84%E8%B8%A9%E5%9D%91%E6%8C%87%E5%8D%97/" rel="next" title="Nuxt.js的踩坑指南"> Nuxt.js的踩坑指南 <i class="fa fa-chevron-right"></i> </a></div> </div> </footer> </article> </div> <script> window.addEventListener('tabs:register', () => { let { activeClass } = CONFIG.comments; if (CONFIG.comments.storage) { activeClass = localStorage.getItem('comments_active') || activeClass; } if (activeClass) { let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); if (activeTab) { activeTab.click(); } } }); if (CONFIG.comments.storage) { window.addEventListener('tabs:click', event => { if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; let commentClass = event.target.classList[1]; localStorage.setItem('comments_active', commentClass); }); } </script> </div> <div class="toggle sidebar-toggle"> <span class="toggle-line toggle-line-first"></span> <span class="toggle-line toggle-line-middle"></span> <span class="toggle-line toggle-line-last"></span> </div> <aside class="sidebar"> <div class="sidebar-inner"> <ul class="sidebar-nav motion-element"> <li class="sidebar-nav-toc"> 文章目录 </li> <li class="sidebar-nav-overview"> 站点概览 </li> </ul> <!--noindex--> <div class="post-toc-wrap sidebar-panel"> <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E7%AE%80%E4%BB%8B"><span class="nav-number">1.</span> <span class="nav-text">一、简介</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8C%E3%80%81%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95"><span class="nav-number">2.</span> <span class="nav-text">二、基本用法</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AE%89%E8%A3%85"><span class="nav-number">2.1.</span> <span class="nav-text">安装</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%BF%90%E8%A1%8C"><span class="nav-number">2.2.</span> <span class="nav-text">运行</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BB%84%E4%BB%B6"><span class="nav-number">2.3.</span> <span class="nav-text">组件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="nav-number">2.4.</span> <span class="nav-text">获取数据</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%89%E3%80%81CSS"><span class="nav-number">3.</span> <span class="nav-text">三、CSS</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%86%85%E5%B5%8C%EF%BC%88Built-in%EF%BC%89CSS"><span class="nav-number">3.1.</span> <span class="nav-text">内嵌(Built-in)CSS</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CSS-in-JS"><span class="nav-number">3.2.</span> <span class="nav-text">CSS-in-JS</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9B%9B%E3%80%81%E8%B7%AF%E7%94%B1%E7%B3%BB%E7%BB%9F"><span class="nav-number">4.</span> <span class="nav-text">四、路由系统</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Link-%E7%BB%84%E4%BB%B6"><span class="nav-number">4.1.</span> <span class="nav-text">Link 组件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Router-%E5%AF%B9%E8%B1%A1"><span class="nav-number">4.2.</span> <span class="nav-text">Router 对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B7%AF%E7%94%B1%E4%BA%8B%E4%BB%B6"><span class="nav-number">4.3.</span> <span class="nav-text">路由事件</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%94%E3%80%81%E9%A2%84%E8%8E%B7%E5%8F%96%E9%A1%B5%E9%9D%A2"><span class="nav-number">5.</span> <span class="nav-text">五、预获取页面</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BB%84%E4%BB%B6-1"><span class="nav-number">5.1.</span> <span class="nav-text">组件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#prefetch-%E6%96%B9%E6%B3%95"><span class="nav-number">5.2.</span> <span class="nav-text">prefetch 方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE"><span class="nav-number">5.3.</span> <span class="nav-text">自定义配置</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%92%8C%E8%B7%AF%E7%94%B1"><span class="nav-number">5.4.</span> <span class="nav-text">自定义服务器和路由</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89"><span class="nav-number">5.5.</span> <span class="nav-text">自定义</span></a></li></ol></li></ol></div> </div> <!--/noindex--> <div class="site-overview-wrap sidebar-panel"> <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person"> <img class="site-author-image" itemprop="image" alt="Bruce Chen" src="/images/portal.jpg"> <p class="site-author-name" itemprop="name">Bruce Chen</p> <div class="site-description" itemprop="description">It's better to burn out than to fade away.</div> </div> <div class="site-state-wrap motion-element"> <nav class="site-state"> <div class="site-state-item site-state-posts"> <a href="/archives/"> <span class="site-state-item-count">83</span> <span class="site-state-item-name">日志</span> </a> </div> <div class="site-state-item site-state-categories"> <a href="/categories/"> <span class="site-state-item-count">7</span> <span class="site-state-item-name">分类</span></a> </div> <div class="site-state-item site-state-tags"> <a href="/tags/"> <span class="site-state-item-count">3</span> <span class="site-state-item-name">标签</span></a> </div> </nav> </div> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=1440429303&auto=1&height=66"></iframe> <div class="links-of-author motion-element"> <span class="links-of-author-item"> <a href="https://github.com/jschentt" title="GitHub → https://github.com/jschentt" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a> </span> <span class="links-of-author-item"> <a href="mailto:jschentt@gmail.com" title="E-Mail → mailto:jschentt@gmail.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a> </span> <span class="links-of-author-item"> <a href="https://twitter.com/BruceCh56742806" title="Twitter → https://twitter.com/BruceCh56742806" rel="noopener" target="_blank"><i class="fab fa-twitter fa-fw"></i>Twitter</a> </span> <span class="links-of-author-item"> <a href="https://www.facebook.com/jinsheng.chen.5439" title="Facebook → https://www.facebook.com/jinsheng.chen.5439" rel="noopener" target="_blank"><i class="fab fa-facebook fa-fw"></i>Facebook</a> </span> </div> </div> </div> </aside> <div id="sidebar-dimmer"></div> </div> </main> <footer class="footer"> <div class="footer-inner"> <div class="copyright"> © <span itemprop="copyrightYear">2022</span> <span class="with-love"> <i class="fa fa-heart"></i> </span> <span class="author" itemprop="copyrightHolder">Bruce Chen</span> </div> <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动 </div> <div class="busuanzi-count"> <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script> <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-user"></i> </span> <span class="site-uv" title="总访客量"> <span id="busuanzi_value_site_uv"></span> </span> </span> <span class="post-meta-divider">|</span> <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-eye"></i> </span> <span class="site-pv" title="总访问量"> <span id="busuanzi_value_site_pv"></span> </span> </span> </div> </div> </footer> </div> <script src="/lib/anime.min.js"></script> <script src="/lib/velocity/velocity.min.js"></script> <script src="/lib/velocity/velocity.ui.min.js"></script> <script src="/js/utils.js"></script> <script src="/js/motion.js"></script> <script src="/js/schemes/pisces.js"></script> <script src="/js/next-boot.js"></script> <script src="/js/local-search.js"></script> </body> </html>