React服务端渲染-next.js

React服务端渲染-next.js

前端项目大方向上可以分为两种模式:前台渲染和服务端渲染。

前台渲染-SPA应用是一个主要阵营,如果说有什么缺点,那就是SEO不好。因为默认的HTML文档只包含一个根节点,实质内容由JS渲染。并且,首屏渲染时间受JS大小和网络延迟的影响较大,因此,某些强SEO的项目,或者首屏渲染要求较高的项目,会采用服务端渲染SSR。

Next.js 是一个轻量级的 React 服务端渲染应用框架。

熟悉React框架的同学,如果有服务端渲染的需求,选择Next.js是最佳的决定。

默认情况下由服务器呈现

自动代码拆分可加快页面加载速度

客户端路由(基于页面)

基于 Webpack 的开发环境,支持热模块替换(HMR)

官方文档

中文官网-带有测试题

初始化项目

方式1:手动撸一个

mkdir next-demo //创建项目

cd next-demo //进入项目

npm init -y // 快速创建package.json而不用进行一些选择

npm install --save react react-dom next // 安装依赖

mkdir pages //创建pages,一定要做,否则后期运行会报错

然后打开 next-demo 目录下的 package.json 文件并用以下内容替换 scripts 配置段:

"scripts": {

 "dev": "next",

 "build": "next build",

 "start": "next start"

}

运行以下命令启动开发(dev)服务器:

npm run dev // 默认端口为3000

npm run dev -p 6688 // 可以用你喜欢的端口

服务器启动成功,但是打开localhost:3000,会报404错误。

那是因为pages目录下无文件夹,因而,无可用页面展示。

利用脚手架:create-next-app

npm init next-app

# or

yarn create next-app

如果想用官网模板,可以在 https://github.com/zeit/next.js/tree/canary/examples 里面选个中意的,比如hello-world,然后运行如下脚本:

npm init next-app --example hello-world hello-world-app

# or

yarn create next-app --example hello-world hello-world-app

下面,我们来看看Next有哪些与众不同的地方。

Next.js特点

特点1:文件即路由

在pages目录下,如果有a.js,b.js,c.js三个文件,那么,会生成三个路由:

http://localhost:3000/a

http://localhost:3000/b

http://localhost:3000/c

如果有动态路由的需求,比如http://localhost:3000/list/:id,那么,可以有两种方式:

方式一:利用文件目录

需要在/list目录下添加一个动态目录即可,如下图:

image

方式二:自定义server.js

修改启动脚本使用server.js:

"scripts": {

   "dev": "node server.js"

 },

自定义server.js:

下面这个例子使 /a 路由解析为./pages/b,以及/b 路由解析为./pages/a

// This file doesn't go through babel or webpack transformation.

// Make sure the syntax and sources this file requires are compatible with the current node version you are running

// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel

const { createServer } = require('http')

const { parse } = require('url')

const next = require('next')

const dev = process.env.NODE_ENV !== 'production'

const app = next({ dev })

const handle = app.getRequestHandler()

app.prepare().then(() => {

 createServer((req, res) => {

   // Be sure to pass `true` as the second argument to `url.parse`.

   // This tells it to parse the query portion of the URL.

   const parsedUrl = parse(req.url, true)

   const { pathname, query } = parsedUrl

   if (pathname === '/a') {

     app.render(req, res, '/b', query)

   } else if (pathname === '/b') {

     app.render(req, res, '/a', query)

   } else {

     handle(req, res, parsedUrl)

   }

 }).listen(3000, err => {

   if (err) throw err

   console.log('> Ready on http://localhost:3000')

 })

})

特点2:getInitialProps中初始化数据

不同于前端渲染(componentDidMount),Next.js有特定的钩子函数初始化数据,如下:

import React, { Component } from 'react'

import Comp from '@components/pages/index'

import { AppModal, CommonModel } from '@models/combine'

interface IProps {

 router: any

}

class Index extends Component

 static async getInitialProps(ctx) {

   const { req } = ctx

   try {

     await AppModal.effects.getAppList(req)

   } catch (e) {

     CommonModel.actions.setError(e, req)

   }

 }

 public render() {

   return

 }

}

export default Index

如果项目中用到了Redux,那么,接口获得的初始化数据需要传递给ctx.req,从而在前台初始化Redux时,才能够将初始数据带过来!!!

特点3:_app.js和_document.js

_app.js可以认为是页面的父组件,可以做一些统一布局,错误处理之类的事情,比如:

页面布局

当路由变化时保持页面状态

使用componentDidCatch自定义处理错误

import React from 'react'

import App, { Container } from 'next/app'

import Layout from '../components/Layout'

import '../styles/index.css'

export default class MyApp extends App {

   componentDidCatch(error, errorInfo) {

       console.log('CUSTOM ERROR HANDLING', error)

       super.componentDidCatch(error, errorInfo)

   }

   render() {

       const { Component, pageProps } = this.props

       return (

           

               

                   

               

           )

   }

}

_document.js 用于初始化服务端时添加文档标记元素,比如自定义meta标签。

import Document, {

 Head,

 Main,

 NextScript,

} from 'next/document'

import * as React from 'react'

export default class MyDocument extends Document {

 static async getInitialProps(ctx) {

   const initialProps = await Document.getInitialProps(ctx)

   return { ...initialProps }

 }

 props

 render() {

   return (

     

       

         

         

         

         

           name="viewport"

           content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no,viewport-fit=cover"

         />

         

         

       

       

         

) }}特点4:浅路由如果通过或者做路由跳转,那么,目标页面一定是全渲染,执行getInitialProps钩子函数。浅层路由允许改变 URL但是不执行getInitialProps 生命周期。可以加载相同页面的 URL,得到更新后的路由属性pathname和query,并不失去 state 状态。因为浅路由不会执行服务端初始化数据函数,所以服务端返回HTML的速度加快,但是,返回的为空内容,不适合SEO。并且,你需要在浏览器钩子函数componentDidMount 中重新调用接口获得数据再次渲染内容区。浅路由模式比较适合搜索页面,比如,每次的搜索接口都是按照keyword参数发生变化:/search?keyword=a 到/search?keyword=b使用方式如下:const href = '/search?keyword=abc'const as = hrefRouter.push(href, as, { shallow: true })然后可以在componentdidupdate钩子函数中监听 URL 的变化。componentDidUpdate(prevProps) { const { pathname, query } = this.props.router const { keyword } = router.query if (keyword) { this.setState({ value: keyword }) ... }}注意:浅层路由只作用于相同 URL 的参数改变,比如我们假定有个其他路由about,而你向下面代码样运行:Router.push('/?counter=10', '/about?counter=10', { shallow: true })那么这将会出现新页面,即使我们加了浅层路由,但是它还是会卸载当前页,会加载新的页面并触发新页面的getInitialProps。Next.js踩坑记录踩坑1:访问window和document对象时要小心!window和document对象只有在浏览器环境中才存在。所以,如果直接在render函数或者getInitialProps函数中访问它们,会报错。如果需要使用这些对象,在React的生命周期函数里调用,比如componentDidMountcomponentDidMount() { document.getElementById('body').addEventListener('scroll', function () { ... }) }踩坑2:集成antd集成antd主要是加载CSS样式这块比较坑,还好官方已经给出解决方案,参考:https://github.com/zeit/next.js/tree/7.0.0-canary.8/examples/with-ant-design多安装4个npm包:"dependencies": { "@zeit/next-css": "^1.0.1", "antd": "^4.0.4", "babel-plugin-import": "^1.13.0", "null-loader": "^3.0.0", },然后,添加next.config.js 和 .babelrc加载antd样式。具体配置参考上面官网给的例子。踩坑3:接口鉴权SPA项目中,接口一般都是在componentDidMount中调用,然后根据数据渲染页面。而componentDidMount是浏览器端可用的钩子函数。到了SSR项目中,componentDidMount不会被调用,这个点在踩坑1中已经提到。SSR中,数据是提前获取,渲染HTML,然后将整个渲染好的HTML发送给浏览器,一次性渲染好。所以,当你在Next的钩子函数getInitialProps中调用接口时,用户信息是不可知的!不可知!如果用户已经登录,getInitialProps中调用接口时,会带上cookie信息如果用户未登录,自然不会携带cookie但是,用户到底有没有登录呢???getInitialProps中,你无法通过接口(比如getSession之类的API)得知要知道,用户是否登录,登录用户是否有权限,那必须在浏览器端有了用户操作之后才会发生变化。这时,你只能在特定页面(如果只有某个页面的某个接口需要鉴权),或者在_app.js这个全局组件上添加登录态判断:componentDidMount中调用登录态接口,并根据当前用户状态做是否重定向到登录页的操作。踩坑4:集成 typescript, sass, less 等等都可以参考官网给出的Demo,例子十分丰富:https://github.com/zeit/next.js/tree/7.0.0-canary.8/examples小结Next.js的其他用法和React一样,比如组件封装,高阶函数等。demo code: https://github.com/etianqq/next-app

发表评论 (审核通过后显示评论):

昵称:
邮箱:
内容: