先解释一下Nuxt.js和Next.js虽然只有一个字母之差,但它们是不同的两个服务端渲染框架.
引用Next中文官网的一句话:
Next.js 是一个轻量级的 React 服务端渲染应用框架。
先执行创建命令:
mkdir 项目名
cd 项目名
npm init -y
npm i react react-dom next --save
mkdir pages//一定要叫这个名,不能更改
配置package.json中的scripts属性
"scripts": {
"dev":"next",
"build":"next build",
"start":"next start"
},
在pages文件夹里创建一个index.js页面,简单写点内容:
export default () => {
return (
<div>
<p>hello world</p>
</div>
)
}
执行npm run dev,出来效果:
1,引入Link组件
import Link from 'next/link';
2.使用
注意点:
路径是用href;
文字里面要用标签包裹(标签可以是a标签或者其他标签都可以,但Link标签里只能写一个其他标签);
给Link标签设置style样式是无效的,因为Link是一个高阶组件(HOC),但我们可以给子元素设置样式.
href属性也可以改为对象写法:
<Link href={{pathname:"/next-route/teacher"}} >
<button style={{color:'red'}}>去教师页面</button>
</Link>
同时对象写法可以传递query参数过去
代码:
<Link href={{pathname:"/next-route/teacher",query:{id:1}}} >
<button style={{color:'red'}}>去教师页面</button>
</Link>
第一步:引入Router对象
import Router from "next/router";
第二步:添加跳转事件
留意一下:浏览器输入网址的请求跳转方式network里会请求页面和js,但通过点击跳转的方式只有js,没有再次请求页面.
直接在pages文件夹里创建一个_error.js页面(只能叫这个名字)
不要写在pages有路由的文件夹里,在根目录里我们要创建一个单独的components文件夹,写代码如下:
import Link from "next/link"
const Mynav=()=>{
return (
<div>
<Link href={{pathname:"/next-route/teacher",query:{}}} >
<button style={{color:'red'}}>去教师页面</button>
</Link>
<Link href={{pathname:"/next-route/student",query:{}}} >
<button style={{color:'red'}}>去学生页面</button>
</Link>
</div>
)
}
export default Mynav
在路由主页中引入使用
导航效果就出来了:
student和teacher页面引入方式和上面一样.
第一步:创建布局组件
在根目录里创建一个layouts文件夹,里面写我的布局组件,上面导航是共用的,但是下面主体内容会动态变化,怎么实现呢?直接使用react里面的this.props.children属性即可动态渲染主体内容
第二步:使用布局组件(核心:把布局组件写成双标签形式,在双标签里放入要显示的动态内容即可)
效果:
同样方式,在teacher和student页面也把Mynav组件去掉,也改成布局组件Mylayout动态内容显示方式(这样Mynav组件就只有在Mylayout里引入一次,这样就实现了布局组件来布局)
这样在路由主页,教师页面和学生页面都采用了布局组件,实现了Mynav导航组件只在布局组件里导入一次.比如如果我们后面还要加一个尾部固定组件的话,那我们只需要在布局组件里再增加一个尾部组件即可,这样非常方便.
上面的Mylayout布局组件在主页,教师页和学生页等每个页面都引入了一次,有没有办法全局一次引入呢?办法如下:
在pages文件加下创建_app.js(只能叫这个名字),写如下代码(是固定写法):
import React from 'react'
import App, { Container } from 'next/app'
import Mylayout from './../layouts/Mylayout'
// Layout就是要写的布局组件,其它是固定写法
class Layout extends React.Component {
render() {
const { children } = this.props
return <Mylayout>{children}</Mylayout>
}
}
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<Container>
<Layout>
<Component {...pageProps} />
</Layout>
</Container>
)
}
}
这样其它页面都不用引入Mylayout局部组件了,只写自己的内容即可.
现在需求是老师渲染列表页面点击某位老师要进入详情页面并把id以参数方式传递过去.
在教师页面修改如下:
// import Mynav from '../../../components/Mynav'
// import Mylayout from './../../../layouts/Mylayout'
import Link from 'next/link'
const teacherList=[
{name:"teacher1",id:1},
{name:"teacher2",id:2},
{name:"teacher3",id:3},
]
const Teacher =()=>{
return (
<div>
{/* */}
{/*
teacher页面
*/}
<p> teacher页面</p>
<ul>
{
teacherList.map((item)=>{
return (
<li key={item.id}>
<Link href={`/next-route/teacher/detail?id=${item.id}`}>
<a>{item.name}</a>
</Link>
</li>
)
})
}
</ul>
</div>
)
}
export default Teacher;
准备一个老师的详情页面,引入withRouter高阶组件
,在withRouter()方法里将组件传递过去,然后在props.router.query.id里得到传递过来的参数(重要:withRouter可以获取url里的参数)
import {withRouter} from 'next/router';
const Detail=withRouter((props)=>{
console.log(props);
return (
<div>
这是{props.router.query.id}老师详情页面
</div>
)
})
export default Detail;
如果不引入withRouter是得不到query这个属性,所以在next.js中一定要引入withRouter这个方法.
withRouter这个高阶组件会讲当前的路由对象注入到组件中去,并将路由对象绑定到组件的props这个参数上.
上面教师详情页显示路径如下
但路径不好看,我们怎么实现teacher/3这样简洁呢?使用next里的浅层路由即可
其实就是使用Link组件有一个as属性,它可以给路径起别名,在教师页面操作如下:
上面的教师详情页当刷新页面时,会找不到页面,因为通过as属性,给browser history来个路由掩饰,但是按刷新按钮路由就找不到了,因为服务器回去重新找/p/xxxx页面,但是实际上此时并不存在xxxx页面,这个问题实际要服务器端协助解决(实际就是后台将我们别名的路由地址转为原来真实的路径),方法如下:
npm install --save express
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)
})
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
server.get('/next-route/teacher/:id', (req, res) => {
const actualPage = '/next-route/teacher/detail'
const queryParams = { id: req.params.id }
app.render(req, res, actualPage, queryParams)
})
比如我们用到的图片,它是不需要打包的,做法是在根目录里新建一个static文件夹(只能叫这个名字),在要用的地方写绝对路径即可.
isomorphic-unfetch支持服务器端渲染.使用方法如下:
1.安装isomorphic-unfetch
npm install --save isomorphic-unfetch
2.使用
引入
import fetch from 'isomorphic-unfetch'
记住:fetch方法调用前要使用组件.getInitiaProps赋值一个函数,在函数里面调用fetch方法.
因为使用异步静态方法getInitialProps获取数据,此静态方法能够获取所有的数据,并将其解析成一个 JavaScript对象,然后将其作为属性附加到 props对象上
上面是函数组件,类组件的话写法如下:
这样数据就出来了.
注意:getInitialProps 不能 在子组件上使用,只能使用在pages文件夹的页面中进行调用。
同时,getInitialProps接收一个上下文对作为参数,这个对象包含以下属性:
next.js支持普通的react样式外,还有自己的独特样式,写法如下:
上面写法有两个属性要注意
jsx:它仅限作用于当前组件,子组件不会生效;
global:它不但作用域当前组件,子组件也会生效.
import Link from 'next/link';
const Movieheader =()=>(
<div class="movie-header">
<style jsx>
{`
.movie-header {
position: fixed;
top: 0;
left: 0;
right: 0;
}
ul {
display: flex;
justify-content: space-around;
align-items: center;
padding: 15px 0;
background-color: #1e2736;
margin: 0;
}
li {
list-style: none;
line-height: 30px;
height: 30px;
}
li a {
color: white;
}
li a:hover {
color: red;
}
`}
</style>
<ul>
<li>
<Link href="/movie/type?type=in-theaters"><a>正在热映</a></Link>
</li>
<li>
<Link href="/movie/type?type=cooming_soon"><a>即将上映</a></Link>
</li>
<li>
<Link href="/movie/type?type=top250"><a>top250</a></Link>
</li>
</ul>
</div>
)
export default Movieheader;
注意数据请求发送方式.
import fetch from 'isomorphic-unfetch';
import {withRouter} from 'next/router'
import Link from 'next/link'
const Movietype = withRouter(props => (
<div className="movie-type">
<h1>这是电影详情页</h1>
<ul>
{props.movieList.map(item => {
return (
<div key={item.id} className="movie-box">
{/* 提示:地址里的type值获取方式可以使用withRouter高阶组件获取 */}
<Link href={`/movie/detail?id=${item.id}&type=${props.router.query.type}`}>
<div>
<img src={item.img} alt={item.title}></img>
<h4>{item.title}</h4>
<p>评分:{item.rating}</p>
</div>
</Link>
</div>
)
})}
</ul>
<style jsx>
{`
.movie-type {
display: flex;
flex-direction: column;
align-items: center;
}
.movie-box {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
padding: 10px 0;
width: 140%;
box-shadow: 0 0 10px #bbb;
}
.movie-box:hover {
box-shadow: rgba(0,0,0,0.3) 0px 19px 60px;
}
`}
</style>
</div>
))
Movietype.getInitialProps = async function(context) {
let res = await fetch(`http://localhost:3301/${context.query.type}`)
// console.log(context.query.type);
let data = await res.json()
console.log(data)
return {
movieList: data
}
}
export default Movietype
import fetch from 'isomorphic-unfetch'
const Detail=(props)=>(
<div className="detail">
<div className="detail-box">
<img src={props.detail.img} alt={props.detail.title}/>
<h4>{props.detail.title}</h4>
<p>电影类型:{props.detail.genres.join(',')}</p>
<p>上映时间:{props.detail.details[0].year}</p>
<p>剧情介绍:{props.detail.details[0].summary}</p>
</div>
<style jsx>{`
.detail {
width: 40%;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
box-shadow: 0 0 10px #bbb;
}
.detail-box {
text-align: center;
}
`}</style>
</div>
)
Detail.getInitialProps=async function(context) {
let res= await fetch(`http://localhost:3301/${context.query.type}/${context.query.id}?_embed=details`)
let data =await res.json();
console.log(data);
return {
detail:data
}
}
export default Detail;
其实很简单,next.js自己封装了一个head组件,需要seo的地方按需引入,在里面写各自的标签即可.
完整效果:
到此,next.js就学到这里了.最后附上全部项目代码克隆链接:
[email protected]:huanggengzhong/SSR.git