跳到主要内容

next js之入门教程全篇

· 阅读需 10 分钟
文浩Marvin

Next.js入门教程

阅读之前

在了解Next.js之前,需要掌握React的基本使用方法。

搭建

安装

# 创建项目目录
mkdir you_project
# 进入项目目录
cd you_project
# 初始化package.json
npm init -y
# 安装依赖包
npm install --save react react-dom next
# 创建一个pages文件夹
mkdir pages

依次执行以上命令之后,Next.js运行所需的最基本的目录和依赖就创建好了。

运行

package.json里的“scripts"字段修改为:

{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}

运行以下命令启动Next.js

npm run dev

在浏览器打开http://localhost:3000/ 看到输出"404 - This page could not be found",表示Next.js安装成功。

添加页面

./pagesNext.js默认的网页路径,其中的index.js就代表整个网站的主页。创建一个./pages/index.js组件:

const Index = () => (
<div>
<p>Hello World!</p>
</div>
)
export default Index

添加./pages/index.js后网站会自动刷新,呈现"Hello World!"。

页面与导航栏

页面

添加http://localhost:3000/about 路径下的页面。

创建./pages/about.js文件,添加以下内容:

export default () => (
<div>
<p>About page</p>
</div>
)

然后在浏览器输入http://localhost:3000/about 即可看到新增的About。

导航栏

./pages/index.js稍加修改引入导航栏功能:

import Link from 'next/link'

const Index = () => (
<div>
<Link href="/about">
<a style={{fontSize: 20}}>About Page</a>
</Link>
<p>Hello Next.js</p>
</div>
)

export default Index

注意:使用了Next.js作为服务端渲染工具,切记仅使用next/link中的Link组件。

除了<a>标签,<button>或自定义的组件都可以被Link包装,只要传递Click事件即可,将上面的代码稍作修改实验这个效果:

import Link from 'next/link'

const Index = () => (
<div>
<Link href="/about">
<button>Click Me</button>
</Link>
<Link href="/about">
<A/>
</Link>
<p>Hello Next.js</p>
</div>
)

export default Index

const A = props => (<div onClick={e => {
props.onClick(e)
}}>Click Me</div>)

关于Next.js路由管理相关的细节内容,可以到这里查看

页面、资源与组件

./pages是一个保留路径,在/pages路径下任何js文件中导出的默认React组件都被视作一个页面。

除了./pagesNext.js还有一个保留路径是./static,它用来存放图片等静态资源。

Next.js会对./pages中的React组件进行“包装",所以./pages内外的React组件在呈现结果上有一些差异,看下面的例子。

创建网站结构

在工程根目录创建/components文件夹,然后添加以下组件:

import Link from 'next/link'

const linkStyle = {
marginRight: 15
}

const Header = () => (
<div>
<Link href="/">
<a style={linkStyle}>Home</a>
</Link>
<Link href="/about">
<a style={linkStyle}>About</a>
</Link>
</div>
)

export default Header

然后将Header整合到about.jsindex.js中:

import Header from '../components/Header'
export default () => (
<div>
<Header />
<p>Hello Next.js</p>
</div>
)

再次进行页面操作,就会出现表头静止页面变换的效果。

网站布局

通常情况下,开发一个网站先制定一个通用的布局(尤其是响应式布局的网站),然后再向布局中的添加各个部分的内容。使用Next.js可以通过组件的方式来设计一个布局,看下面的例子。 在/components中增加LayoutFooter组件:

// componments/layout.js
import Header from './header'
import Footer from './footer'

const layoutStyle = {
margin: 20,
padding: 20,
border: '1px solid #DDD'
}

const Layout = (props) => (
<div style={layoutStyle}>
<Header />
{props.children}
<Footer />
</div>
)

export default Layout

// components/footer.js
const Footer = () => (
<div>
<p style={{color:'blue'}}>Footer</p>
</div>
)

export default Footer

然后将/pages/index.js修改为:

import Layout from '../components/layout'

export default () => (
<Layout>
<p>Hello Next.js</p>
</Layout>
)

这样,页面的内容和布局就完全隔离开了。

页面跳转

传递参数

在实际应用中,经常需要在页面间传递参数,可以使用高阶组件withRouter来实现。 下面的代码对/pages/index.js进行了一些修改,使其在跳转时携带query参数:

const SubLink = props => (
<li>
<Link href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
</li>
)

export default () => (
<Layout>
<h2>Information</h2>
<SubLink title="First Post"/>
<SubLink title="Second Post"/>
<SubLink title="Third Post"/>
</Layout>
)

点击First Post之后浏览器的URL会出现这样的路径:“http://localhost:3000/post?title=First%20Post” 。接下来利用withRouter来获取这个参数。创建./pages/post.js的文件:

import {withRouter} from 'next/router'
import Layout from '../components/layout'

const Page = withRouter((props) => (
<Layout>
<h3>Post Page</h3>
<p>Info:{props.router.query.title}</p>
</Layout>
))
export default Page

现在点击First Post链接之后,跳转的页面会显示First Post

路径隐藏

Next.js提供了一个让URL更加清晰干净的特性功能——URL隐藏(官网直译的话应该叫“URL遮挡”),他的作用是可以隐藏原来比较复杂的URL,让网站路径更加清晰,有利于SEO等。实现这个特性非常简单,在使用Link组件时传递一个as参数。下面将继续修改./pages/index.js中的内容以实现这个特性:

const SubLink = props => (
<li>
<Link as={`p/${props.as}`} href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
</li>
)

export default () => (
<Layout>
<h2>Information</h2>
<SubLink as="first-post" title="First Post"/>
<SubLink as="first-post" title="Second Post"/>
<SubLink as="first-post" title="Third Post"/>
</Layout>
)

注意观察SubLink组件中的修改,为Link增加了一个as参数,这个参数传递的内容将会在浏览器的地址栏显示。例如点击FIrst Post后,浏览器的地址栏会显示http://localhost:3000/p/first-post ,但是我们通过withRouter组件获取的URL还是href传递的路径。

服务端渲染

只要运行了Next.js,他时时刻刻都在执行服务端渲染,可以通过刷新页面看到效果。如果没有太多需求,不进行任何调整Next.js能为我们完成静态页面的服务端渲染,但是通常情况下,还需要处理异步请求等等情况。

二次服务端渲染

前面介绍了在Link组件上使用as参数可以设置浏览器路径栏上显示的内容。但是这个时候仅仅支持客户端跳转,如果进行页面刷新会出现404页面。导致这个问题出现的原因是在服务端并不知道/p/first-post对应/pages文件夹中的哪个文件。为了解决这个问题,需要在服务端进行二次渲染。

首先需要添加Express服务:

npm install --save express

安装完成之后在根目录添加一个server.js文件,其内容如下:

const express = require('express')
const next = require('next')

// 不等于'production'则表示运行的是开发环境
const dev = process.env.NODE_ENV !== 'production'
// 创建一个服务端运行的Next app
const app = next({dev})
// 请求处理器
const handle = app.getRequestHandler()

app.prepare()
.then(() => {
const server = express()

server.get('/p/:id', (req, res) => {
//将/p/:id的路径切换成/post?title=req.params.id的路径
app.render(req, res, '/post', {title: req.params.id})
})

server.get('*', (req, res) => {
return handle(req, res)
})

server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})

然后修改package.json的“scripts"字段,将启动方式方式指向server.js

"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}

完成这2步网站服务端也可以正常跳转,实现功能的位置是这段代码:

server.get('/p/:id', (req, res) => {
app.render(req, res, '/post', {title: req.params.id})
})

他将原来的请求“/p/:id”转换为请求"/post?title=id"。

更多的服务端渲染的配置说明请看这里

数据异步请求

对于一个前后端分离的系统来说,异步数据请求是几乎每个页面都需要的。Next.js通过getInitialProps来实现。 下面的示例数据来自https://www.tvmaze.com/api 。创建./pages/tvshows.js的文件:

import Layout from '../components/layout.js'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'

const TvShow = (props) => (
<Layout>
<h1>Batman TV Shows</h1>
<ul>
{props.shows.map(({show}) => (
<li key={show.id}>
<Link href={`/tv?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
)

TvShow.getInitialProps = async function() {
//contxt是衔接Next.js包装组件和自定义主键的上下文,包含的参数有asPath、pathname、query

// 发送异步请求
const res = await fetch('https://api.tvmaze.com/search/shows?q=batman')

// 从response中异步读取数据流
const data = await res.json()

console.log(`Show data fetched. Count: ${data.length}`)

// 返回已获取的数据
return {
shows: data
}
}

export default TvShow

TvShow组件的作用是异步请求数据并组装成列表展示。

然后再创建一个查看详情的页面——./pages/tv.js,实现过程和上面一样:

import Layout from '../components/layout'
import fetch from 'isomorphic-unfetch'

const Tv = (props) => (
<Layout>
<h1>{props.show.name}</h1>
<p>{props.show.summary.replace(/<[/]?p>/g, '')}</p>
<img src={props.show.image.medium}/>
</Layout>
)

Tv.getInitialProps = async function (context) {
const { id } = context.query
const res = await fetch(`https://api.tvmaze.com/shows/${id}`)
const show = await res.json()

console.log(`Fetched show: ${show.name}`)

return { show }
}
export default Tv

按照这个套路可以解决绝大部分数据异步请求的问题。不过如果数据组装过慢,会出页面现卡顿的问题,可以通过服务端缓存或异步页面加载实现,后续的篇幅会介绍。

样式

源生添加样式

一个页面永远离不开样式,在Next.js中推荐一种简介高效的方法——<style jsx>

为的主页添加一些样式:

(
<Layout>
<h2>Information</h2>
<SubLink as="first-post" title="First Post"/>
<SubLink as="first-post" title="Second Post"/>
<SubLink as="first-post" title="Third Post"/>
<style jsx>{`
h2{
font-family: "Arial";
}
`}</style>
<style jsx global>{`
.list{
list-style: none;
margin: 5px 0;
}
`}</style>
</Layout>
)

<style jsx>的作用就是为当前组件声明样式,需要注意的是在这个标签内声明的样式只能覆盖当前组件,子组件是不会出现层叠效果的。而<style jsx global>标签的效果则是和标准的css层叠效果一致,在这个标签中声明的样式会影响到子组件。

Loader添加载样式

Next.js可以加载各种样式文件,下面以Sass/Scss为例。

首先添加相关依赖:

npm install --save @zeit/next-sass node-sass

在项目根目录添加next.config.js文件,用于指示Next加载对用的功能:

const withSass = require('@zeit/next-sass')
module.exports = withSass()

现在就可以加载.scss文件了,添加一个/pages/post.scss文件:

$font-size: 50px;
.header{
font-size: $font-size;
color:red;
}

修改/pages/post.js加载样式:

import {withRouter} from 'next/router'
import Layout from '../components/layout'
//加载样式
import './post.scss'

const Page = withRouter((props) => (
<Layout>
<h3 className="header">Post Page</h3>
<p>Info:{props.router.query.title}</p>
</Layout>
))

export default Page

由于是使用的webpackLoader,可以根据需要在next.config.js文件中进行一些相关的设置:

module.exports = withSass({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: "[local]___[hash:base64:5]",
}
})

然后在组件中直接以对象的方式使用:

import style from './post.scss'
const Page = withRouter((props) => {
console.log(style)
return (
<Layout>
<h3 className={style.header}>Post Page</h3>
<p>Info:{props.router.query.title}</p>
</Layout>
)
})

更多关于cssLoaderOptions的参数说明可以查看webpack里css-loader的options说明。除了scss,Next.js还支持css、less、post css的Loader

发布

在了解以上内容之后,已经可以开发一个网站了,接下来介绍如何发布生产包。

package.json中的“scripts"字段可以设置打包和生产运行方式:

  "scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}

首先进行打包:

npm run build

打包完毕之后可以启动生产环境:

npm start

现在用浏览器打开http://localhost:3000/ 地址可以发现运行的是生产环境(可以使用React工具查看,也可以打开开发人员模式)。 由于之前了在server.js中引入了Express,所以现在启动的是一个Express服务器。打包之后的文件都在./.next 路径下,可以仅仅拷贝依赖包(node_module)package.jsonserver.js以及./.next来运行生产环境。

Loading Comments...