最近在写组件库,关注到了 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 是一个很好用的开发框架,用最少的配置就可以正常开发,降低了开发人员业务外的繁琐工作,因其内置的特性,使得开发人员可以快速开发。