最近在写组件库,关注到了 Next.js,项目中也正在使用,现在把它整理成文档记录一下,也希望可以帮助需要的同学。
那么,什么是 Next.js ?
它是一个 React 开发框架。
通常使用 React 开发项目时,需要做配置打包、编译等等复杂繁琐的工作。Next.js 提供了一个解决方案,开发人员可以把精力放在业务上,而不必关心项目的配置。
它能解决什么问题?
自动编译和打包
热加载
预渲染、客户端渲染、服务器渲染
静态和动态路由
静态资源
代码拆分
它有什么特点?
基于页面的路由方案(/pages)
自动根据页面进行代码分割
支持定制 Babel 和 Webpack 的配置项
支持热更新
利用 Serverless Functions 及 API 路由 构建 API 功能
如何创建新应用并使用呢?
创建应用
npx create-next-app nextjs-demo --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"
上面命令的主要作用是通过调用
create-next-app
工具来创建一个 Next.js 项目。通过--example
参数指定使用 此模板 。
运行应用
首先,进行文件目录:
cd nextjs-demo
其次,启动应用:
npm run dev
这时,在浏览器打开 http://localhost:3000/
来运行应用。
编辑页面
首先,找到并打开 /pages/index.js 页面。
其次,修改标题,Welcome to 改成 Learn 并保存。
最后,查看页面(Welcome to 已改成 Learn)。
路由
Next.js 没有路由配置文件,采用的是约定式路由。即 pages 文件夹下创建的文件,都会默认生成以文件名命名的路由。
例如:
pages/index.js 代表的是入口 / 路由。
pages/about/policy.js 代表的是 /about/policy 路由。
pages/about/index.js 代表的是 /about 路由。
pages/about.js 代表的是 /about 路由。
页面导航需要借助 next/link 组件。
组件 <Link> 接收字符串,也可以是 URL 对象,而且它会自动格式化生成 URL 字符串。
使用方法如下:
import Link from 'next/link'
...
<Link href="/about">
<a>About Page</a>
</Link>
...
此时如果想给路由传参数,可以使用 query string ,也可以使用 URL 对象:
<Link href="/about?title=hello">
<a>About Page</a>
</Link>
或
// 将生成 URL 字符串/about?title=hello
<Link href={{ pathname: '/about', query: { title: 'hello' }}}>
<a>here</a>
</Link>
当取参数的时候,可以使用框架提供的 withRouter 方法,参数封装在 query 对象中:
import { withRouter } from 'next/router'
const Page = withRouter(props => (
<h1>{props.router.query.title}</h1>
))
export default Page
如果希望浏览器地址栏不显示 query string,可以使用as属性:
<Link as={`/p/${props.id}`} href={`/about?id=${props.id}`}
<a>{props.title}</a>
</Link>
此时,浏览器的地址栏将显示 localhost:3000/p/123
替换路由
给 <Link> 标签添加 replace
不会产生新的路由。
<Link href="/about" replace>
<a>here</a>
</Link>
注意:<Link> 的默认行为会滚动到页面顶部。当有 hash 定义时(#),页面将会滚动到对应的 id 上,就像 <a> 标签一样。为了预防滚动到顶部,可以给 <Link> 加 scroll={false} 属性
命令式
除了页面导航,也可以使用框架内置的 next/router 实现客户端路由切换。
import Router from 'next/router'
const handler = () =>
Router.push({
pathname: '/about',
query: { name: 'Zeit' }
})
...
<div>
Click <span onClick={handler}>here</span> to read more
</div>
...
可通过 push 或 replace 进行跳转,参数是 URL 对象(与 <Link> 组件的 URL 对象一样)。
支持在页面跳转之前进行拦截,只需要监听popstate
:
import Router from 'next/router'
Router.beforePopState(({ url, as, options }) => {
// 只有 / 和 /other 可以正常返回
if (as !== "/" || as !== "/other") {
window.location.href = as
return false
}
return true
});
beforePopState 中返回 false,Router 将不会执行 popstate 事件。
Router 对象的 API:
- route 当前路由的 String 类型
- pathname 不包含查询内容的当前路径,为 String 类型
- query 查询内容,被解析成 Object 类型, 默认为 {}
- asPath 展现在浏览器上的实际路径,包含查询内容,为 String 类型
- push(url, as=url) 页面渲染第一个参数 url 的页面,浏览器栏显示的是第二个参数 url
- replace(url, as=url) 页面渲染第一个参数 url 的页面,浏览器栏显示的是第二个参数 url
- beforePopState(cb=function) 在路由器处理事件之前拦截
路由事件
如果业务复杂,需要监听路由,可以监听下面的事件:
routeChangeStart(url) 路由开始切换时触发
routeChangeComplete(url) 完成路由切换时触发
routeChangeError(err, url) 路由切换报错时触发
beforeHistoryChange(url) 浏览器 history 模式开始切换时触发
hashChangeStart(url) 开始切换 hash 值但是没有切换页面路由时触发
hashChangeComplete(url) 完成切换 hash 值但是没有切换页面路由时触发
如何监听和取消呢?
在路由切换时监听事件:
const handleRouteChange = url => {
console.log('App is changing to: ', url)
}
Router.events.on('routeChangeStart', handleRouteChange)
取消监听事件:
Router.events.off('routeChangeStart', handleRouteChange)
路由取消事件:
Router.events.on('routeChangeError', (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
})
代码分割
import cowsay from 'cowsay-browser'
...
<pre>
{cowsay.say({ text: 'hi there!' })}
</pre>
...
页面只会加载通过 import 导入的和用到的代码。即:不会加载不需要的代码。
如想实现组件懒加载,可以通过框架提供的 next/dynamic 工具函数。
支持 SSR
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(import('../components/hello'))
...
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
...
不支持 SSR
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(import('../components/hello3'), {
ssr: false
})
...
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
...
同时加载多个模块
import dynamic from 'next/dynamic'
const Hello = dynamic({
modules: () => {
const components = {
Hello1: import('../components/hello1'),
Hello2: import('../components/hello2')
}
return components
},
render: (props, { Hello1, Hello2 }) =>
<div>
<h1>
{props.title}
</h1>
<Hello1 />
<Hello2 />
</div>
})
export default () => <Hello title="Dynamic Bundle" />
CSS
支持嵌入样式
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
div {
background: red;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
<style global jsx>{`
body {
background: black;
}
`}</style>
</div>
内嵌样式
<p style={{ color: 'red' }}>hello world</p>
使用 CSS / Sass / Less / Stylus 样式文件
需要配置默认文件 next.config.js
静态资源
静态资源默认是在 public
文件夹下,代码可直接引用。
<img src="/vercel.svg" alt="Vercel Logo" className="logo" />
也可以将静态资源放在 static 文件夹下,代码引用时可通过 /static/ 来引入相关的静态资源。
<img src="/static/my-image.png" alt="my image" />
注意:不要自定义静态文件夹的名字,只能叫
static
,因为只有这个名字
Next.js 才会把它当作静态资源。
生成 <head>
import Head from 'next/head'
...
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
...
注意:只有后面的 head 会最终执行。
预加载页面
Next.js 的预加载功能只预加载 JS 代码。当页面渲染时需要等待数据请求。
<Link>添加 prefetch 属性
import Link from 'next/link'
...
<nav>
<ul>
<li>
<Link prefetch href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link prefetch href="/about">
<a>About</a>
</Link>
</li>
<li>
<Link prefetch href="/contact">
<a>Contact</a>
</Link>
</li>
</ul>
</nav>
...
命令式
import React from 'react'
import { withRouter } from 'next/router'
class MyLink extends React.Component {
componentDidMount() {
const { router } = this.props
// 预加载页面
router.prefetch('/dynamic')
}
render() {
const { router } = this.props
return (
<div>
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
路由跳转延缓 100 ms
</a>
</div>
)
}
}
export default withRouter(MyLink)
总结
Next.js 是一个很好用的开发框架,用最少的配置就可以正常开发,降低了开发人员业务外的繁琐工作,因其内置的特性,使得开发人员可以快速开发。