24.next.js

1.next.js

  1. 安装

    1
    npm install --save react react-dom next
  2. 在项目根目录下添加文件夹pages(一定要命名为pages,这是next的强制约定,不然会导致找不到页面),然后在package.json文件里面添加script用于启动项目:

    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
    }
    }
  3. 在pages文件夹下创建index.js文件,文件内容:

    1
    2
    3
    4
    5
    6
    7
    const Index = () => (
    <div>
    <p>Hello next.js</p>
    </div>
    )

    export default Index
  4. 前端路由

    1
    2
    3
    4
    5
    6
    7
    const About = () => (
    <div>
    <p>This is About page</p>
    </div>
    )

    export default About;
  5. 路由的跳转

    next.js使用next/link实现页面之间的跳转,用法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import Link from 'next/link'

    const Index = () => (
    <div>
    <Link href="/about">
    <a>About Page</a>
    </Link>
    <p>Hello next.js</p>
    </div>
    )

    export default Index
  6. 解释路由跳转的原理:

    官方文档说用前端路由跳转是不会有网络请求的,实际会有一个对about.js文件的请求,而这个请求来自于页面内动态插入的script标签。但是about.js只会请求一次,之后再访问是不会请求的,毕竟相同的script标签是不会重复插入的。

  7. Link标签不支持添加style和className等属性,如果要给链接增加样式,需要在子元素上添加:

    1
    2
    3
    <Link href="/about">
    <a className="about-link" style={{color:'#ff0000'}}>Go about page</a>
    </Link>

1.Layout

所谓的layout就是就是给不同的页面添加相同的header,footer,navbar等通用的部分,同时又不需要写重复的代码。在next.js中可以通过共享某些组件实现layout。

我们先增加一个公共的header组件,放在根目录的components文件夹下面(页面级的组件放pages中,公共组件放components中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;

然后在index和about页面中引入header组件,这样就实现了公共的layout的header:

1
2
3
4
5
6
7
8
9
10
import Header from '../components/Header';

const Index = () => (
<div>
<Header />
<p>Hello next.js</p>
</div>
)

export default Index;

如果要增加footer也可以按照header的方法实现。
除了引入多个header、footer组件,我们可以实现一个整体的Layout组件,避免引入多个组件的麻烦,同样在components中添加一个Layout.js文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Header from './Header';

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

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

export default Layout

这样我们只需要在页面中引入Layout组件就可以达到布局的目的:

1
2
3
4
5
6
7
8
9
import Layout from '../components/Layout';

const Index = () => (
<Layout>
<p>Hello next.js</p>
</Layout>
)

export default Index;

2.页面间传值

next中的页面间传值方式和传统网页一样也可以用url参数实现,我们来做一个简单的博客应用:

首先将index.js的内容替换成如下来展示博客列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Link from 'next/link';
import Layout from '../components/Layout';

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

export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
<PostLink title="Hello next.js" />
<PostLink title="next.js is awesome" />
<PostLink title="Deploy apps with Zeit" />
</ul>
</Layout>
);

通过在Link的href中添加title参数就可以实现传值。

现在我们再添加博客的详情页post.js

1
2
3
4
5
6
7
8
9
10
11
import { withRouter } from 'next/router';
import Layout from '../components/Layout';

const Post = withRouter((props) => (
<Layout>
<h1>{props.router.query.title}</h1>
<p>This is the blog post content.</p>
</Layout>
));

export default Post;

上面代码通过 withRouter 将 next 的 router 作为一个 prop 注入到 component 中,实现对 url 参数的访问。

通过 props.router.query.title 进行取到传递的值

使用query string可以实现页面间的传值,但是会导致页面的url不太简洁美观,尤其当要传输的值多了之后。所以next.js提供了Route Masking这个特性用于路由的美化。

3.路由伪装(Route Masking)

这项特性的官方名字叫Route Masking,没有找到官方的中文名,所以就根据字面意思暂且翻译成路由伪装。所谓的路由伪装即让浏览器地址栏显示的url和页面实际访问的url不一样。实现路由伪装的方法也很简单,通过 Link 组件的 as 属性告诉浏览器 href 对应显示为什么 url 就可以了,index.js 代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Link from 'next/link';
import Layout from '../components/Layout';

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

export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
<PostLink id="hello-nextjs" title="Hello next.js" />
<PostLink id="learn-nextjs" title="next.js is awesome" />
<PostLink id="deploy-nextjs" title="Deploy apps with Zeit" />
</ul>
</Layout>
);

这是因为刷新页面会直接向服务器请求这个url,而服务端并没有该url对应的页面,所以报错。为了解决这个问题,需要用到next.js提供的自定义服务接口(custom server API)。

2.自定义服务接口

自定义服务接口前我们需要创建服务器,安装Express:

1
npm install --save express

在项目根目录下创建server.js 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare()
.then(() => {
const server = express();
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里面的dev script改为:

1
2
3
"scripts": {
"dev": "node server.js"
}

运行npm run dev后项目和之前一样可以运行,接下来我们需要添加路由将被伪装过的url和真实的url匹配起来,在server.js中添加:

1
2
3
4
5
6
7
8
......
const server = express();
server.get('/p/:id', (req, res) => {
const actualPage = '/post';
const queryParams = { title: req.params.id };
app.render(req, res, actualPage, queryParams);
});
......

这样我们就把被伪装过的url和真实的url映射起来,并且query参数也进行了映射。重启项目之后就可以刷新详情页而不会报错了。但是有一个小问题,前端路由打开的页面和后端路由打开的页面title不一样,这是因为后端路由传过去的是id,而前端路由页面显示的是title。这个问题在实际项目中可以避免,因为在实际项目中我们一般会通过id获取到title,然后再展示。作为Demo我们偷个小懒,直接将id作为后端路由页面的title。

之前我们的展示数据都是静态的,接下来我们实现从远程服务获取数据并展示。★★

3.远程数据获取

next.js提供了一个标准的获取远程数据的接口:getInitialProps,通过getInitialProps我们可以获取到远程数据并赋值给页面的props。getInitialProps即可以用在服务端也可以用在前端。接下来我们写个小Demo展示它的用法。我们打算从TVMaze API 获取到一些电视节目的信息并展示到我的网站上。首先,我们安装isomorphic-unfetch,它是基于fetch实现的一个网络请求库:

1
npm install --save isomorphic-unfetch

然后我们修改index.js如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
);
})}
</ul>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?q=marvel');
const data = await res.json();
return {
shows: data
}
}

export default Index;

以上代码的逻辑应该很清晰了,我们在getInitialProps中获取到电视节目的数据并返回,这样在Index的props就可以获取到节目数据,再遍历渲染成节目列表。

4.增加样式

到目前为止,咱们做的网页都太平淡了,所以接下来咱们给网站增加一些样式,让它变得漂亮。

对于React应用,有多种方式可以增加样式。主要分为两种:

  1. 使用传统CSS文件(包括SASS,PostCSS等)
  2. 在JS文件中插入CSS

使用传统CSS文件在实际使用中会用到挺多的问题,所以next.js推荐使用第二种方式。next.js内部默认使用styled-jsx框架向js文件中插入CSS。这种方式引入的样式在不同组件之间不会相互影响,甚至父子组件之间都不会相互影响。

1.styled-jsx

接下来,我们看一下如何使用styled-jsx。将index.js的内容替换如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a className="show-link">{show.name}</a>
</Link>
</li>
);
})}
</ul>
<style jsx>
{`
*{
margin:0;
padding:0;
}
h1,a{
font-family:'Arial';
}
h1{
margin-top:20px;
background-color:#EF141F;
color:#fff;
font-size:50px;
line-height:66px;
text-transform: uppercase;
text-align:center;
}
ul{
margin-top:20px;
padding:20px;
background-color:#000;
}
li{
list-style:none;
margin:5px 0;
}
a{
text-decoration:none;
color:#B4B5B4;
font-size:24px;
}
a:hover{
opacity:0.6;
}
`}
</style>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?q=marvel');
const data = await res.json();
console.log(`Show data fetched. Count: ${data.length}`);
return {
shows: data
}
}

export default Index;

增加了一点样式之后比之前好看了一点点。我们发现导航栏的样式并没有变。因为Header是一个独立的的component,component之间的样式不会相互影响。如果需要为导航增加样式,需要修改Header.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Link from 'next/link';

const Header = () => (
<div>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<style jsx>
{`
a{
color:#EF141F;
font-size:26px;
line-height:40px;
text-decoration:none;
padding:0 10px;
text-transform:uppercase;
}
a:hover{
opacity:0.8;
}
`}
</style>
</div>
)

export default Header;

2.全局样式

当我们需要添加一些全局的样式,比如 rest.css 或者鼠标悬浮在 a 标签上时出现下划线,这时候我们只需要在style-jsx标签上增加 global关键词就行了,我们修改Layout.js如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Header from './Header';

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

const Layout = (props) => (
<div style={layoutStyle}>
<Header />
{props.children}
<style jsx global>
{`
a:hover{
text-decoration:underline;
}
`}
</style>
</div>
)

export default Layout

这样鼠标悬浮在所有的a标签上时会出现下划线

5.部署项目

部署next.js应用

Build

部署之前我们首先需要能为生产环境build项目,在package.json中添加script:

1
"build": "next build"

接下来我们需要能启动项目来serve我们build的内容,在package.json中添加script:

1
"start": "next start"

然后依次执行:

1
2
npm run build
npm run start

build完成的内容会生成到.next文件夹内,npm run start之后,我们访问的实际上就是.next文件夹的内容。

运行多个实例
如果我们需要进行横向扩展( Horizontal Scale)以提高网站的访问速度,我们需要运行多个网站的实例。首先,我们修改package.json的start script:

1
"start": "next start -p $PORT"

如果是windows系统:

1
"start": "next start -p %PORT%"

然后运行build: npm run build,然后打开两个命令行并定位到项目根目录,分别运行:

然后运行build: npm run build,然后打开两个命令行并定位到项目根目录,分别运行:

1
2
PORT=8000 npm start
PORT=9000 npm start

运行完成后打开localhost:8000和localhost:9000都可以正常访问:

部署并使用自定义服务

我们将start script修改为:

1
"start": "NODE_ENV=production node server.js"

这样我们就解决了自定义服务的部署。重启项目后刷新详情页也能够正常访问了。

到此为止,我们就了解了next.js的基本使用方法,如果有疑问可以查看next.js官方文档,也可以给我留言讨论。

本文Demo源码:Github 源码
next.js官网:https://nextjs.org/
next.js官方教程:https://nextjs.org/learn
next.js Github:https://github.com/zeit/next.js
————————————————
版权声明:本文为CSDN博主「MudOnTire」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mudontire/article/details/80980910