Next.js 是一个轻量级的 React 服务端渲染应用框架:
https://nextjs.frontendx.cn/docsnextjs.frontendx.cn
1、什么是服务器渲染
后端先调用【数据库】,获得数据之后,将数据和页面元素进行拼装,组合成完整的 html 页面,再直接返回给浏览器,以便用户浏览。
整个渲染过程,是在服务器端执行!浏览器只负责去展示!
例如:
服务器渲染
2、怎么判断这个网站是不是服务器渲染?
打开一个网站,右键,查看网页源代码,有与网站内容相关的代码,就是服务器渲染的。
3、什么是客户端渲染?
数据由浏览器通过 ajax 请求动态取得,再通过 js 将数据填充到 dom 元素最终展示到网页中。
例如:http://h5.ele.me/msite/
客户端渲染
4、服务器渲染 VS 客户端渲染
看看就可以了
5、什么是 next.js ?
Next.js 是一个轻量级的 React 服务端渲染应用框架。
Next.js 使 React 应用 更简单。
安装:
npm install --save next react react-dom
初始化一个 npm 的项目(即初始化一个 package.json 文件):
npm init -Y
此时的项目结构
在根目录创建一个 :
pages 文件 --> index.js 文件
将下面脚本添加到 package.json 中:
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
即:
{
"name": "next",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"next": "^9.3.6",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"author": "",
"license": "ISC"
}
index.js :(有状态组件)
import React, { Component } from 'react'
export default class Index extends Component {
render() {
return (
<div>
<h1>hello world</h1>
</div>
)
}
}
这个组件还有更简单的写法:(无状态组件)
export default () => (
<div>
<h1>hello world!!!</h1>
</div>
)
运行程序:
npm run dev
默认打开 3000 端口
当我们在 index.js 文件中修改一下内容,再看 localhost:3000
热替换!!——没有刷新就显示出来了
6、什么是 react 同构!!???
客户端与服务器端使用相同的组件,使用同一份代码。
服务器端:至负责首次渲染。
客户端:负责行为和交互等等。(比如点击事件)
7、使用 CSS / Sass / Less / Stylus files 样式
- @zeit/next-css
- @zeit/next-sass
- @zeit/next-less
- @zeit/next-stylus
@zeit/next-csswww.npmjs.com
安装:
npm install --save @zeit/next-css
配置文件:
// next.config.js
const withCSS = require('@zeit/next-css')
module.exports = withCSS()
8、静态文件服务(如图像)
在根目录下新建文件夹叫static
。代码可以通过/static/
来引入相关的静态资源。
export default () => <img src="/static/my-image.png" alt="my image" />
9、定制 head
index.js :
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>next 教程</title>
<meta charSet="utf-8" />
</Head>
<p>Hello world!</p>
</div>
)
头部/底部
目录结构
Layout .js:
import Head from 'next/head'
export default ({children}) => (
<div>
<Head>
<title>头部测试</title>
</Head>
{children}
<footer>
版权所有.未经许可.不可转载
</footer>
</div>
)
index.js:
import Layout from './components/layout'
export default () => (
<Layout>
<div>
<h1>Hello world!</h1>
</div>
</Layout>
)
list.js:
import React, { Component } from 'react'
import '../styles/list.css'
import Layout from './components/layout'
export default class List extends Component {
state = {
list:[ "a" , "b" , "c" ]
}
render() {
return (
<Layout>
<div>
<ul>
{
this.state.list.map(item =>(
<li>{item}</li>
))
}
</ul>
</div>
</Layout>
)
}
}
结果·
10、数据获取和生命周期
如果你需要一个有状态、生命周期或有初始数据的 React 组件(而不是无状态函数)
如下所示:
import React from 'react'
export default class extends React.Component {
//状态
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
//方法,返回
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
当页面渲染时加载数据,我们使用了一个异步方法 getInitialProps 。它能异步获取 JS 普通对象,并绑定在 props 上。
当服务渲染时,getInitialProps 将会把数据序列化,就像 JSON.stringify 。所以确保getInitialProps 返回的是一个普通 JS 对象,而不是 Date, Map 或 Set 类型。
当页面初始化加载时,getInitialProps 只会加载在服务端。没有跨域的限制。
只有当路由跳转(Link组件跳转或 API 方法跳转)时,客户端才会执行 getInitialProps。
注意:getInitialProps 将不能使用在子组件中。只能使用在 pages 页面组件中。
getInitialProps 的属性:
- pathname ------- URL 的 path 部分
- query ------------ URL 的 query 部分,并被解析成对象
- asPath ----------- 显示在浏览器中的实际路径(包含查询部分),为String类型
- req --------------- HTTP 请求对象 (只有服务器端有)
- res ---------------- HTTP 返回对象 (只有服务器端有)
- jsonPageRes ----- 获取数据响应对象 (只有客户端有)
- err ---------------- 渲染过程中的任何错误
https://m.maizou.com/v5/#/filmsm.maizou.com
如果接口可以用
它的结果
11、路由
路由的基本功能:next 默认按照文件结构(路径),进行页面跳转!
路由又分为:
基本路由:按照文件结构,进行页面跳转。(基本路由跳转又分为:link 跳转和编程式跳转。)
动态路由:路由页面跳转的 3 种方式:
link 跳转
编程式跳转
参数传递——路由跳转一般会传递一些参数
<Link>
组件实现客户端的路由切换:
引入
import Link from 'next/link'
layout.js:
import Head from 'next/head'
import Link from 'next/link'
export default ({children}) => (
<div>
<Head>
<title>头部测试</title>
</Head>
<div>
<Link href='/'>主页</Link> |
<Link href='/list'>列表</Link> |
<Link href='/nestStyle'>内联样式</Link>
</div>
{children}
<footer>
版权所有.未经许可.不可转载
</footer>
</div>
)
index.js:
import Layout from './components/layout'
export default () => (
<Layout>
<div>
<h1>Hello world!</h1>
</div>
</Layout>
)
list.js:
import React, { Component } from 'react'
import Layout from './components/layout'
import '../styles/list.css'
import Router from 'next/router'
export default class List extends Component {
state = {
list:[ "a" , "b" , "c" ]
}
render() {
return (
<Layout>
<div>
<ul>
{
this.state.list.map((item, index) =>(
<li key={index} onClick={() => Router.push('/detail?arg=' + item)}>{item}</li>
))
}
</ul>
</div>
</Layout>
)
}
}
注意:
<Link>
支持任何有onClick
事件的组件。
如果你不包含<a>
标签,它仅给组件添加onClick
事件,而不会添加href
属性!!
所以想要 list 页面也能跳转,需要:
nextStyle.js:
import React, { Component } from 'react'
import Layout from './components/layout'
export default class NextStyle extends Component {
render() {
return (
<Layout>
<div>
<style jsx>{`
h1 {
background-color: blue;
}
`}</style>
<h1>这里是内联样式!!(但不推荐这种写法)</h1>
</div>
</Layout>
)
}
}
这样就实现了路由传参
Router.push('/detail?arg='+ item)}
但 push 支持对象形式的
替换路由:
<Link>
组件默认将新 url 推入路由栈中。可以使用replace
属性来防止添加新输入。
// pages/index.js
import Link from 'next/link'
export default () =>
<div>
Click{' '}
<Link href="/about" replace>
<a>here</a>
</Link>{' '}
to read more
</div>
暴露 href
给子元素:
import Link from 'next/link'
import Unexpected_A from 'third-library'
export default ({ href, name }) =>
<Link href={href} passHref>
<Unexpected_A>
{name}
</Unexpected_A>
</Link>
禁止滚动到页面顶部:
<Link>
的默认行为就是滚到页面顶部。
当有 hash 定义时(#),页面将会滚动到对应的 id 上,就像<a>
标签一样。
为了预防滚动到顶部,可以给<Link>
加scroll={false}
属性。
<Link scroll={false} href="/?counter=10"><a>Disables scrolling</a></Link>
<Link href="/?counter=10"><a>Changes with scrolling to top</a></Link>
命令式:
也可以用next/router
实现客户端路由切换。
import Router from 'next/router'
export default () =>
<div>
Click <span onClick={() => Router.push('/about')}>here</span> to read more
</div>
拦截器 popstate
:
有些情况(比如使用custom router),你可能想监听popstate
,在路由跳转前做一些动作。 比如,你可以操作 request 或强制 SSR 刷新。
import Router from 'next/router'
Router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== "/" || as !== "/other") {
// Have SSR render bad routes as a 404.
window.location.href = as
return false
}
return true
});
如果你在
beforePopState
中返回 false,Router
将不会执行popstate
事件。
Router
对象的 API 如下:
-
route
- 当前路由的String
类型 -
pathname
- 不包含查询内容的当前路径,为String
类型 -
query
- 查询内容,被解析成Object
类型. 默认为{}
-
asPath
- 展现在浏览器上的实际路径,包含查询内容,为String
类型 -
push(url, as=url)
- 页面渲染第一个参数 url 的页面,浏览器栏显示的是第二个参数 url -
replace(url, as=url)
- performs areplaceState
call with the given url -
beforePopState(cb=function)
- 在路由器处理事件之前拦截.
push
和 replace
函数的第二个参数as
,是为了装饰 URL 作用。如果你在服务器端设置了自定义路由将会起作用。
URL 对象用法:
push
或 replace
可接收的 URL 对象(<Link>
组件的 URL 对象一样)来生成 URL。
import Router from 'next/router'
const handler = () =>
Router.push({
pathname: '/about',
query: { name: 'Zeit' }
})
export default () =>
<div>
Click <span onClick={handler}>here</span> to read more
</div>
路由事件:
可以监听路由相关事件。 下面是事件支持列表:
-
routeChangeStart(url)
- 路由开始切换时触发 -
routeChangeComplete(url)
- 完成路由切换时触发 -
routeChangeError(err, url)
- 路由切换报错时触发 -
beforeHistoryChange(url)
- 浏览器 history 模式开始切换时触发 -
hashChangeStart(url)
- 开始切换 hash 值但是没有切换页面路由时触发 -
hashChangeComplete(url)
- 完成切换 hash 值但是没有切换页面路由时触发
这里的
url
是指显示在浏览器中的 url。如果你用了Router.push(url, as)
(或类似的方法),那浏览器中的 url 将会显示 as 的值。
正确使用路由事件routeChangeStart
的例子:
const handleRouteChange = url => {
console.log('App is changing to: ', url)
}
Router.events.on('routeChangeStart', handleRouteChange)
如果你不想长期监听该事件,你可以用off
事件去取消监听:
Router.events.off('routeChangeStart', handleRouteChange)
如果路由加载被取消(比如快速连续双击链接):
Router.events.on('routeChangeError', (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
})