本篇教程基于上一篇的基础,主要讲解服务端渲染,样式以及部署相关的一些知识,若你没有看过上一篇的内容,或者你看过又忘了,建议重新去看一遍。
顺便说一句,Next.js 3.0的版本在前几天已经正式对外发布,本篇教程仍然基于2.x的版本,若你使用3.0的版本,代码上可能有不一致的地方,需要你留意一下。
为了快速开始,咱们直接使用官方的例子进行讲解,先把代码拷贝下来:
git clone https://github.com/arunoda/learnnextjs-demo.git
cd learnnextjs-demo
git checkout clean-urls-ssr
项目拷贝下来后我们进入目录,安装一下依赖并启动:
cd learnnextjs-demo
npm install
npm run dev
打开页面,即可看到如下效果:
服务端渲染
官方的例子是根据TVMaze提供的api来展示电视节目,因此我们需要安装isomorphic-unfetch用于获取远程数据:
npm install isomorphic-unfetch -S
然后将以下代码替换到pages/index.js
中:
import Layout from '../components/MyLayout.js'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'
const Index = (props) => (
<Layout>
<h1>Batman TV Shows</h1>
<ul>
{props.shows.map(({show}) => (
<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=batman')
const data = await res.json()
console.log(`Show data fetched. Count: ${data.length}`)
return {
shows: data
}
}
export default Index
上面的代码中,写在Index.getInitialProps
中的内容,既可以跑在server端,也可以跑在浏览器端,当我们刷新页面时,可以看到server段的控制台输出了:
Show data fetched. Count: 10
但在浏览器控制台中却没有看到输出,那什么时候这段代码会跑在浏览器端呢?那就是当你通过客户端路由进来的时候,这段代码才会执行,我们来更改一下代码,
server.js中,找到包含/p/:id
的地方,并替换如下:
server.get('/p/:id', (req, res) => {
const actualPage = '/post'
const queryParams = { id: req.params.id }
app.render(req, res, actualPage, queryParams)
})
同时,更改pages/post.js
:
import Layout from '../components/MyLayout.js'
import fetch from 'isomorphic-unfetch'
const Post = (props) => (
<Layout>
<h1>{props.show.name}</h1>
<p>{props.show.summary.replace(/<[/]?p>/g, '')}</p>
<img src={props.show.image.medium}/>
</Layout>
)
Post.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 Post
这时候当我们是从首页的链接点进去post页面时,会由post页面从浏览器端发出请求获取数据,要是在这个页面直接刷新页面,则会由服务端获取数据,这就是Next.js实现的ssr,是不是感觉很简单。
另外,如果有些代码只希望在服务端执行,而不希望浏览器端执行,在可以根据context里面有没有包含req这个字段来判断,代码如下:
Post.getInitialProps = async function (context) {
if(context.req) {
// 只会在服务端执行
}
return { show }
}
上面的代码其实是有问题的,想想看,我希望在服务端执行的代码,自然不希望webpack把它打包到客户端,否则会增大打包后的脚本,用户体验也不好,解决方案可以参考这里,这里就不展开了。
样式
在react中写样式,一般可以归为2类,一类是基于css文件的传统方式(包括sass,postcss等),另一类则是css in js。
基于传统的方式写css,在Next.js中会有些问题,特别是ssr的时候,因此,官网给出的解决方案是使用css in js,在Next.js中,已经预装了一个css in js的框架叫styled-jsx。
我们回到我们的代码中,更改pages/index.js
,代码如下:
import Layout from '../components/MyLayout.js'
import Link from 'next/link'
function getPosts () {
return [
{ id: 'hello-nextjs', title: 'Hello Next.js'},
{ id: 'learn-nextjs', title: 'Learn Next.js is awesome'},
{ id: 'deploy-nextjs', title: 'Deploy apps with ZEIT'},
]
}
export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
{getPosts().map((post) => (
<li key={post.id}>
<Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
<style jsx>{`
h1, a {
font-family: "Arial";
}
ul {
padding: 0;
}
li {
list-style: none;
margin: 5px 0;
}
a {
text-decoration: none;
color: blue;
}
a:hover {
opacity: 0.6;
}
`}</style>
</Layout>
)
在标签<style jsx>
中,我们写我们的css,css必须包含在 {``}
中,否则会报错。
由于css in js有作用域的隔离,也就是说css只会应用于当前组件,不会应用于其子组件。因此,以下代码,样式只会作用于h1和ul标签,不会作用于li标签:
import Layout from '../components/MyLayout.js'
import Link from 'next/link'
function getPosts() {
return [
{ id: 'hello-nextjs', title: 'Hello Next.js' },
{ id: 'learn-nextjs', title: 'Learn Next.js is awesome' },
{ id: 'deploy-nextjs', title: 'Deploy apps with ZEIT' },
]
}
const PostLink = ({ post }) => (
<li>
<Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
<a>{post.title}</a>
</Link>
</li>
)
export default () => (
<Layout>
<h1>My Blog</h1>
<ul>
{getPosts().map((post) => (
<PostLink key={post.id} post={post} />
))}
</ul>
<style jsx>{`
h1, a {
font-family: "Arial";
}
ul {
padding: 0;
}
li {
list-style: none;
margin: 5px 0;
}
a {
text-decoration: none;
color: blue;
}
a:hover {
opacity: 0.6;
}
`}</style>
</Layout>
)
所以,你需要把样式写在子组件中:
const PostLink = ({ post }) => (
<li>
<Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
<a>{post.title}</a>
</Link>
<style jsx>{`
li {
list-style: none;
margin: 5px 0;
}
a {
text-decoration: none;
color: blue;
font-family: "Arial";
}
a:hover {
opacity: 0.6;
}
`}</style>
</li>
)
或者,使用全局选择器(global selectors),只需在style标签中加一个global关键字,如下:
<style jsx global>{`
h1, a {
font-family: "Arial";
}
ul {
padding: 0;
}
li {
list-style: none;
margin: 5px 0;
}
a {
text-decoration: none;
color: blue;
}
a:hover {
opacity: 0.6;
}
`}</style>
部署Next.js应用
部署Next.js也是一件非常简单的事情,我们更改一下我们的package.json
文件,在scripts字段中添加build和start:
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "next start"
}
然后执行:
npm run build
npm run start
npm run build
命令会打包适用于生产环境的代码,npm run start
则会启动我们的应用,默认端口为3000。
若想启动两个应用实例,只需要自定义端口即可,代码如下:
"scripts": {
"start": "next start -p $PORT"
}
npm run build
PORT=8000 npm start
PORT=9000 npm start
如上,会在8000及9000端口各自启动一个实例。
至此,Next.js的基础概念已经介绍完了,更高级的用法,可以参考官方的例子:Next.js。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。