renderToString
方法用于将 React 组件转换为 HTML 字符串,通过 react-dom/server
导入.问题: Node 环境不支持 ESModule 模块系统,不支持 JSX 语法
配置服务器端打包命令: "dev:server-build": "webpack --config webpack.server.js --watch"
配置服务端启动命令: "dev:server-run": "nodemon --watch build --exec\"node build/bundler.js\""
在客户端对组件进行二次“渲染”,为组件元素附加事件
使用 hydrate 方法对组件进行渲染,为组件元素附加事件。
hydrate 方法在实现渲染的时候,会复用原本已经存在的 DOM 节点,减少重新生成节点以及删除原本 DOM 节点的开销。
通过 react-dom 导入 hydrate
ReactDOM.hydrate(<Home/>, document.getElementById('#root'))
"dev:client-build": "webpack --config webpack.client.js --watch"
在响应给客户端的 HTML 代码中添加 script 标签,请求客户端 JavaScript 打包文件
<html>
<head>
<title> React SSRtitle>
head>
<body>
<div id="root">${content}div>
<script src="bundle.js">script>
body>
html>
服务器端程序实现静态资源访问功能,客户端 JavaScript 打包文件会被作为静态资源使用
app.use(express.static('public'))
服务器端 webpack 配置和客户端 webpack 配置存在重复,将重复配置抽象到 webpack.base.js 配置文件中
目的:使用一个命令启动项目,解决多个命令启动的繁琐问题,通过 npm-run-all 工具实现。
"dev": "npm-run-all --parallel dev:*"
问题:在服务器端打包文件中,包含了 Node 系统模块,导致打包文件本身体积庞大。
解决方案:通过 webpack 配置剔除打包文件中的 Node 模块。
const nodeExternals = require('webpack-node-externals')
const config = {
externals: [nodeExternals()]
}
module.exports = merge(baseConfig, config)
优化代码组织方式,渲染 React 组件代码是独立功能,所以把它从服务器端入口文件中进行抽离。
share/routes.js
import Home from './pages/Home'
import List from './pages/List'
export default [
{
path: '/',
component: Home,
exact: true
}, {
path: '/list',
component: List,
}
]
import React from 'react'
import {
renderToString} from 'react-dom/server'
import {
StaticRouter } from "react-router-dom";
import routes from '../share/routes'
import {
renderRoutes } from "react-router-config";
export default (req) => {
const content = renderToString(
<StaticRouter location={
req.path}>
{
renderRoutes(routes)}
</StaticRouter>
)
return `
React SSR
${
content}
`
}
添加客户端路由配置
import React from 'react'
import ReactDOM from 'react-dom'
import {
BrowserRouter } from "react-router-dom"
import {
renderRoutes } from "react-router-config";
import routes from '../share/routes'
ReactDOM.hydrate(
<BrowserRouter>
{
renderRoutes(routes)}
</BrowserRouter>
, document.getElementById('root'))
转移状态中的恶意代码
let response = {
data: [{
id: 1, name: ''}]
}
import serialize from 'serialize-javascript'
const initialState = serialize(store.getState())
npm init next-app next-guide
npm run dev
创建页面:
pages/list.js文件
export default function List () {
return (
<div>List Page</div>
)
}
可访问http://localhost:3000/list查看效果
文件与路由的关系:
pages/index.js => /
pages/list.js => /list
pages/post/first.js => /post/first
import Link from 'next/link'
export default function Home() {
return <div>
Index Page works
<Link href="/list"><a>Jump to List Page</a></Link>
</div>
}
应用程序根目录中的 public 文件夹用于提供静态资源。
访问形式可参照
public/images/1.jpg --> /images/1.jpg
public/css/base.css --> /css/base.css
通过 Head 组件修改元数据
import Head from ‘next/head’
<>
next app在 Next.js 中 内置了 styled-jsx, 它是一个 CSS-in-JS 库,允许在 React 组件中编写 CSS, CSS 仅作用于组件内部。
import Head from 'next/head'
import Link from 'next/link'
export default function Home() {
return <>
<Head>
<title>Index Page</title>
</Head>
<div>
Index Page works
<Link href="/list"><a className="demo">Jump to List Page</a></Link>
<img src="/images/1.jpeg" height="100" />
</div>
<style jsx>{
`
.demo {
color: red
}
`}</style>
</>
}
通过使用 CSS 模块功能,允许将组件的 CSS 样式编写在单独的 CSS 文件中.
CSS 模块约定样式文件的名称必须为"组件文件名称.module.css"
pages/list.js
import Head from "next/head";
import style from './list.module.css'
export default function List () {
return (
<>
<Head>
<title>List Page</title>
</Head>
<div className={
style.demo}>List Page</div>
</>
)
}
page/list.module.css
.demo {
color: green;
font-size: xx-large;
}
import '../styles/globals.css'
function MyApp({
Component, pageProps }) {
return <Component {
...pageProps} />
}
export default MyApp
styles/globals.css
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
background: tomato;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
预渲染是指数据和 HTML 的拼接在服务器端提前完成。
预渲染可以使 SEO 更加友好。
预渲染会带来更好的用户体验,可以无需运行 JavaScript 即可查看应用程序 UI 。
在 Next.js 中支持两种形式的预渲染:静态生成和服务器端渲染。
静态生成和服务器端渲染是生成 HTML 的时机不同。
静态生成:静态生成是在构建时生成 HTML 。以后的每个请求都共用构建时生成好的 HTML 。
服务器端渲染:服务器端渲染是在请求时生成 HTML 。每个请求都会重新生成 HTML
next.js 允许开发者为每个页面选择不同的渲染方式,不同的预渲染方式用不同的特点,应该根据场景进行渲染
但是大多建议页面使用静态生成
静态生成一次构建,反复使用,访问速度快,因为页面都是事先生成好的
使用场景: 营销页面、博客页面、电子商务产品列表、帮助和文档
服务端渲染访问速度不如静态生成快,但是由于每个请求都会重新渲染,适用于数据频繁更新的页面或者页面内容随请求的变化而变化的页面
无数据和有数据的静态生成
如果组件不需要再其他地方获取数据,直接进行静态生成
如果组件需要在其他地方获取数据,在构建时next.js会预先获取组件需要的数据,然后对组件进行静态生成
getStaticProps 方法的作用是获取组件静态生成需要的数据。并通过 props 的方式将数据传递给组件。
该方法是一个异步函数,需要在组件内部进行导出。
在开发模式下, getStaticProps 改为在每个请求上运行。
在生产模式下, getStaticProps 只会在构建的时候执行,而每次访问 /list 页面时不会再执行 getStaticProps 方法。
pages/list.js
import Head from "next/head";
import style from './list.module.css'
import {
readFile } from "fs";
import {
promisify } from "util";
import {
join } from "path";
const read = promisify(readFile)
export default function List ({
data}) {
return (
<>
<Head>
<title>List Page</title>
</Head>
<div className={
style.demo}>List Page</div>
<div>{
data}</div>
</>
)
}
export async function getStaticProps () {
let data = await read(join(process.cwd(), 'pages', '_app.js'), 'utf-8')
console.log(data) // 会在 node 环境下输出,这个函数会在构建时运行
return {
props: {
data
}
}
}
如果采用服务端渲染,需要在组件中导出 getServerSideProps 方法
将 list.js 中的 getStaticProps 方法 改成 getServerSideProps 方法,其中 getServerSideProps 还有个参数为 context.
开发模式下不执行 getServerSideProps 方法。
生产模式下:
list.js
import Head from "next/head";
import {
readFile } from "fs";
import {
promisify } from "util";
import {
join } from "path";
// 将 回调函数形式的方法转成 promise形式
const read = promisify(readFile);
export default function List({
data }) {
return (
<div>
<h1>list page working</h1>
<p>
{
data}
</p>
</div>
);
}
// 这里服务端渲染
export async function getServerSideProps() {
const data = await read(join(process.cwd(), "pages", "_app.js"), "utf-8");
console.log(data);
// 返回 props
return {
props: {
data,
},
};
}
实现:
创建基于动态路由的页面组件文件,命名时在文件名称外面加上 [],比如 [id].js
导出异步函数 getStaticPaths, 用于获取所有用户可以访问的路由参数
export async function getStaticPaths () {
// 此处获取所有用户可以访问的路由参数
return {
// 返回固定合适的路由参数
paths: [{
params: {
id: 1}}, {
params: {
id: 2}}],
// 当用户访问的路由有参数没有在当前函数中返回时,是否显示 404 页面, false 表示显示, true 表示不显示
fallback: false
}
}
导出异步函数 getStaticProps, 用于根据路由参数获取具体的数据
export async function getStaticProps ({
params}) {
// params -> {id: 1}
// 此处根据路由参数获取具体数据
return {
// 将数据传递到组件中进行静态页面的生成
props: {
}
}
}
注意: getStaticPaths 和 getStaticProps 只运行在服务器端,永远不会运行在客户端,甚至不会被打包到客户端 JavaScript 中,意味着这里可以随意写服务器端代码,比如查询数据库
[id].js
import {
route } from 'next/dist/next-server/server/router';
import {
useRouter } from 'next/router'
export default function Post({
data }) {
const router = useRouter()
if(router.isFallback) return <div>loading.............</div>
return (
<div>
<h1>{
data.id}</h1>
<h2>{
data.title}</h2>
</div>
);
}
export async function getStaticPaths() {
return {
paths: [
{
params: {
id: "1" },
},
{
params: {
id: "2" },
},
],
fallback: true,
};
}
export async function getStaticProps({
params }) {
const {
id } = params;
let data = {
};
switch (id) {
case "1":
data = {
id: "1", title: "文章一" };
break;
case "2":
data = {
id: "2", title: "文章二二二" };
break;
case "3":
data = {
id: "3", title: "文章33333" };
break;
default:
break;
}
return {
props: {
data
}
}
}
要创建自定义 404 页面,需要在 pages 文件夹中创建 404.js 文件
export default function Error () {
return <div>404 ~</div>
}
API Routes 可以理解为接口, 客户端向服务器端发送接口请求获取接口数据
Next.js 应用允许 React 开发者编写服务端代码创建数据接口
export default function (req, res) {
res.status(200).send({
id: 1, name: 'TOM'})
}
注意:当前 API Routes 可以接受任何 Http 请求方法
不要在 getStaticPaths 或 getStaticProps 函数中访问 API Routes, 因为这两个函数就是在服务器端运行的,可以直接写服务器端代码
npm init next-app movie
cd movie
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
npm install react-icons
npm install @emotion/babel-preset-css-prop -D
npm install @babel/core
npm run dev
访问: localhost:3000
pages/_app.js
// import '../styles/globals.css'
import {
ChakraProvider } from '@chakra-ui/react'
import theme from '@chakra-ui/theme'
function MyApp({
Component, pageProps }) {
return <ChakraProvider theme={
theme}>
<Component {
...pageProps} />
</ChakraProvider>
}
export default MyApp
npm install
npm run dev
数据服务地址IP: localhost:3005
在 axiosConfig.js 文件中导出 baseURL = ‘http://localhost:3005’
npm run export
serve out
package.json
"mydev": "nodemon server/index.js",
server/index.js
const express = require('express')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({
dev})
const handler = app.getRequestHandler()
// prepare 方法是准备一下 next 应用
app.prepare().then(() => {
const server = express()
server.get('/hello', (req, res) => {
res.send('Hello Next.js')
})
server.get('*', (req, res) => {
handler(req, res)
})
server.listen(3000, () => console.log('服务器启动成功,请访问: http://localhost:3000'))
})
Gatsby 是一个静态站点生成器官网