Next.js入门教程

为什么80%的码农都做不了架构师?>>>   hot3.png

阅读之前

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

参考代码:https://github.com/chkui/nextjs-getting-started 。

搭建

安装

# 创建项目目录
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 = () => (
  

Hello World!

) export default Index

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

页面与导航栏

页面

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

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

export default () => (
  

About page

)

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

导航栏

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

import Link from 'next/link'

const Index = () => (
  
About Page

Hello Next.js

) export default Index

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

除了标签,

Hello Next.js

) export default Index const A = props => (
{ props.onClick(e) }}>Click Me
)

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

页面、资源与组件

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

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

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

创建网站结构

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

import Link from 'next/link'

const linkStyle = {
    marginRight: 15
}

const Header = () => (
    
)

export default Header

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

import Header from '../components/Header'
export default () => (
    

Hello Next.js

)

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

网站布局

通常情况下,开发一个网站先制定一个通用的布局(尤其是响应式布局的网站),然后再向布局中的添加各个部分的内容。使用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) => (
    
{props.children}
) export default Layout
// components/footer.js
const Footer = () => (
    

Footer

) export default Footer

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

import Layout from '../components/layout'

export default () => (
    
        

Hello Next.js

)

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

页面跳转

传递参数

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

const SubLink = props => (
    
  • {props.title}
  • ) export default () => (

    Information

    )

    点击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) => (
        
            

    Post Page

    Info:{props.router.query.title}

    )) export default Page

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

    路径隐藏

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

    const SubLink = props => (
        
  • {props.title}
  • ) export default () => (

    Information

    )

    注意观察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) => (
        
            

    Batman TV Shows

    ) 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) => (
        
            

    {props.show.name}

    {props.show.summary.replace(/<[/]?p>/g, '')}

    ) 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中推荐一种简介高效的方法—— )