成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

詳解Vue服務(wù)端渲染

Paul_King / 777人閱讀

摘要:二服務(wù)端渲染初體驗(yàn)使用的服務(wù)端渲染功能,需要引入提供的服務(wù)端渲染模塊,其作用是創(chuàng)建一個(gè)渲染器,該渲染器可以將實(shí)例渲染成字符串。

詳解Vue服務(wù)端渲染 一、服務(wù)端渲染 - 簡(jiǎn)介

所謂服務(wù)端渲染就是將代碼的渲染交給服務(wù)器,服務(wù)器將渲染好的html字符串返回給客戶端,再由客戶端進(jìn)行顯示。

服務(wù)器端渲染的優(yōu)點(diǎn)

有利于SEO搜索引擎優(yōu)化,因?yàn)榉?wù)端渲染是將渲染好的html字符串返回給了客戶端,所以其可以被爬蟲爬取到;

加快首屏渲染時(shí)間,不會(huì)出現(xiàn)白屏;

服務(wù)器端渲染的缺點(diǎn)

SSR會(huì)占用更多的CPU和內(nèi)存資源

Vue中一些常用的瀏覽器API可能無法使用,比如Vue的生命周期在服務(wù)器端渲染只能使用beforeCreate()和created(),因?yàn)榉?wù)端呈現(xiàn)的僅僅是html字符串是沒有所謂的mount的。

二、服務(wù)端渲染 - 初體驗(yàn)

使用Vue的服務(wù)端渲染功能,需要引入Vue提供的服務(wù)端渲染模塊vue-server-renderer,其作用是創(chuàng)建一個(gè)渲染器,該渲染器可以將Vue實(shí)例渲染成html字符串。

用Koa來搭建一個(gè)web服務(wù)器來實(shí)現(xiàn):
① 目錄結(jié)構(gòu)

② 創(chuàng)建一個(gè)server.js 文件

const Koa = require("koa");
const Router = require("koa-router");
const fs = require("fs");

const app = new Koa(); // 創(chuàng)建服務(wù)器端app實(shí)例
const router = new Router(); // 創(chuàng)建服務(wù)器端路由

const Vue = require("vue");
const VueServerRender = require("vue-server-renderer"); // 引入服務(wù)端渲染模塊
const vm = new Vue({ // 創(chuàng)建Vue實(shí)例
    data() {
        return {msg: "hello vm"}
    },
    template: `
{{msg}}
` // 渲染器會(huì)將vue實(shí)例中的數(shù)據(jù)填入模板中并渲染成對(duì)應(yīng)的html字符串 }); const template = fs.readFileSync("./server.template.html", "utf8"); // 讀取基本的html結(jié)構(gòu) const render = VueServerRender.createRenderer({ template }); // 創(chuàng)建渲染器并以server.template.html作為html頁面的基本結(jié)構(gòu) router.get("/", async ctx => { // ctx.body = await render.renderToString(vm); ctx.body = await new Promise((resolve, reject) => { render.renderToString(vm, (err, html) => { // 將vm實(shí)例渲染成html并插入到server.template.html模板中 console.log(`${html}`); }); ); }); app.use(router.routes()); // 添加路由中間件 app.listen(3000, () => { console.log("node server listening on port 3000."); }); // 監(jiān)聽3000端口

注意:

server.template.html文件中必須有 占位符,即將Vue實(shí)例vm渲染成的html字符串插入到占位符所在的位置;

render.renderToString(vm)方法不傳回調(diào)函數(shù)的時(shí)候返回的是Promise對(duì)象,但是如果傳入了回調(diào)函數(shù),那么就返回void了, 推薦自己創(chuàng)建一個(gè)Promise函數(shù);

Vue服務(wù)端渲染出來的字符串中會(huì)包含data-server-rendered="true"這樣一個(gè)標(biāo)識(shí),標(biāo)識(shí)這是由Vue服務(wù)端渲染的結(jié)果字符

hello vm
三、服務(wù)端渲染 - 引入Vue項(xiàng)目

上面初體驗(yàn)中,我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Vue服務(wù)端渲染,但是我們實(shí)際中Vue是一個(gè)很大的項(xiàng)目,里面是包含了很多組件的大型應(yīng)用,而不是像初體驗(yàn)中的一個(gè)簡(jiǎn)單的Vue實(shí)例,所以我們必須引入一個(gè)Vue項(xiàng)目,包括Vue的入口文件main.js、App.vue、components、public/index.html等,如:

通過webpack來打包我們的整個(gè)Vue項(xiàng)目,webpack將以Vue的根實(shí)例main.js作為入口文件,打包出一個(gè)合并的最終的bundle.js和一個(gè)頁面入口index.html文件,該index.html文件引入bundle.js后就能加載整個(gè)Vue項(xiàng)目中的頁面以及頁面中的事件等等,這里我們的Vue項(xiàng)目是一個(gè)很簡(jiǎn)單的模板項(xiàng)目,關(guān)鍵在于webpack的配置

// webpack.config.js

const path = require("path");
const resolve = (dir) => {
    return path.resolve(__dirname, dir);
}
const VueLoader = require("vue-loader/lib/plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    entry: resolve("./src/main.js"), // webpack 入口, 即Vue的入口文件main.js
    output: {
        filename: "bundle.js", // 打包后輸出的結(jié)果文件名
        path: resolve("./dist") // 打包后輸出結(jié)果存放目錄
    },
    resolve: {
        extensions: [".js", ".vue"] // 沒有寫擴(kuò)展名的時(shí)候,解析順序
    }, 
    module: {
        rules: [
            {
                test: /.js$/, 
                use: {
                    loader: "babel-loader", // 將所有的js文件通過babel-loader轉(zhuǎn)換為ES5代碼
                    options: {
                        presets: ["@babel/preset-env"]
                    }
                },
                exclude: /node_modules/
            },
            {
                test: /.css$/, // 解析.vue文件中的css
                use: [
                    "vue-style-loader", "css-loader"
                ]
            },
            {
                test: /.vue$/, // 解析.vue文件,需要配合其中的插件進(jìn)行使用
                use: "vue-loader"
            }
        ]
    },
    plugins: [
        new VueLoader(), // 解析.vue文件的插件
        new HtmlWebpackPlugin({
            filename: "index.html", // 打包后輸出的html文件名
            template: resolve("./public/index.html") // 該模板文件在哪
        })
    ]
}
打包輸出后的dist目錄中會(huì)出現(xiàn)兩個(gè)文件: bundle.js和index.html, 直接在本地點(diǎn)擊index.html文件即可執(zhí)行并呈現(xiàn)整個(gè)Vue項(xiàng)目
四、服務(wù)端渲染 - 將Vue項(xiàng)目分割為客戶端和服務(wù)端

① 在非服務(wù)端渲染的時(shí)候,我們使用的打包入口文件是main.js,其主要就是創(chuàng)建了一個(gè)Vue實(shí)例,并且渲染App.vue,然后將渲染好的App.vue掛載到index.html文件#app元素中,但是我們的服務(wù)端渲染是無法mount的,也就是說無法將渲染結(jié)果渲染到#app元素上,所以需要改造main.js文件

// 改造后的main.js文件

import Vue from "vue";
import App from "./App";
/** 
 1. main.js在服務(wù)端渲染中的作用就是提供一個(gè)Vue項(xiàng)目的根實(shí)例,所以導(dǎo)出一個(gè)函數(shù)
 2. 讓客戶端和服務(wù)端都能獲取到Vue項(xiàng)目的根實(shí)例,然后根據(jù)需要,
 3. 客戶端通過手動(dòng)調(diào)用$mount()進(jìn)行掛載
 4. */
export default () => {
    const app = new Vue({
        render: h => h(App)
    });
    return {app}; // 返回整個(gè)Vue根實(shí)例
}

② 新建兩個(gè)入口文件: client-entry.js 和 server-entry.js
// client-entry.js

import createApp from "./main";
const {app} = createApp(); // 獲取到Vue項(xiàng)目根實(shí)例
app.$mount("#app"); // 將根實(shí)例掛載到#app上
此時(shí)將webpack.config.js的入口文件改成client-entry.js應(yīng)該和之前是一樣的

// server-entry.js

import createApp from "./main";
/** 
 * 服務(wù)端需要調(diào)用當(dāng)前這個(gè)文件產(chǎn)生一個(gè)Vue項(xiàng)目的根實(shí)例
 * 由于服務(wù)端與客戶端是1對(duì)多的關(guān)系,所以不能每個(gè)客戶端訪問都返回同一個(gè)Vue項(xiàng)目根實(shí)例
 * 所以需要返回一個(gè)函數(shù),該函數(shù)返回一個(gè)新的Vue項(xiàng)目根實(shí)例
 * */ 
export default () => {
    const {app} = createApp(); // 獲取到Vue項(xiàng)目根實(shí)例
    return app;
}
為什么客戶端入口文件就不需要暴露一個(gè)一個(gè)函數(shù)?因?yàn)榭蛻舳丝梢员辉L問多次,即多次執(zhí)行,每次執(zhí)行返回的都是一個(gè)新的Vue項(xiàng)目實(shí)例了。而服務(wù)器只會(huì)啟動(dòng)一次,但是卻需要每次客戶端訪問都返回一個(gè)新的Vue項(xiàng)目實(shí)例,所以必須放到函數(shù)中

③ 拆分webapck.config.js, 將其分成兩個(gè)配置文件,同樣一個(gè)用于客戶端,一個(gè)用于服務(wù)端打包
由于客戶端和服務(wù)端的webpack配置文件有很多是相同的,所以可以抽取出一個(gè)webpack.base.js

// webpack.base.js

const path = require("path");
const resolve = (dir) => {
    return path.resolve(__dirname, dir);
}
const VueLoader = require("vue-loader/lib/plugin");
module.exports = {
    output: {
        filename: "[name].bundle.js", // 打包后輸出的結(jié)果文件名
        path: resolve("./../dist/") // 打包后輸出結(jié)果存放目錄
    },
    resolve: {
        extensions: [".js", ".vue"] // 沒有寫擴(kuò)展名的時(shí)候,解析順序
    }, 
    module: {
        rules: [
            {
                test: /.js$/, 
                use: {
                    loader: "babel-loader", // 將所有的js文件通過babel-loader轉(zhuǎn)換為ES5代碼
                    options: {
                        presets: ["@babel/preset-env"]
                    }
                },
                exclude: /node_modules/
            },
            {
                test: /.css$/, // 解析.vue文件中的css
                use: [
                    "vue-style-loader", "css-loader"
                ]
            },
            {
                test: /.vue$/, // 解析.vue文件,需要配合其中的插件進(jìn)行使用
                use: "vue-loader"
            }
        ]
    },
    plugins: [
        new VueLoader(), // 解析.vue文件的插件
    ]
}

// webpack-client.js

const merge = require("webpack-merge");
const base = require("./webpack.base");
const path = require("path");
const resolve = (dir) => {
    return path.resolve(__dirname, dir);
}
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(base, {
    entry: {
        client: resolve("./../src/client-entry.js"), // 給客戶端入口文件取名client,output的時(shí)候可以獲取到該名字動(dòng)態(tài)輸出
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: "index.html", // 打包后輸出的html文件名
            template: resolve("./../public/index.html") // 該模板文件在哪
        })
    ]
});

// webpack-server.js

const merge = require("webpack-merge");
const base = require("./webpack.base");
const path = require("path");
const resolve = (dir) => {
    return path.resolve(__dirname, dir);
}
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(base, {
    entry: {
        server: resolve("./../src/server-entry.js"), // 給客戶端入口文件取名client,output的時(shí)候可以獲取到該名字動(dòng)態(tài)輸出
    },
    target: "node", // 給node使用
    output: {
        libraryTarget: "commonjs2" // 把最終這個(gè)文件導(dǎo)出的結(jié)果放到module.exports上
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: "index.server.html", // 打包后輸出的html文件名
            template: resolve("./../public/index.server.html"), // 該模板文件在哪
            excludeChunks: ["server"] // 排除某個(gè)模塊, 不讓打包輸出后的server.bundle.js文件引入到index.server.html文件中
        })
    ]
});
服務(wù)端webpack配置文件比較特殊,在output的時(shí)候需要配置一個(gè)libraryTarget,因?yàn)槟J(rèn)webpack輸出的時(shí)候是將打包輸出結(jié)果放到一個(gè)匿名自執(zhí)行函數(shù)中的,通過將libraryTarget設(shè)置為commonjs2,就會(huì)將整個(gè)打包結(jié)果放到module.exports上;
服務(wù)端webpack打包后輸出的server.bundle.js文件不是直接引入到index.server.html文件中使用的,還需要經(jīng)過處理渲染成html字符串才能插入到index.server.html文件中,所以打包輸出后,要在html-webpack-plugin中排除對(duì)該模塊的引用
由于webpack配置文件被分割,所以啟動(dòng)webapck-dev-server的時(shí)候需要指定配置文件,在package.json文件中添加腳本
"scripts": {
    "client:dev": "webpack-dev-server --config ./build/webpack.client.js --mode development",
    "client:build": "webpack --config ./build/webpack.client.js --mode development",
    "server:build": "webpack --config ./build/webpack.server.js --mode development"
  },
此時(shí)分別指向npm run client:build 和 npm run server:build即可在dist目錄下生成index.html、client.bundle.js, index.server.html、server.bundle.js,其中client.bundel.js被index.html引用,server.bundle.js沒有被index.server.html引入,index.server.html僅僅是拷貝到了dist目錄下,同時(shí)server.bundle.js的整個(gè)輸出結(jié)果是掛在module.exports下的

④ 將打包好的server.bundle.js交給服務(wù)器進(jìn)行渲染并生成html字符串返回給客戶端,和之前初體驗(yàn)一樣,創(chuàng)建一個(gè)web服務(wù)器,只不過,這次不是渲染一個(gè)簡(jiǎn)單的Vue實(shí)例,而是渲染整個(gè)打包好的server.bundle.js

vue-server-renderer提供了兩種渲染方式:

和初體驗(yàn)中的一樣,把server.bundle.js當(dāng)作簡(jiǎn)單Vue實(shí)例進(jìn)行渲染,我們打包后server.bundle.js的內(nèi)容都是掛到了module.exports上,所以我們可以直接require,require返回的結(jié)果是一個(gè)對(duì)象,該對(duì)象上只有一個(gè)屬性即default,屬性值為一個(gè)函數(shù),執(zhí)行該函數(shù)即可獲取整個(gè)Vue項(xiàng)目對(duì)應(yīng)的Vue實(shí)例。

// 獲取server.bundle.js中的Vue實(shí)例進(jìn)行渲染
const VueServerRender = require("vue-server-renderer"); // 引入服務(wù)端渲染模塊
const template = fs.readFileSync("./server.template.html", "utf8"); // 讀取基本的html結(jié)構(gòu)
const render = VueServerRender.createRenderer({
    template
}); // 創(chuàng)建渲染器并以server.template.html作為html頁面的基本結(jié)構(gòu)
router.get("/", async ctx => {
const vm = require("./dist/server.bundle").default(); // 執(zhí)行server.budle的default方法獲取Vue實(shí)例,每次請(qǐng)求獲取一個(gè)新的Vue實(shí)例
    ctx.body = await new Promise((resolve, reject) => {
        render.renderToString(vm, (err, html) => { // 將vm實(shí)例渲染成html并插入到server.template.html模板中
            if (err) reject(err);
            console.log(`${html}`);
            resolve(html);
        });
    });
});
require server.bunlde.js之后調(diào)用default屬性獲取的方法,其實(shí)就是server.entry.js中導(dǎo)出的方法,這個(gè)方法可以接收路由參數(shù),后面集成路由的時(shí)候會(huì)用到

通過vue-server-renderer提供的createBundleRenderer()方法進(jìn)行渲染,該方法需要傳入server.bundle.js中的文件內(nèi)容字符串, 再傳入模板html即可,所以需要讀取server.bundle.js中的內(nèi)容:

// 直接渲染server.bundle.js
const VueServerRender = require("vue-server-renderer"); // 引入服務(wù)端渲染模塊
// 讀取server.bundle.js中的內(nèi)容,即文件中的字符串
const ServerBundle = fs.readFileSync("./dist/server.bundle.js", "utf8");
const template = fs.readFileSync("./dist/index.server.html", "utf8"); // 讀取基本的html結(jié)構(gòu)
const render = VueServerRender.createBundleRenderer(ServerBundle, { // 傳入server.bundle.js字符串創(chuàng)建渲染器
    template
});
router.get("/", async ctx => {
    ctx.body = await new Promise((resolve, reject) => {
        render.renderToString((err, html) => { // 將server.bundle.js渲染成html字符串
            if (err) reject(err);
            resolve(html);
        });
    });
});
render.renderToString()執(zhí)行的時(shí)候內(nèi)部也是要通過ServerBundle獲取到server.entry.js中導(dǎo)出的default()方法獲取到Vue項(xiàng)目實(shí)例進(jìn)行渲染的,總之就是要獲取到Vue項(xiàng)目的實(shí)例進(jìn)行渲染
重啟服務(wù)器,再次訪問,查看源碼,可以看到頁面已經(jīng)不是一個(gè)空的基礎(chǔ)頁面了,而是真實(shí)包含html內(nèi)容的頁面,但是仍然存在一個(gè)問題,那就是之前的事件并不起作用了,因?yàn)榉?wù)器將sever.bundle.js渲染成的是html字符串返回給客戶端的,是不包含事件的,其中的事件執(zhí)行函數(shù)在client.bundle.js中,所以我們可以在index.server.html文件中通過script標(biāo)簽顯式地引入client.bundle.js,如:

    
    

注意: 當(dāng)訪問頁面的時(shí)候,就會(huì)向服務(wù)器請(qǐng)求client.bundle.js文件,所以服務(wù)器需要將client.bundle.js以靜態(tài)資源的方式發(fā)布出去。

剛才我們是手動(dòng)在index.server.html中通過script標(biāo)簽引入client.bundle.js, 非常的不方便,vue-server-renderer給我們提供了兩個(gè)插件,vue-server-renderer/client-plugin和vue-server-renderer/server-plugin,可以在webpack配置文件中引入,那么打包的時(shí)候,會(huì)分別生成兩個(gè)json文件,vue-ssr-client-manifest.json和vue-ssr-server-bundle.json,這兩個(gè)文件主要是生成客戶端和服務(wù)端bundle的對(duì)應(yīng)關(guān)系,這樣就不需要我們收到引入client.bundle.js了。
之前是通過讀取server.bundle.js的內(nèi)容來渲染的,現(xiàn)在可以直接requirevue-ssr-server-bundle.json文件即可,同時(shí)在渲染的時(shí)候再添加vue-ssr-client-manifest.json即可,如:
// 直接渲染server.bundle.js
const VueServerRender = require("vue-server-renderer"); // 引入服務(wù)端渲染模塊
// 讀取server.bundle.js中的內(nèi)容,即文件中的字符串
// const ServerBundle = fs.readFileSync("./dist/server.bundle.js", "utf8");
const ServerBundle = require("./dist/vue-ssr-server-bundle.json");
const clientManifest = require("./dist/vue-ssr-client-manifest.json");
const template = fs.readFileSync("./dist/index.server.html", "utf8"); // 讀取基本的html結(jié)構(gòu)
const render = VueServerRender.createBundleRenderer(ServerBundle, { // 傳入server.bundle.js字符串創(chuàng)建渲染器
    template,
    clientManifest
});
使用者兩個(gè)插件之后,就不會(huì)生成server.bundle.js文件了
五、服務(wù)端渲染 - 集成路由

要集成路由,那么需要在Vue項(xiàng)目中加入路由功能,和客戶端路由配置一樣,只不過不是直接導(dǎo)出路由實(shí)例,而是和main.js一樣導(dǎo)出一個(gè)方法返回一個(gè)新的路由實(shí)例,如:

import Vue from "vue";
import VueRouter from "vue-router";
import Foo from "./components/Foo";

Vue.use(VueRouter);

export default () => { // 導(dǎo)出函數(shù)返回路由實(shí)例
    const router = new VueRouter({
        mode: "history",
        routes: [
            {
                path: "/",
                component: Foo
            },
            {
                path: "/bar",
                component: () => import("./components/Bar.vue")
            }
        ]
    });
    return router;
}

然后在main.js中調(diào)用路由方法獲取路由實(shí)例并掛到Vue實(shí)例上,同時(shí)對(duì)外暴露,如:

export default () => {
    const router = createRouter();
    const app = new Vue({
        router, // 掛在路由實(shí)例到Vue實(shí)例上
        render: h => h(App)
    });
    return {app, router}; // 對(duì)外暴露路由實(shí)例
}
此時(shí)Vue項(xiàng)目已經(jīng)實(shí)現(xiàn)路由功能,但是訪問的時(shí)候卻會(huì)報(bào)錯(cuò),The client-side rendered virtual DOM tree is not matching server-rendered content,即客戶端和服務(wù)端渲染的頁面不一致,之所以出現(xiàn)這種情況是因?yàn)椋?strong>客戶端加了路由功能進(jìn)行了相應(yīng)的路由跳轉(zhuǎn),但是服務(wù)端沒有進(jìn)行路由跳轉(zhuǎn),所以頁面會(huì)不一致,解決方法就是,服務(wù)器也要進(jìn)行相應(yīng)的路由跳轉(zhuǎn)
前面提到過createBundleRenderer()方法創(chuàng)建的渲染器在執(zhí)行renderToString()方法的時(shí)候,可以傳遞一個(gè)context上下文對(duì)象,可以將客戶端的訪問url保存到context對(duì)象上,而這個(gè)context對(duì)象會(huì)傳到server.entry.js對(duì)外暴露函數(shù)中,然后在該函數(shù)中獲取路由進(jìn)行相應(yīng)跳轉(zhuǎn)即可,如:
// server.entry.js
export default (context) => {
    const {app, router} = createApp(); // 獲取到Vue項(xiàng)目根實(shí)例server
    console.log("相當(dāng)于新創(chuàng)建了一個(gè)服務(wù)端");
    router.push(context.url); // 在服務(wù)端進(jìn)行路由跳轉(zhuǎn)
    return app;
}
此時(shí)再訪問頁面,就不會(huì)出現(xiàn)上述客戶端和服務(wù)端渲染頁面不一致的情況了,但是還有一個(gè)問題,那就是我們?cè)跒g覽器中直接訪問路由路徑的時(shí)候,會(huì)提示404,因?yàn)槲覀兎?wù)器并沒有配置相應(yīng)的路由,所以客戶端定義的路由路徑,需要在服務(wù)器端進(jìn)行相應(yīng)的配置
還有就是異步組件渲染的問題,我們現(xiàn)在的server.entry.js中是直接返回Vue實(shí)例的,同時(shí)在其中進(jìn)行router跳轉(zhuǎn),如果路由跳轉(zhuǎn)的那個(gè)是異步組件,可能還沒跳轉(zhuǎn)完成,就返回了Vue實(shí)例,而出現(xiàn)渲染異常的情況,所以我們要返回一個(gè)Promise對(duì)象,等路由跳轉(zhuǎn)完成后再返回Vue實(shí)例,如:
// 改造后的sever.entry.js
export default (context) => {
    return new Promise((resolve, reject) => {
        const {app, router} = createApp(); // 獲取到Vue項(xiàng)目根實(shí)例server
        router.push(context.url);
        router.onReady(() => { // 等路由跳轉(zhuǎn)完成
            let matchs = router.getMatchedComponents();
            if (matchs.length === 0) {
                reject({code: 404});
            }
            resolve(app);
        }, reject);

    });
}
404頁面的處理,我們可以在router.onReady回調(diào)中進(jìn)行處理,可以根據(jù)路由匹配結(jié)果進(jìn)行提示,如果路由匹配結(jié)果為0,那么就是沒有匹配成功則reject一個(gè)錯(cuò)誤,服務(wù)器捕獲到錯(cuò)誤后進(jìn)行404提示即可
六、服務(wù)端渲染 - 集成Vuex

同樣,要集成Vuex,首先和客戶端渲染一樣,引入Vuex并創(chuàng)建store,只不過是對(duì)外暴露一個(gè)函數(shù),然后在函數(shù)中返回新的store對(duì)象,如:
// store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default () => {
    const store = new Vuex.Store({
        state: {
            name: "even"
        },
        mutations: {
            changeName(state) {
                state.name = "lhb";
            }
        },
        actions: {
            changeName({commit}) {
                console.log("changeName action");
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        commit("changeName");
                        resolve();
                    }, 3000);
                });
            }
        }
    });
    return store;
}
然后在main.js中引入并注入到Vue實(shí)例中,跟Vue根實(shí)例和路由一樣對(duì)外暴露。服務(wù)端渲染集成Vuex關(guān)鍵在于服務(wù)端渲染的時(shí)候執(zhí)行mutaion或者action后,Vuex中數(shù)據(jù)僅在服務(wù)器端改變,所以需要將服務(wù)器端的狀態(tài)數(shù)據(jù)保存起來,實(shí)際上會(huì)保存到window對(duì)象的__INITIAL_STATE__屬性上,客戶端渲染的時(shí)候只需要從window.__INITIAL_STATE__數(shù)據(jù)中獲取到服務(wù)端Vuex的狀態(tài)然后進(jìn)行替換即可。

① 在Foo.vue組件中添加一個(gè)asyncData()方法,用于派發(fā)action,如:
// Foo.vue

export default {
    asyncData(store) { // asyncData只在服務(wù)端執(zhí)行
        console.log("asyncData");
        return store.dispatch("changeName");
    }
}

② 在server-entry.js中,如果匹配到了Foo.Vue組件,那么執(zhí)行該組件的asyncData()方法,此時(shí)服務(wù)器端的Vuex的狀態(tài)就會(huì)發(fā)生改變,如:
// server-entry.js

export default (context) => {
    return new Promise((resolve, reject) => {
        console.log(context.url);
        const {app, router, store} = createApp(); // 獲取到Vue項(xiàng)目根實(shí)例server
        router.push(context.url);
        router.onReady(() => { // 等路由跳轉(zhuǎn)完成
            let matchs = router.getMatchedComponents();
            Promise.all(matchs.map((component) => {
                if (component.asyncData) { // 如果匹配的組件中含有asyncData方法則執(zhí)行
                    return component.asyncData(store); // 服務(wù)器端Vuex狀態(tài)會(huì)發(fā)生改變
                }
            })).then(() => {
                console.log("success");
                context.state = store.state; // 服務(wù)器端store狀態(tài)改變后將其掛載到context上,然后會(huì)掛載到window的__INITIAL_STATE__上
                resolve(app);
            });
            if (matchs.length === 0) {
                reject({code: 404});
            }
        }, reject);
    });
}
將服務(wù)器Vuex狀態(tài)保存的時(shí)候,必須是保存到context的state屬性上,服務(wù)器端渲染完成后,會(huì)添加一個(gè)script標(biāo)簽其中只有一行代碼,就是將服務(wù)器端Vuex狀態(tài)保存到window.__INITIAL_STATE__上

③ 接下來就是需要客戶端去取出window.__INITIAL_STATE__中的狀態(tài)數(shù)據(jù)并替換,在store.js中返回store對(duì)象前進(jìn)行判斷,如果是客戶端執(zhí)行Vuex,那么取出window.__INITIAL_STATE__中的狀態(tài)數(shù)據(jù)并替換,如:

if(typeof window !== "undefined" && window.__INITIAL_STATE__) { // 如果是客戶端執(zhí)行
        store.replaceState(window.__INITIAL_STATE__); // 將服務(wù)器端store狀態(tài)替換掉客戶端狀態(tài)
    }
    return store;
將Vuex中的數(shù)據(jù)顯示出來,此時(shí)再訪問Foo.vue就可以看到name數(shù)據(jù)的變化了,我們現(xiàn)在只有在進(jìn)行服務(wù)器端渲染Foo.vue的時(shí)候才會(huì)執(zhí)行asyncData()方法,數(shù)據(jù)才會(huì)發(fā)生變化,如果在客戶端進(jìn)行渲染Foo.vue組,那么不會(huì)執(zhí)行asyncData(),所以可以在Foo.vue組件mounted的時(shí)候派發(fā)一個(gè)相同的action進(jìn)行數(shù)據(jù)改變即可

// Foo.vue

export default {
    mounted () {
          this.$store.dispatch("changeName");  
    }
}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106368.html

相關(guān)文章

  • 用WEB技術(shù)棧開發(fā)NATIVE應(yīng)用(二):WEEX 前SDK原理詳解

    摘要:依舊采取傳統(tǒng)的開發(fā)技術(shù)棧進(jìn)行開發(fā),同時(shí)在終端的運(yùn)行體驗(yàn)不輸。首先來看下前端開發(fā)框架目前與構(gòu)成了三大最流行的前端開發(fā)框架,具有組件化以及三大特性,還學(xué)習(xí)的,引入了狀態(tài)管理模塊。 摘要: WEEX依舊采取傳統(tǒng)的web開發(fā)技術(shù)棧進(jìn)行開發(fā),同時(shí)app在終端的運(yùn)行體驗(yàn)不輸native app。其同時(shí)解決了開發(fā)效率、發(fā)版速度以及用戶體驗(yàn)三個(gè)核心問題。那么WEEX是如何實(shí)現(xiàn)的?目前WEEX已經(jīng)完全開...

    ls0609 評(píng)論0 收藏0
  • vue搭建的個(gè)人博客介紹----mapblog小站

    摘要:后端主要使用的框架,數(shù)據(jù)庫采用。后臺(tái)管理登錄采用與后端進(jìn)行登陸狀態(tài)的確認(rèn)。本文首發(fā)于小站,這是一個(gè)積累和分享知識(shí)的個(gè)人博客 這篇文章擱置了很長(zhǎng)時(shí)間,最終決定還是把它寫出來,給剛開始學(xué)習(xí)vue并且想用vue寫個(gè)人博客的同學(xué)一個(gè)參考。因?yàn)楫?dāng)初我也是參考了其他人分享的知識(shí),從一個(gè)vue小白變成了一個(gè)入門級(jí)選手,并最終完成了這個(gè)個(gè)人博客的搭建工作,代碼已托管在Github-justJokee。...

    Ashin 評(píng)論0 收藏0
  • vue服務(wù)渲染demo將vue-cli生成的項(xiàng)目轉(zhuǎn)為ssr

    摘要:無需使用服務(wù)器實(shí)時(shí)動(dòng)態(tài)編譯,而是使用預(yù)渲染方式,在構(gòu)建時(shí)簡(jiǎn)單地生成針對(duì)特定路由的靜態(tài)文件。與可以部署在任何靜態(tài)文件服務(wù)器上的完全靜態(tài)單頁面應(yīng)用程序不同,服務(wù)器渲染應(yīng)用程序,需要處于運(yùn)行環(huán)境。更多的服務(wù)器端負(fù)載。 目錄結(jié)構(gòu) -no-ssr-demo 未做ssr之前的項(xiàng)目代碼用于對(duì)比 -vuecli2ssr 將vuecli生成的項(xiàng)目轉(zhuǎn)為ssr -prerender-demo 使用prer...

    whinc 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

Paul_King

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<