vue-ssr

本文围绕vue-ssr展开,介绍其优势,如与前端无缝连接、首屏展示数据、利于SEO,但并非所有情况适用。还提及学习难度,分享从0搭建项目的经验。同时阐述了Vuex的必要性、解决的问题及核心概念,如Mutations、State、Getters等,最后介绍了简单的server-render。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要用vue-ssr?

前端用的是vue, 后端渲染用vue-ssr,可以无缝的和前端连接起来
使用vue-ssr可以把数据渲染成HTML, 并在首屏展示, 用户体验好, 传统的前端vue, 服务器第一次请求只返回#app的空DOM, 当js和ajax请求完成, 才会展示, 体验差
利于SEO

是所有情况都适用vue-ssr吗?
当然不是, 他的最最主要作用是首屏渲染, 其他都是次要的, 比如有3个tab页签, 只有第一个页签是首屏展示的, 其他两个是通过点击才展示数据, 那这样就没有必要把另外两个页签的数据也取出来, 做vue-ssr, 这样会增加服务器端的压力和流量, 这个后面会说到

vue-ssr很容易上手吗?
说实话, 不是很容易, 虽然现在网上的例子很多, 官方也有一个例子vue-hackernews, 但是官方给出的例子太复杂, 属于大而全的例子, 不太适合原理不太清楚的新手, 网上的例子一般都是半个流程, 比如只告诉你渲染简单的模板, 根本不会把项目中用到的整个流程都串起来, 而且机理性的东西的文章也少,增加了学习的难度。

说了这么多,你写的东西能干啥?
这个系列文章是从头开始搭建整个项目的,从一个实际的一个简单的场景,告诉你怎么样用client端渲染,怎么在server端取数据,并传递给前端,达到数据共享,以及告诉你用ssr时踩的一些坑,如何解决的。我搞这个也搞了一段时间,网上资料也查了好多,我也算是集各家之所长吧,尽量写的详细些,帮忙各位能从0开始搭建起来。

技术栈是什么?
vue2+webpack2+vuex+axios

Client端的渲染?
因为Client端的渲染是ssr渲染的一部分,这一块是必须要说的,同时,Client端的渲染在我做的时候,也有一些问题和一些坑,尤其是使用webpack2的时候,之前webpack1时代的东西大部分都不好使了,只能重新学,重新试,正好在这也分享给大家

Vue全家桶都是必要的吗?
当然不是,只有Vuex是必须的,实践发现,vuex也不是必须的,使用vuex可以很方便的管理前后端共享的数据。如果要不作用vuex,可以在created中使用如下方法来获取数据

let context = this.$ssrContext ? this.$ssrContext.state : window['__INITIAL_STATE__']
注:一定要在created里面使用,如果在mounted里面使用,Node端是不认识window对象,从而报错。

为什么必须要用到vuex?
你想啊,使用服务器渲染,数据必须得从服务器取,取了数据之后,怎么才能达到前后端共享数据?就得用Vuex!

即然数据在服务器端已经取到了,为什么还要共享到前端?

这就涉及到ssr渲染的奥秘了,按理说,服务端把数据取到之后,渲染成HTML返回到前端,这样前端就用不到这些数据了,取数据只为了渲染,这种情况只适用于纯静态的渲染,就是拿到10条数据,渲染成一个列表,这个列表上没有交互,没有click、hover等效果,但是一些有click事件,就像例子上面那样,点击每个item,都会弹出title,这些是需要js来做的,但是vue-ssr不能绑定javascript事件,只能是HTML+CSS,也就是说服务器端使用vue-ssr渲染出来的返回到浏览器的也只能是HTML+CSS。再强调一次:vue-ssr渲染出来只是HTML+CSS的字符串,绑定事件需要在浏览器端来做,前端需要数据和已经渲染好的DOM做比对,从而添加上各种事件!

那事件怎么办?
这就回到了第一个问题,为什么前端也需要数据,既然服务器不能增加事件,那只能前端增加喽。
vue-ssr有一个比较关键的地方就是,前后必须共用同一套vue文件,也就是说一个.vue文件,前端也要用,后端也要用,为什么要这样做,大家想过没有?

答案:就是后端从vuex里面取到数据之后,对<template>里面的HTML使用vue的语法进渲染,最终渲染成真正的HTML,对<style>里面的内容,使用loader,抽取成css,所以服务端渲染的成果是HTML+CSS;前端也是从vuex里面取到数据,前端的渲染主要做2件事,

1.拿到数据,使用virtual-dom进行预渲染,然后和服务端渲染出来的进行比对,比对两边渲染的内容是不是一致的;
2.对DOM元素的事件进行绑定,也就是回答的问题,事件在这块进行的处理。

一点小建议: 多多理解原理, 多多实践
我想只有理解了原理性的东西,再看代码,再看流程,才能理解是为什么要这么搞,因为这些东西官方文档上面说的也不是很清楚,网上的例子也不少,但是很少提这块,刚开始我拿到各种例子之后,也很蒙,这写的都是啥!啥!啥!尤其是官方给的例子,根本就没有头绪。现在理清了,分享给大家,希望有帮忙,下一篇我们来看下代码实现。

为什么要单独增加Vuex?
因为Vuex里面涉及很多概念性的东西,一时之间弄不懂,当时我在项目中集成Vuex时查了很多资料,踩了不少的坑。如果刚开始接触Vuex,你肯定会从官方文档看起,官方给的例子,就是加一减一的例子,你会发现,Vuex好复杂啊,本来可以一步完成的事,为什么要那么多步,而且还搞不清楚每步和每步是什么关系,蒙了。而且他的例子只针对简单的业务场景,对于生产环境(多component的环境),发现根本就是适用,下面让我来一一道来

Vuex解决了什么问题?
刚开始上项目时,我也没打算用Vuex,因为感觉那玩意没啥用,太复杂。后来一边做,一边就发现一个比较难解决的问题:兄弟组件间通信的问题!

如果不用Vuex,怎么做?
我相信,不用Vuex也可以解决,解决方案是:Root组件做为中转站,兄弟组件1向Root组件broadcast,Root组件收到之后,再broadcast,Root组件收到之后,再broadcastRootdispatch,兄弟组件2从Root组件拿到数据,然后做业务处理,数据从树根到树顶,再到树根。

这样会带来什么问题?
可维护性会下降,你要想修改数据,你得维护三个地方
可读性会下降,因为一个组件里的数据,你根本就看不出来是从哪来的
增加耦合,大量的上传派发,会让耦合性大大的增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。

是所有项目都用Vuex么?
凡是兄弟组件有大量通信的,建议一定要用,不管大项目和小项目,因为这样会省很多事。

从哪里开始?
在这里咱们先不谈Vuex的里面的几个概念,想看,官方上有,刚上来就提这么,效果也不好。咱们先从一个简单的实例开始。

什么例子?
咱们假设在A.vue里面通过Ajax取来数据,然后做展示,这个简单吧,咱们用Vuex来看看怎么搞。如果不用Vuex,我们取完数据会放到Data里面,然后拿到数据做v-for渲染,像这样

mounted() {
    Vue.axios.get('http://localhost:5000/data').then((response) => {
        const list = response.data.data.liveWodList
        this.newList = list
    })
}

如果用Vuex呢?
显然他不在你的A.vue里面,所以你得告诉他,来数据了,快收一下,怎么通知呢,这就涉及到Vuex的第一个操作:commit。这里这个操作,对应Vuex的核心概念之一:Mutations(变化)!他的作用就是通知Vuex要搞事情了,比如删除数据、增加数据等,代码是这样的this.$store.commit(‘setData’, list),这个有两个参数,第一个参数是要搞的事情,第二个参数是具体的数据。

数据存哪了?
你的数据是来了,我得有地来接收数据吧,接收数据的地对应Vuex的核心概念之二State(状态),就是所有需要变化的东西都存在我这。代码是这样的:
function setData(state, data) {
state.list = data
}

怎么拿到数据?
有放肯定有取啊,数据存在State,取也是从这里取。取数据就对应Vuex的核心概念之三Getters,代码是这样的的:
const getters = {
list: state => state.list
}

这是Vuex在真正项目中用到的,分模块,每个模块一个文件(modules),首先我们看下store/mutation-types.js。这个文件的结构比较简单,代码如下:

功能是:定义常量。常量的作用不用细说,防止手写写错。实际开发中,应该是每一个模块一个常量,现在只有一个LIST,未来可能会多增加NEWS/USER等,也是一个模块,一个常量对象。

再看modules/list.js,代码如下:
这里面就有对应的三个概念state/mutations/getters,可以和我上面说的对比一下,现在看代码,应该很清晰了。注意:里面有很多ES6的语法,不明白的可以查一查。
store/store-index.js为入口文件,里面主要是引入各配置,供Vue使用。注意:这个文件的引入是在src/index.js里面!

好像还缺一个?
对,还缺一个Action,为什么没提这个Action,按我的理解,Action这一层应该是在多个操作中有价值,比如有一个预约按钮,点击之后,会更新几个Component的状态。现实开发中,基本上都是点击按钮,触发一个事件,那增加Action就会增加整个流程的链路,增加复杂度。

开始一个简单的server-render
客户端打包需要的文件是这个:tools/webpack.js,很显然,既然vue在服务端渲染,那就需要有一个服务器的webpack文件,所以有tools下面就多出一个webpack.server.js的文件,里面的内容很简单,如下:

const path = require('path');
//  获取项目根目录
const projectRoot = path.resolve(__dirname, '..');
module.exports = {
    target: 'node', // 这里必须是node,因为打包完成的运行环境是node
    entry: path.join(projectRoot, 'src/server-index.js'),
    output: {
        libraryTarget: 'commonjs2', // !different
        // 打包出的路径
        path: path.join(projectRoot, 'build'),
        // 打包最终的文件名
        filename: 'bundle.server.js',
    },
    module: {
        // 因为使用webpack2,这里必须是rules,如果使用use, 
        // 会报个错:vue this._init is not a function
        rules: [{
                test: /\.vue$/,
                loader: 'vue-loader',
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: projectRoot,
                // 这里会把node_modules里面的东西排除在外,提高打包效率
                exclude: /node_modules/,
            }
        ]
    },
}

注:具体里面的功能,请看注释,有了这个webpack.server.js之后,里面的入口文件是server-index.js,和前端打包一样,服务端打包可得有一个入口文件,里面最主要的内容就是你要打包哪个.vue文件,里面的内容是,
// 这个文件里面除了定义入口的.vue,其他的都不用配置
import Vue from 'vue';
// 引入.vue入口文件
import App from './component/List.vue';
const app = new Vue(App);
// 你问我这块代码是啥意思,其实我也不知道,想要打包server端代码,这个代码块是必须的
// 以后会在这段代码里面加入其他一些配置信息
export default function (context) {
    return new Promise((resolve, reject) => {
        resolve(app);
    });
};

现在你使用webpack来打包文件,在build目录下面会生成一个bundle.server.js文件,详见tools/dev.js
接下来就是比较关健的一步了,如何把bundle.server.js里面的内容,转成HTML。这个项目里面,我们使用的express,
第一步:我们增加app.get('/', function (req, resp) {...}),这样我们就可以在访问localhost:5000时能看到效果
第二步:npm install vue-server-renderer --save,其实这个包我们现在才开始用,*注:这个包必须和vue的版本必须一致,目前都是2.3.2,不管哪个版本,版本号必须严格一致,否则会报warning*
第三步:读取bundle.server.js,
第四步:把上面读取的js文件,传递给vue-ssr,代码: const bundleRenderer = vueServerRenderer.createBundleRenderer(code);这步的作用是解析bundle.server.js,然后生成HTML,注:vueServerRenderer提供两个方法,分别为:createBundleRenderer和createRenderer,使用webpack打包的Component代码,必须作用createBundleRenderer这个方法,具体参照:官方文档,参数就是上面读取的js文件
第五步:渲染,最终生成HTML,bundleRenderer.renderToString((err, html) => {console.log(html)})
server.js代码完整版本:

const filePath = path.join(__dirname, './build/bundle.server.js')
    const code = fs.readFileSync(filePath, 'utf8');
    const bundleRenderer = vueServerRenderer.createBundleRenderer(code);
    bundleRenderer.renderToString((err, html) => {
        if (err) {
            console.log(err.message);
            console.log(err.stack);
        }
        console.log(html);
        resp.send(html)
    });
    
总结下生成HTML的步骤:
1.有一个你想打包.vue文件的入口文件,就是src/server-index.js
2.在webpack的配置文件中,把入口文件指向他
3.使用webpack对文件进行打包,会生成build/bundle.server.js文件
4.使用vue-server-renderer包解析这个文件,最终渲染成HTML
最终效果(浏览器端):
最终效果(console端):
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值