vue的服务端渲染(nuxtjs)

什么是服务端渲染?

 

?传统的web开发体验

 

传统web开发

 

通过express来写一个案例

 

npm init -y

 

npm i express -S

 

const express = require('express')
const app = express()

app.get('/',function(req,res){
    res.send(`
        <html>
            <body>
                <div id="app">
                    <h1>开课吧</h1>
                    <p class="demo">开课吧还不错</p>
                </div>
            </body>
        </html>
    `)
})

app.listen(3000,()=>{
    console.log(`服务运行在http://localhost:3000`)
})

 

检验方法:打开页面,查看源码

 

检验是否是传统开发

 

?spa时代

 

到了vue,react时代,单页面应用优秀的用户体验,逐渐成为了主流,页面整体是js渲染出来的,称之为客户端渲染

 

单页面应用结构图

 

检验方法:打开页面,查看源码

 

单页面效果

 

单页面缺点(主要有两个):

 

首屏渲染性能:必须得等到js加载完毕,才能渲染出首屏

 

seo:爬虫只能拿到一个id为app的div,会认为页面是空的,不利于div

 

为了解决上面的两个问题,于是出现了SSR

 

?ssr

 

后端渲染出完整的首屏的dom结构返回,前端拿到的内容带上首屏,后续的页面操作,再用单页的路由跳转和渲染,称之为服务端渲染(server side render)

 

ssr渲染流程图

 

注意:

 

ssr一般只做首屏的!

 

ssr是在传统的web和spa之间取了折中!

 

ssr体验(nuxt.js)知识点

 

?Nuxt.js是一个基于Vue.js的通用型框架。

 

通过对客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染

 

结论:

 

nuxt.js不仅仅用于服务端渲染也可用于spa应用开发

 

利用nuxt提供的基础项目结构,异步数据加载,中间件支持,布局等特性可大幅提高开发效率

 

nuxt可用以网站静态化(nuxt有命令可以打包成静态文件,一般用于访问次数最多的页面,技巧:数据更新触发打包命令

 

?nuxt.js特性

 

基于vue.js

 

自动代码分层

 

服务端渲染

 

强大的路由功能,支持异步数据

 

静态文件服务

 

es6/es7语法支持

 

打包和压缩js和css

 

html头部标签管理

 

本地开发支持热加载

 

集成ESlint

 

支持各种样式预处理器:sass,less,stylus

 

支持http/2推送

 

?nuxt渲染流程

 

一个完整的服务器请求到渲染流程

 

服务器请求渲染流程

 

?nuxt安装

 

运行 npx create-nuxt-app 项目名

 

运行项目:npm run dev

 

目录结构:

 

nuxt目录结构

 

约定于优先配置

 

pages目录下所有的*.vue文件生成应用的路由配置(自动生成路由配置),新建pages/users.vue

 

<template>
  <div class="container">
      用户列表
  </div>
</template>

<script>
export default {
  
}
</script>

<style>

</style>

 

会在.nuxt目录下自动配置router

 

打开页面: http://localhost:3000/users

 

自动配置的路由效果

 

路由

 

?导航

 

虽说和vue的router-link差不多,但是如果我们使用了nuxt,那么为了后期的一些优化我们推荐使用nuxt-link

 

index.vue里写

 

<nuxt-link to="/users">跳转到用户列表</nuxt-link>

 

路由效果

 

?子路由和路由导航

 

修改pages目录结构

 

pages目录结构

 

效果:

 

效果

 

自动生成目录.nuxt下仔细查看router.js的内容:

 

nuxt的路由配置

 

结论:

 

name的命名:文件所在目录-文件名

 

改造主页的index.vue

 

<template>
  <div class="container">
    
    <nuxt-link to="/users">跳转到用户列表</nuxt-link>
    <el-button @click="$router.push('/users')">用户列表</el-button>
    <el-button @click="$router.push({name:'users-detail'})">用户详情</el-button>
  </div>
</template>

 

而我们是安装选项时集成了elementUI的

 

路由导航

 

结果点击按钮可以正常跳转

 

?动态路由

 

动态路由的做法

 

根据上面的方法可以自动配置一个动态路由

 

再来改在首页的index.vue和_id.vue 以及detail.vue

 

//首页index.vue
<template>
  <div class="container">
    
    <nuxt-link to="/users">跳转到用户列表</nuxt-link>
    <el-button @click="$router.push('/users')">用户列表</el-button>
    <el-button @click="$router.push({name:'users-detail',query:{id:2}})">用户详情</el-button>
    <el-button @click="$router.push({name:'users-id',params:{id:1}})">用户详情</el-button>
  </div>
</template>

 

//_id.vue
<template>
  <div class="container">
      用户id:{{ $route.params.id }}
  </div>
</template>

 

//detail.vue
<template>
  <div class="container">
      用户id:{{ $route.query.id }}
  </div>
</template>

 

页面参数的效果(query)

 

detailvue效果

 

路由参数的效果(params)

 

_id.vue的效果

 

?嵌套路由

 

构造的目录结构要如下才会生成嵌套路由

 

嵌套路由目录在pages根目录下建立users.vue

 

目录结构如下

 

目录结构

 

生成的路由配置如下

 

export const routerOptions = {
  mode: 'history',
  base: decodeURI('/'),
  linkActiveClass: 'nuxt-link-active',
  linkExactActiveClass: 'nuxt-link-exact-active',
  scrollBehavior,

  routes: [{
      path: "/users",
      component: _c8a52a2e,
      children: [{
        path: "",
        component: _228b8179,
        name: "users"
      }, {
        path: "detail",
        component: _7190858c,
        name: "users-detail"
      }, {
        path: ":id",
        component: _a064e3be,
        name: "users-id"
      }]
    }, {
      path: "/",
      component: _564390b3,
      name: "index"
    }],

  fallback: false
}

 

users.vue里要写路由展示区域nuxt.child

 

<template>
    <div>
        <nuxt-child />
    </div>
</template>

 

如此就可以正常使用了!

 

?路由守卫

 

plugin目录下建立router.js

 

export default ({app}) => {
    app.router.beforeEach((to,from,next)=>{
        console.log(`我要去${to.path}`)
        next()
    })
}

 

nuxt.config.js里注册这个方法

 

  plugins: [
    '@/plugins/element-ui',
    '@/plugins/router'
  ],

 

自定义布局

 

default.vue是会匹配所有的页面,在上面添加内容会增加到所有页面,所以如果有一个所有页面都要共享的组件就写在default.vue里

 

layout目录下建立users.vue,注意必须有<nuxt/> 否则会替换掉整个页面

 

  <template>
     <div>
         用户页面自定义布局
         <nuxt/>
     </div>
 </template>

 

再去pages下的users指定一下使用哪个自定义布局,通过layout:'自定义布局的组件名'

 

<template>
    <div>
        <nuxt-child />
    </div>
</template>

<script>
    export default {
        layout: 'users'
    }
</script>

 

错误页面

 

其实nuxt本身自带错误页面的(优点丑,而且错了就啥都看不见),所以我们自定义一个

 

在layout里新建error.vue

 

<template>
    <div class="container">
        <h1 v-if="error.statusCode == 404">页面找不到</h1>
        <h1 v-else>应用发生错误异常</h1>
        <nuxt-link to="/">首页</nuxt-link>
    </div> 
</template>

<script>
    export default {
        props:['error']
    }
</script>

 

在pages下的users.vue里加上{{ foot.bar }},注意,这是不存在的,为了让程序出错

 

<template>
    <div>
        <nuxt-child />
        {{ foot.bar }}
    </div>
</template>

<script>
    export default {
        layout: 'users'
    }
</script>

 

错误一场

 

样式有点丑,可以自己修改的

 

页面(涵盖异步数据获取)

 

例如定义data数据啥的,和vue是一模一样的!!,不多说

 

值得注意的是,我们可以设置一些其他的信息,例如页面标题

 

<script>
export default {
  head:{
      title:'页面详情'
  }
}
</script>

 

效果:

 

head的使用

 

可以覆盖掉全局的seo信息

 

?异步数据的获取

 

asyncData方法使得我们可以在设置组件的数据之前就能异步获取或处理数据

 

asyncData//这个会在beforeCreate生命周期钩子之前执行

 

注意他会在客户端和服务端两端执行

 

在pages下的users.vue里写

 

<template>
    <div>
        <nuxt-child />
        <ul>
            <li v-for="user in users" :key="user.id" >{{ user.name }}</li>
        </ul>
    </div>
</template>

<script>
    function getUsers() {
        return new Promise(resolve=>{
            setTimeout(()=>{//模拟异步请求
                resolve([{name:'tom',id:1},{name:'jon',id:2}])
            },1500)
        })
    }
    export default {
        layout: 'users',
        async asyncData() {//这个会在beforeCreate生命周期钩子之前执行
            console.log(process.server); //返回true就是在服务端执行了
            //使用async await
            const users = await getUsers();
            //return的数据会和data合并
            return { users };
        },
    }
</script>

 

注意事项:

 

注意事项

 

?上下文对象的使用:

 

在pages下的users下的detail.vue中

 

<script>
export default {
  head:{
      title:'页面详情'
  },
  async asyncData({ query,error }) {//这个会在beforeCreate生命周期钩子之前执行
        console.log(process.server); //返回true就是在服务端执行了
        if ( query.id ){
            return { user: {name:'tom'} }
        }
        error({ statusCode:400,message:'请传递用户id' })
        
        }
}
</script>

 

users

 

不传入id就爆了我们需要的错误

 

?整合axios

 

如何安装nuxt是没选择axios,那么我们就要整合一下了!

 

npm install @nuxtjs/axios -S

 

再在nuxt.config.js里配置,用来解决跨域的问题

 

modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
  ],
  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {
    proxy: true
  },
  proxy:{
    "/api": "http://localhost:3001/"
  },

 

当然如果所有页面都启用了ssr那么自然也就不需要跨域了,但通常大家都是首屏启用了ssr,其他页面仍是单页面,所有应当配置跨域

 

server目录下建立api.js,定义我们的后端接口,需要安装koa,koa-router,全局安装nodemon,装了就不需要

 

const koa = require("koa")
const app =new koa()
const Router = require("koa-router")
const router = new Router({prefix:'/api/users'}) //请求地址

const users = [{name:'tom',id:1},{name:'jey',id:2}]

router.get('/:id',ctx => {//带个参数id
    const user = users.find(u => u.id == ctx.params.id)
    if(user){
        ctx.body = {ok:1,user}
    }else{
        ctx.body = {ok:0}
    }
})

app.use(router.routes())
app.listen(3001)

 

nodemon api.js来启动这个服务

 

_id.vue中

 

<template>
  <div class="container">
      用户id:{{ $route.params.id }}
      <!-- {{ foot.bar }} -->
      {{ user.name }}
  </div>
</template>

<script>
export default {
  async asyncData({ params,$axios }) {
    //注意返回的就是响应数据,因为是被封装过的(讲课的说的,我测试结果不是!)
    const data = await $axios.get(`/api/users/${params.id}`)
    if(data.data.ok){
      return { user:data.data.user }
    }
    error({ statusCode:400,message:"id有误,查询失败" })
  },
}
</script>

 

注意我注释掉的东西,效果如下

 

数据请求使用代理效果

 

?拦截器实现

 

nuxt.config.js里配置

 

  plugins: [
    '~/plugins/axios'
  ],

 

在plugins目录下建立axios.js

 

//前端请求自动加上token
export default function({ $axios }){
    //onRequest为请求拦截器帮助方法
    $axios.onRequest(config => {
        if(!process.server){
            // config.headers.token = localStorage.getItem('token')//将来是这个操作
            config.headers.token = 'jiba'  
        }
    })
}

 

这样就完成了一个拦截器,效果看下图network的token

 

封装拦截器toekn

 

vuex

 

应用根目录下如果存在store目录,Nuxt.js将启用vuex状态树。

 

定义各状态树时具名导出state,mutations,getters,actions即可

 

注意:也是直接用的,不需要去配置什么,详情看下面教程

 

在store目录下建立index.js

 

//因为是具名导出,所以state必须是state,mutations必须是...
export const state = () => ({
    count: 0
})

export const mutations = {
    increment(state) {
        state.count ++
    }
}

 

在store目录下建立users.js

 

export const state = () => ({
    list: []
})

export const mutations = {
    set(state,list) {
        state.list = list;
    },
    add(state,name) {
        state.list.push({name})
    }
}

 

在pages目录下建立mine.vue

 

<template>
    <div>
        我的
        <p @click="increment">计数:{{count}}</p>
        <p><input type="text" placeholder="添加用户" @keyup.enter="add($event.target.value)"></p>
        <ul>
            <li v-for="user in list" :key="user.id">{{user.name}}</li>
        </ul>
    </div>
</template>

<script>
    import {mapState,mapMutations} from 'vuex'

    function getUsers() {//提供一个接口
        return new Promise(resolve => {
            setTimeout(() => {
                resolve([{name:'tom',id:1},{name:'jb',id:2}])
            },1500)
        })
    }
    export default {
        fetch({store}) {//这个fetch不是请求的fetch,这是一个钩子,执行时间早于data,组件还没创建
            //fetch在创建组件前执行填充状态树
            //提交时注意命名空间
            return getUsers().then(users => store.commit("users/set",users))//对应store的users下的set
        },
        computed: {
            ...mapState(["count"]),
            ...mapState("users",["list"])
        },
        methods: {
            ...mapMutations(["increment"]),
            ...mapMutations("users",["add"])
        },
    }
</script>

 

注意:

 

1.此fetch非彼fetch

 

2.store下的index.js是基石,store会自动生成为这个样子

 

store

 

3.fetch的commit后的有规则

 

Vue SSR 实战首屏渲染(当然比起nuxt这样很麻烦)

 

?先创建个vue的项目

 

vue create ssr

 

?安装vsr(vue的server渲染器)

 

npm install vue-server-renderer --save

 

?一个简单的实例,单独建立个server.js

 

const express = require('express')
const vue = require('vue')

const app = express()
const renderer = require('vue-server-renderer').createRenderer()
const page = new vue({
    data:{
        name: "开课吧",
        count: 1
    },
    template: `
        <div>
            <h1>{{name}}</h1>
            <h1>{{count}}</h1>
        </div>
    `
})

app.get('/',async function(req,res){//进入这个页面会将组件转为字符串发给前端在页面
    const html = await renderer.renderToString(page)
    res.send(html)
})

app.listen(3000,()=>{
    console.log('启动成功')
})

 

效果会是这样

 

 

一个简单的ssrdemo

 

?在vue中构建步骤

 

ssr渲染过程

 

首先webpack会把资源会打包成两个包,一个服务,一个客户端,用户访问服务后根据客户端知道找谁,把相应的html渲染出来

 

通常前端都是vue单文件组件,用vue-loader构建,所以ssr环境需要webpack怎么操作呢?下面讲解

 

路由vue-router

 

单页面应用的页面路由,都是前端控制,后端只负责提供数据

 

一个简单的单页应用,使用vue-router,为了方面前后端公用路由数据,我们新建router.js,对外暴露createRouter

 

npm i vue-router -S

 

在src下建立router.js

 

import Vue from 'vue'
import Router from 'vue-router'
import Index from './components/Index'
import Kkb from './components/Kkb'
Vue.use(Router)

export function createRouter () {
    return new Router({
        routes: [
            {path: '/',component: Index},
            {path: '/kkb',component: Kkb}
            //...
        ]
    })
}

 

在components目录下建立Index.vue组件和Kkb.vue

 

//index
<template>
    <div>
        <h1>hi,{{ name }}</h1>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: '首页'
            }
        },
    }
</script>

 

//kkb
<template>
    <div>
        <h1>hi,{{ name }}</h1>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: '开课吧'
            }
        },
    }
</script>

 

再App.vue中使用路由

 

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <ul>
      <li><router-link to="/">首页</router-link></li>
      <li><router-link to="/kkb">开课吧</router-link></li>
    </ul>
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'app',
  components: {
    
  }
}
</script>

<style lang="scss">
</style>

 

csr和ssr设置统一入口

 

src下建立createapp.js

 

import Vue from 'vue'
import App from './App.vue'

import { createRouter } from './router'

export function createApp (context) {//context是服务器传来的请求对象
    const router = createRouter()
    const app = new Vue({
        router,
        context,
        render: h => h(App)
    })
    return { app,router }
}

 

main.js里这么做

 

import Vue from 'vue'
import App from './App.vue'
import { createApp } from './createapp'

const { app,router } = createApp()
Vue.config.productionTip = false

router.onReady(()=>{
  app.mount("#app")
})

// new Vue({
//   render: function (h) { return h(App) },
// }).$mount('#app')

 

再在src下建立entry-server.js

 

import { createApp } from './createapp'

export default context => {
    //我们返回一个promise
    //确保路由或者组件准备就绪
    return new Promise((resolve,reject) => {
        const { app,router } = createApp(context)
        router.push(context.url)
        router.onReady(()=>{
            resolve(app)
        },reject)
    })
}

 

服务端渲染,我们需要能够处理.vue组件,所以需要webpack的支持

 

后端加入webpack

 

npm install cross-env vue-server-renderer webpack-node-externals
lodash.merge --save

 

这讲的就是个大致的思路,用起来非常麻烦,推荐使用nuxt.js。后续还会更新,有空就更新

 

 

 

转载于:https://www.cnblogs.com/h5hu/articles/11531141.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值