今天看啥  ›  专栏  ›  sunxiaoying123

SSR之next.js--篇一

sunxiaoying123  · 掘金  ·  · 2019-10-22 15:49
阅读 8

SSR之next.js--篇一

1、名词解释

       SSR(server-side-render),根据名称我们都知道叫做服务端渲染,我相信对于工作几年的前端开发者肯定很熟悉这个名词,多少也都用过,在这里,我想说的是针对咱们刚刚踏入前端圈的朋友们的一点总结和感悟,希望我们能够不断的提高对next的认知,相互进步,相互成长!这就是我们的目标~
  • 服务端渲染就是页面的渲染和生成由服务器来完成,并将渲染好的页面返回客户端
  • 客户端渲染就是页面的生成和数据的渲染过程是在客户端(或浏览器或APP)完成

其中掘金就使用vue的ssr功能做了全栈服务端渲染,切效果良好。

那我们用next.js的优点是什么呢?

2、next.js的特性

  • 默认服务端渲染模式,以文件系统为基础的客户端路由
  • 代码自动分割使得页面加载更快
  • (以页面为基础)的简介的客户端路由
  • 以webpack的热替换为基础的开发环境
  • 使用React的JSX和ES6的module,模块化和维护更方便
  • 可以运行在Express和其他node.js的http服务器上
  • 可以定制化专属的babel和webpack配置
那我们开始上手吧,hello world!

3、hello world

照做以下步骤吧~复制代码


接着打开package.json添加如下代码,如下:

{
  "scripts": {
    "dev": "next"
}}复制代码

到这里位置项目的准备工作已完成,运行以下命令开启项目服务器

npm run dev复制代码

执行完毕后,在浏览器打开localhost:3000,就会看到以下页面:


这是next.js默认生成的404页面,儿开启服务后访问的之所以是现在的404页面,是因为我们还没有设置项目主页。

那接下来就是创建页面了~~~啦啦啦~~~我学next我开心~~~

4、创建页面

next.js是从服务器生成页面,再返回给前端展示。

重点内容开始了哦~~~~复制代码

  • next.js默认从pages目录下取页面进行渲染返回给前端展示,并默认取pages/index.js为系统是首页进行展示
  • 注意,pages是默认存放页面的目录,路由的根路也是pages目录。

我们在pages/index.js中创建一个React函数式组件:

const Index = () => (
    <div>
        <p>小英 study next.js</p>
    </div>
)
export default Index复制代码

next.js默认使用webpack构建项目,webpack的热部署功能一样能提升开发效率。创建完pages/index.js后,再访问http://localhost:3000即可看到设置好的页面。


很好,不在是我们不喜欢的404了,看到内容了啊~~

再来构建下多页面的吧~

使用next.js的目的就是构建非SPA的多页面项目,下面开始创建第二个页面。

在pages目录下创建文件pages/content.js

export default () => (  <div>    <p>这是content 页面</p>  </div>)复制代码

打开路由localhost:3000/content,看到我们新建的第二个页面了。


所以现在我们明白所有的路由都是通过后端服务器来控制的 ,要想实现客户端路由,需要借助next.js的Link API,所以接下来我们看下Link API吧~~

4、Link API

从next/link中可以引用到Link组件。在pages/index.js文件中引用Link,修改如下:

import Link from 'next/link'const Index = () => (  <div>    <Link href="/content">      <a>去往content页面</a>    </Link>    <p>小英 study next.js</p>  </div>)export default Index复制代码

我们使用的Link组件,其实可以当做a标签使用,最终渲染的时候也是a标签,页面渲染如下:


点击超链接,即可进入到content页面。下图是实际渲染的结果:


结论:link组件是通过location.history的浏览器API保存历史路由,所以,可以通过浏览器左上角的前进后退按钮来切换历史路由。而在开发过程中,我们不需要单独写客户端路由的配置。


5、组件复用

next.js是以多页面为中心,只要将页面文件放在pages目录下,就可以通过浏览器上以文件名为路由名来访问到,相反,只要不想让用户通过页面直接访问的组件,都不放在pages目录下,开发者想放在哪个目录自定义即可。

接下来,我们建一个components目录,里面建一个公共的Header组件,用于头部导航,通过导航可以再页面间切换。

import Link from 'next/link'const linkStyle = {  marginRight: 15}const Header = () => (  <div>    <Link href="/">      <a style={linkStyle}>首页</a>    </Link>    <Link href="/content">      <a style={linkStyle}>内容</a>    </Link>  </div>)export default Header复制代码

好了,定义完组件,我们在pages/index.js里面引用看一下:

import Header from '../components/Header.js'const Index = () => (  <div>    <Header />    <p>小英 study next.js</p>  </div>)export default Index复制代码

在 pages/content.js 中同样引入 Header 组件,在浏览器上通过点击导航切换页面。

import Header from '../components/Header.js'export default () => (  <div>    <Header/>    <p>这是content 页面</p>  </div>)复制代码

这样就可以再index和content两个页面来回切换了


进一步我们在封装下Header组件,Layout组件,包含我们的Header和页面content的组件;


/**Layout**组件/
import Header from './Header'const layoutStyle = {  margin: 0,  padding: 0,  border: '1px solid #DDD'}const Layout  = (props) => (  <div style={layoutStyle}>    <Header/>    {props.children}  </div>)export default Layout复制代码

/**在index中使用Layout组件**/
import Layout from '../components/Layout.js'import Link from 'next/link'const linkArr = [  '我是链接1',  '我是链接2',  '我是链接3',  '我是链接4',  '我是链接5',  '我是链接6',]const LinkContent = (props) => (  <li>    <Link href={`tolink?title=${props.title}`}>    <a>{props.title}</a>    </Link>  </li>)const Index = () => (  <Layout>    <h1>这是我的地盘</h1>    <ul>    {      linkArr.map(item => {        return  <LinkContent title={item} />      })    }    </ul>  </Layout>)export default Index复制代码

页面渲染如下:(以下相当于是列表页,点击列表中的链接相当于到详情页,以下就形成了动态页面之间的跳转)


/**详情页**/
import Layout from '../components/Layout.js'export default (props) => (  <Layout>    <h1>{props.url.query.title}</h1>    <p>这是不同title对应的详情页</p>  </Layout>)复制代码


总结:使用next.js创建动态页面,与使用React和Vue创建一个spa的页面大体相同,区别就是页面的渲染主题不同
  • 前者是nodejs服务器获取到后端数据渲染完页面后再返回给前端展示
  • 后者是前端先获取页面主体架构,再通过ajax的方式请求后端的数据,在前端渲染展示

6、Route Masking

next.js提供一个独特的特性:路由遮盖(Route Masking)。它可以使得在浏览器上显示的是路由A,而在app内部实际显示的是路由B。这个特性可以使我们的路由简洁,以上边为例,地址栏显示的是http://localhost:3000/tolink?title=%E6%88%91%E6%98%AF%E9%93%BE%E6%8E%A56,这个地址含有个title参数,看着很不整洁吗,接下来我们就要next改造路由,目标是改成http://localhost:3000/p/xiaoying

这里我们要用一个Link组件的as属性,并给组件添加一个id属性:(以index为例)

import Layout from '../components/Layout.js'import Link from 'next/link'const linkArr = [  '我是链接1',  '我是链接2',  '我是链接3',  '我是链接4',  '我是链接5',  '我是链接6',]const LinkContent = (props) => (  <li>    <Link     as={`p/${props.id}`}    href={`tolink?title=${props.title}`}>    <a>{props.title}</a>    </Link>  </li>)const Index = () => (  <Layout>    <h1>这是我的地盘</h1>    <ul>    {      linkArr.map((item,index) => {        return  <LinkContent id={index} title={item} />      })    }    </ul>  </Layout>)export default Index复制代码


But。。。。我们刷新下页面,咦怎么404了呢???


结论:

  • 当在 Link 组件上使用 as 属性时,浏览器上显示的是 as 属性的值,走的是客户端路由,而服务器真正映射的是 href 属性的值,走的是服务端路由。
  • 显示 404页面,是因为路由遮盖默认只在客户端路由中有效,要想在服务端也支持路由遮盖,需要在服务端单独设置路由解析的方法。

7、服务端支持路由遮盖

下面以express为例创建后端服务器讲解如何设置服务器来支持路由遮盖。

先来安装一个express

npm install --save express复制代码

在项目目录下创建server.js文件

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('/p/:id', (req, res) => {    const actualPage = '/tolink'    const queryParam = {      title: req.param.id    }    app.render(req, res, actualPage, queryParam)  })  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.log(ex.stack)  process.exit(1)})复制代码

/**index.js**/
import Layout from '../components/Layout.js'import { withRouter } from "next/router";class ToLink extends React.Component {
// 没有该函数是不能实现服务端路由遮盖的  static getInitialProps ({ query: { title } }) {    return { title }  }  render() {    let { title } = this.props    return (      <Layout>        <h1>{title}</h1>        <p>这是不同title对应的详情页</p>      </Layout>    )  }} export default withRouter(ToLink)复制代码

这时在tolink页面刷新的时候就能够正常显示页面了,这里强调一点,我在参考一些文章的时候,没有提到加 getInitialProps这个方法,就说页面刷新就能正常显示是不正确的,本人多次试验,页面本身的内容是可以正常显示的,但是根据路由获取的title参数是一直拿不到的,加了这个参数就会先去获取参数,这样页面才能完整显示~~~以下提供截图证明:


所以,接下来我们就说说这个静态的方法:

8、接口请求

next.js在react的基础上为组件新增了一个特性:getInitialProps, 同于获取并处理组件的属性,返回组件的默认属性。我们可以在该方法中请求数据,获取页面需要的数据并渲染返回给前端页面。(上边的服务端路由遮盖也是用了这个方法,原理就是获取组件的默认属性

引入一个支持在客户端和服务端发送fetch请求的插件isomorphic-unfrtch, 当然我们也可以用axios等其他工具。

npm install --save isomorphic-unfetch复制代码

然后修改pages/index.js:

import Layout from '../components/Layout.js'import { withRouter } from "next/router";import fetch from 'isomorphic-unfetch'import Link from 'next/link'const LinkContent = (props) => {  console.log(props)  return (    <li>      <Link       as={`/p/${props.id}`}      href={{pathname: '/tolink', query: {title: props.title}}}>      <a>{props.title}</a>      </Link>    </li>  )}class Index extends React.Component {  static async getInitialProps(){    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    }  }  render() {    const { shows } = this.props    return(      <Layout>      <h1>这是我的地盘</h1>      <ul>      {        shows.map(( { show } ) => {          return  <LinkContent key={show.id} id={show.id} title={show.name} />        })      }      </ul>    </Layout>  )     }}export default withRouter(Index)复制代码

在getInitialProps中我们使用async await关键字异步获取我们需要的参数shows,这样数据就是动态获取的,从而实现动态渲染。注意:getInitialProps不能使用在子组件中。只能使用在pages页面中。

结论如下:
  • 当页面渲染时加载数据,我们使用了一个异步方法getInitialProps。它能异步获取 JS 普通对象,并绑定在props上
  • 当服务渲染时,getInitialProps将会把数据序列化,就像JSON.stringify。所以确保getInitialProps返回的是一个普通 JS 对象,而不是Date, Map 或 Set类型。
  • 当页面初始化加载时,getInitialProps只会加载在服务端。只有当路由跳转(Link组件跳转或 API 方法跳转)时,客户端才会执行getInitialProps。


结论3在这里给大家做个验证:

servert.js, 这是我在server.js中添加的一个测试id,只有在服务端渲染的时候,我们的tolink页面才会获取的这个id


pages/tolink.js,我们在该页面的 getInitialProps方法中打印一下id


这个结果是我在刷新tolink页面,或者初始化加载的时候,在终端(服务端)打印的结果,有“测试id”这几个字;


继续往下看:


这是我通过首页的列表Link的方式点击进来这个页面,在浏览器控制台打印的结果,很明显这是客户端走的这个方法,是没有”测试id“四个字的,而是undefined,所以这也就验证了结论3的正确性~~~~,(其实我刚开始也是不懂这个意思,现在明白了,希望正在看的你也能明白哦~~)

9、部署

Next.js 项目的部署,需要一个 Node.js的服务器,可以选择 Express, Koa 或其他 Nodejs 的Web服务器。本文中以 Express 为例来部署 Next 项目。

为了区分部署环境,我们需要在 package.json 中修改 script 属性如下:

"scripts": {
  "build": "next build",
  "start": "NODE_ENV=production node server.js -",
  "dev": "NODE_ENV=dev node server.js"
}复制代码

其中,build 命令是用于打包项目,start 命令是用于生产环境部署,dev 命令是用于本地开发。

执行如下命令即可将 Next项目 部署到服务器:

npm run build
npm run start
复制代码

执行完命令后,可在 http://host:3000 访问。其中,host 是指服务器的IP地址。

总结:next.js部署生成环境,必须用build命令打包构建,然后再用start命令部署。


next.js还有很多内容需要我们不断总结和采坑,未完待续。。。。。。😃😄





原文地址:访问原文地址
快照地址: 访问文章快照