Next.js

最近在写组件库,关注到了 Next.js,项目中也正在使用,现在把它整理成文档记录一下,也希望可以帮助需要的同学。

那么,什么是 Next.js ?

它是一个 React 开发框架。

通常使用 React 开发项目时,需要做配置打包、编译等等复杂繁琐的工作。Next.js 提供了一个解决方案,开发人员可以把精力放在业务上,而不必关心项目的配置。

它能解决什么问题?
  • 自动编译和打包

  • 热加载

  • 预渲染、客户端渲染、服务器渲染

  • 静态和动态路由

  • 静态资源

  • 代码拆分

它有什么特点?
  • 基于页面的路由方案(/pages)

  • 预渲染,支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)

  • 自动根据页面进行代码分割

  • 内置 CSSSass 、.less 和 .styl 的支持,并支持任何 CSS-in-JS

  • 支持定制 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 是一个很好用的开发框架,用最少的配置就可以正常开发,降低了开发人员业务外的繁琐工作,因其内置的特性,使得开发人员可以快速开发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容