ホームページ > ウェブフロントエンド > jsチュートリアル > vue-ssr を使用してサーバー側レンダリングを実装する方法

vue-ssr を使用してサーバー側レンダリングを実装する方法

亚连
リリース: 2018-06-12 18:21:50
オリジナル
4925 人が閲覧しました

この記事では主に vue-ssr に基づくサーバーサイド レンダリングの詳細を紹介します。

パート 1 基本的な概要

1. 概要

サーバー側レンダリングの実装原理とメカニズム: サーバー側でデータを解析してレンダリングし、HTML フラグメントを直接生成してフロントエンドに返します。次に、フロントエンドは、バックエンドによってフロントエンド ページに返された HTML フラグメントを解析できます。大きく分けて 2 つの形式があります。

1. サーバーは、vm テンプレートなどのテンプレート エンジンを通じてページ全体を直接レンダリングします。 Java バックエンドのエンジンと、php バックエンドのスマート テンプレート エンジンです。
2. サービスは HTML コード ブロックをレンダリングして生成し、フロントエンドは AJAX を通じてそれらを取得し、js を使用して動的に追加します。

2. サーバーサイドレンダリングの長所と短所

サーバーサイドレンダリングは、次の 2 つの主要な問題を解決できます。

1. SEO の問題。これは、検索エンジンのスパイダーが Web サイトのコンテンツをクロールするのに有益であり、ウェブサイトのランキング。
2. 最初の画面の読み込みが遅いという問題。たとえば、成熟した SPA プロジェクトでは、サーバー側のレンダリングで多くのリソースを読み込む必要があるため、最初の画面のレンダリングが高速化されることがあります。
同様に、サーバー側のレンダリングにも欠点があります。フロントエンド ページのサーバー側のレンダリングでは、サーバーへの負荷が確実に増加するため、主に独自のビジネス シナリオに応じて適切な方法を選択する必要があります。

3. SSRの実装原理

クライアントはサーバーにリクエストし、サーバーはリクエストアドレスに従って一致するコンポーネントを取得し、一致したコンポーネントを呼び出してPromise(正式にはpreFetchメソッド)を返し、必要なデータを取得します。 。最後に、

1

<script>window.__initial_state=data</script>

ログイン後にコピー

を通じて Web ページに書き込まれ、最終的にサーバーによってレンダリングされた Web ページが返されます。

次に、クライアントは vuex によって書き込まれたInitial_state を現在のグローバル状態ツリーに置き換え、この状態ツリーを使用してサーバーによってレンダリングされたデータに問題があるかどうかを確認します。サーバーによってレンダリングされていないコンポーネントが見つかった場合は、非同期リクエストを送信してデータを取得します。端的に言えば、React の shouldComponentUpdate に似た Diff 操作です。

Vue2 は一方向のデータ フローを使用し、SSR を通じて唯一のグローバル状態を返し、コンポーネントが SSR であるかどうかを確認できます。

4. vue バックエンドレンダリング用のメインプラグイン: vue-server-renderer

仮想 dom の導入により、vue のサーバーサイドレンダリングが可能になりました。以下はレンダリングフローチャートです。公式 vue-server-renderer によって提供されます:

Vue のバックエンド レンダリングは、ページのソース コード (ソース)、ノード レイヤーのレンダリング部分の 3 つの部分に分かれていることがわかります。そしてブラウザのレンダリング部分。

ソースは 2 つのエントリ ポイントに分かれています。1 つはフロントエンド ページのクライアント エントリで、主に Vue オブジェクトをインスタンス化してページにマウントするために使用されます。もう 1 つはバックエンド レンダリングのサーバー エントリです。主にサーバーを制御するために使用されるサービス。レンダリング モジュールはコールバックし、Promise オブジェクトを返し、最後に Vue オブジェクトを返します (テスト後、Vue オブジェクトを直接返すことも可能です)。フロントはビジネス開発コードです。開発が完了したら、Webpack を通じてビルドされ、対応するバンドルが生成されます。クライアント バンドルはブラウザ側で実行できるパッケージ化ファイルです。サーバー バンドルについて。Vue2 は vue-server-renderer モジュールを提供します。このモジュールは、以下に紹介する renderer/bundleRenderer の 2 種類のレンダリングを提供します。

renderer は vue オブジェクトを受け取り、それをレンダリングします。これは単純な vue オブジェクトに対して実行できますが、複雑なプロジェクトの場合、このメソッドを使用して vue オブジェクトを直接要求すると、サーバー側の構造とロジックに影響します。まず、モジュールのステータスは各レンダリング リクエストで継続されるため、このレンダリング リクエストのステータスが後続のリクエストに影響を与えないようにする必要があります。別のレンダリング モードでは、レンダリングは BundleRenderer を通じて行われます。

bundleRenderer は、より複雑なプロジェクトのサーバー側レンダリングに公式に推奨される方法であり、Webpack を使用して、特定の要件に従ってサーバー エントリをパッケージ化し、サーバー バンドルを生成することができるアプリのパッケージ化および圧縮ファイルと同等です。呼び出しごとに vue オブジェクトが再初期化されるため、開発者は現在のビジネスに集中するだけで済み、サーバー側のロジック コードをさらに開発する必要がなくなります。レンダリング。レンダラーが生成された後、renderToString と renderToStream という 2 つのインターフェイスがあり、1 つはページを文字列ファイルに一度にレンダリングするもので、もう 1 つはストリーミングをサポートする Web サーバーに適したストリーミング レンダリングです。サービスをリクエストする速度が速くなります。

パート 2: ゼロから構築する

1. はじめに

前のセクションでは、vue バックエンド レンダリングを使用する必要がある理由と、vue バックエンド レンダリングの基本原則について簡単に説明しました。独自の vue バックエンド レンダリング スキャフォールディングを構築するには、当然のことながら、公式ページの応答サンプル vue-hackernews-2.0 を参照して、プロジェクトを最初から構築する必要があります。ソース コードは次のセクションで共有します。 。

2、前期准备

基本环境要求:node版本6.10.1以上,npm版本3.10.10以上,本机环境是这样的,建议升级到官方最新版本。

使用的技术栈:

1、vue 2.4.2
2、vuex 2.3.1
3、vue-router 2.7.0
4、vue-server-renderer 2.4.2
5、express 4.15.4
6、axios 0.16.2
7、qs 6.5.0
8、q https://github.com/kriskowal/q.git
9、webpack 3.5.0
10、mockjs 1.0.1-beta3
11、babel 相关插件

以上是主要是用的技术栈,在构建过程中会是用相应的插件依赖包来配合进行压缩打包,以下是npm init后package.json文件所要添加的依赖包。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

"dependencies": {

 "axios": "^0.16.2",

 "es6-promise": "^4.1.1",

 "express": "^4.15.4",

 "lodash": "^4.17.4",

 "q": "git+https://github.com/kriskowal/q.git",

 "qs": "^6.5.0",

 "vue": "^2.4.2",

 "vue-router": "^2.7.0",

 "vue-server-renderer": "^2.4.2",

 "vuex": "^2.3.1"

 },

 "devDependencies": {

 "autoprefixer": "^7.1.2",

 "babel-core": "^6.25.0",

 "babel-loader": "^7.1.1",

 "babel-plugin-syntax-dynamic-import": "^6.18.0",

 "babel-plugin-transform-runtime": "^6.22.0",

 "babel-preset-env": "^1.6.0",

 "babel-preset-stage-2": "^6.22.0",

 "compression": "^1.7.1",

 "cross-env": "^5.0.5",

 "css-loader": "^0.28.4",

 "extract-text-webpack-plugin": "^3.0.0",

 "file-loader": "^0.11.2",

 "friendly-errors-webpack-plugin": "^1.6.1",

 "glob": "^7.1.2",

 "less": "^2.7.2",

 "less-loader": "^2.2.3",

 "lru-cache": "^4.1.1",

 "mockjs": "^1.0.1-beta3",

 "style-loader": "^0.19.0",

 "sw-precache-webpack-plugin": "^0.11.4",

 "url-loader": "^0.5.9",

 "vue-loader": "^13.0.4",

 "vue-style-loader": "^3.0.3",

 "vue-template-compiler": "^2.4.2",

 "vuex-router-sync": "^4.2.0",

 "webpack": "^3.5.0",

 "webpack-dev-middleware": "^1.12.0",

 "webpack-hot-middleware": "^2.18.2",

 "webpack-merge": "^4.1.0",

 "webpack-node-externals": "^1.6.0"

 }

ログイン後にコピー

3、项目主目录搭建

基本目录结构如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

├── LICENSE

├── README.md

├── build

│ ├── setup-dev-server.js

│ ├── vue-loader.config.js

│ ├── webpack.base.config.js

│ ├── webpack.client.config.js

│ └── webpack.server.config.js

├── log

│ ├── err.log

│ └── out.log

├── package.json

├── pmlog.json

├── server.js

└── src

 ├── App.vue

 ├── app.js

 ├── assets

 │ ├── images

 │ ├── style

 │ │ └── css.less

 │ └── views

 │  └── index.css

 ├── components

 │ ├── Banner.vue

 │ ├── BottomNav.vue

 │ ├── FloorOne.vue

 │ └── Header.vue

 ├── entry-client.js

 ├── entry-server.js

 ├── index.template.html

 ├── public

 │ ├── conf.js

 │ └── utils

 │  ├── api.js

 │  └── confUtils.js

 ├── router

 │ └── index.js

 ├── static

 │ ├── img

 │ │ └── favicon.ico

 │ └── js

 │  └── flexible.js

 ├── store

 │ ├── actions.js

 │ ├── getters.js

 │ ├── index.js

 │ ├── modules

 │ │ └── Home.js

 │ ├── mutationtypes.js

 │ └── state.js

 └── views

  └── index

   ├── conf.js

   ├── index.vue

   ├── mock.js

   └── service.js

ログイン後にコピー

文件目录基本介绍:

  1. views文件夹下分模块文件,模块文件下下又分模块本身的.vue文件(模版文件),index.js文件(后台数据交互文件),mock.js(本模块的mock假数据),conf.js(配置本模块一些参数,请求路径,模块名称等信息)

  2. components 公共组件文件夹

  3. router 主要存放前端路由配置文件,写法规范按照vue-router官方例子即可。

  4. store 主要是存放共享状态文件,里面包含action.js,getter.js,mutationtype.js等,后期会根据模块再细分这些。

  5. public 主要存放公共组件代码和项目使用的公共文件代码,例如后期我们将axios封装成公共的api库文件等等

  6. static文件夹代表静态文件,不会被webpack打包的

  7. app.js 是项目入口文件

  8. App.vue 是项目入口文件

  9. entry-client和entry-server分别是客户端入口文件和服务端的入口文件

  10. index.template.html是整个项目的模版文件

开始编写app.js项目入口代码

使用vue开发项目入口文件一般都会如下写法:

1

2

3

4

5

6

7

8

9

10

11

import Vue from &#39;vue&#39;;

import App from &#39;./index.vue&#39;;

import router from &#39;./router&#39;

import store from &#39;./store&#39;;

 

new Vue({

 el: &#39;#app&#39;,

 store,

 router,

 render: (h) => h(App)

});

ログイン後にコピー

这种写法是程序共享一个vue实例,但是在后端渲染中很容易导致交叉请求状态污染,导致数据流被污染了。

所以,避免状态单例,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例,同样router和store入口文件也需要重新创建一个实例。

为了配合webpack动态加载路由配置,这里会改写常规路由引入写法,这样可以根据路由路径来判断加载相应的组件代码:

1

2

3

import Home from &#39;../views/index/index.vue&#39;

// 改写成

component: () => (&#39;../views/index/index.vue&#39;)

ログイン後にコピー

以下是路由的基本写法router,对外会抛出一个createRouter方法来创建一个新的路由实例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

import Vue from &#39;vue&#39;

import Router from &#39;vue-router&#39;;

Vue.use(Router)

export function createRouter() {

 return new Router({

  mode: &#39;history&#39;,

  routes: [{

   name:&#39;Home&#39;,

   path: &#39;/&#39;,

   component: () =>

    import (&#39;../views/index/index.vue&#39;)

  }]

 })

}

ログイン後にコピー

以下是store状态管理的基本写法,对外暴露了一个createStore方法,方便每次访问创建一个新的实例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// store.js

import Vue from &#39;vue&#39;

import Vuex from &#39;vuex&#39;

import * as actions from &#39;./actions&#39;

import getters from &#39;./getters&#39;

import modules from &#39;./modules/index&#39;

Vue.use(Vuex)

export function createStore() {

 return new Vuex.Store({

 actions,

 getters,

 modules,

 strict: false

 })

}

ログイン後にコピー

结合写好的router和store入口文件代码来编写整个项目的入口文件app.js代码内容,同样最终也会对外暴露一个createApp方法,在每次创建app的时候保证router,store,app都是新创建的实例,这里还引入了一个vue路由插件vuex-router-sync,主要作用是同步路由状态(route state)到 store,以下是app.js完整代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import Vue from &#39;vue&#39;

import App from &#39;./App.vue&#39;

import { createRouter } from &#39;./router&#39;

import { createStore } from &#39;./store&#39;

import { sync } from &#39;vuex-router-sync&#39;

require(&#39;./assets/style/css.less&#39;);

export function createApp () {

 // 创建 router 和 store 实例

 const router = createRouter()

 const store = createStore()

 // 同步路由状态(route state)到 store

 sync(store, router)

 // 创建应用程序实例,将 router 和 store 注入

 const app = new Vue({

 router,

 store,

 render: h => h(App)

 })

 // 暴露 app, router 和 store。

 return { app, router, store }

}

ログイン後にコピー

entry-client.js代码编写:

首页引入从app文件中暴露出来的createApp方法,在每次调用客户端的时候,重新创建一个新的app,router,store,部分代码如下:

1

2

import { createApp } from &#39;./app&#39;

const { app, router, store } = createApp()

ログイン後にコピー

这里我们会使用到onReady方法,此方法通常用于等待异步的导航钩子完成,比如在进行服务端渲染的时候,例子代码如下:

1

2

3

4

5

import { createApp } from &#39;./app&#39;

const { app, router, store } = createApp()

router.onReady(() => {

 app.$mount('#app')

})

ログイン後にコピー

我们会调用一个新方法beforeResolve,只有在router2.5.0以上的版本才会有的方法,注册一个类似于全局路由保护router.beforeEach(),除了在导航确认之后,在所有其他保护和异步组件已解决之后调用。基本写法如下:

1

2

3

4

5

6

router.beforeResolve((to, from, next) => {

 // to 和 from 都是 路由信息对象

 // 返回目标位置或是当前路由匹配的组件数组(是数组的定义/构造类,不是实例)。通常在服务端渲染的数据预加载时时候。

 const matched = router.getMatchedComponents(to)

 const prevMatched = router.getMatchedComponents(from)

})

ログイン後にコピー

服务端把要给客户端的 state 放在了 window. INITIAL_STATE 这个全局变量上面。前后端的 HTML 结构应该是一致的。然后要把 store 的状态树写入一个全局变量( INITIAL_STATE ),这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构,并且同步到初始化状态,然后整个页面被客户端接管。基本代码如下:

1

2

3

4

// 将服务端渲染时候的状态写入vuex中

if (window.__INITIAL_STATE__) {

 store.replaceState(window.__INITIAL_STATE__)

}

ログイン後にコピー

接下来贴出来完整的客户端代码,这里的Q也可以不用引入,直接使用babel就能编译es6自带的Promise,因为本人使用习惯了,这里可以根据自身的需求是否安装:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

import { createApp } from &#39;./app&#39;

import Q from &#39;q&#39;

import Vue from &#39;vue&#39;

 

Vue.mixin({

 beforeRouteUpdate (to, from, next) {

 const { asyncData } = this.$options

 if (asyncData) {

  asyncData({

  store: this.$store,

  route: to

  }).then(next).catch(next)

 } else {

  next()

 }

 }

})

const { app, router, store } = createApp()

 

// 将服务端渲染时候的状态写入vuex中

if (window.__INITIAL_STATE__) {

 store.replaceState(window.__INITIAL_STATE__)

}

 

router.onReady(() => {

 router.beforeResolve((to, from, next) => {

  const matched = router.getMatchedComponents(to)

  const prevMatched = router.getMatchedComponents(from)

  // 我们只关心之前没有渲染的组件

  // 所以我们对比它们,找出两个匹配列表的差异组件

  let diffed = false

  const activated = matched.filter((c, i) => {

  return diffed || (diffed = (prevMatched[i] !== c))

  })

  if (!activated.length) {

  return next()

  }

  // 这里如果有加载指示器(loading indicator),就触发

  Q.all(activated.map(c => {

  if (c.asyncData) {

   return c.asyncData({ store, route: to })

  }

  })).then(() => {

  // 停止加载指示器(loading indicator)

  next()

  }).catch(next)

 })

 app.$mount(&#39;#app&#39;)

})

ログイン後にコピー

entry-server.js代码编写:

基本编写和客户端的差不多,因为这是服务端渲染,涉及到与后端数据交互定义的问题,我们需要在这里定义好各组件与后端交互使用的方法名称,这样方便在组件内部直接使用,这里根我们常规在组件直接使用ajax获取数据有些不一样,代码片段如下:

1

2

3

4

5

6

7

//直接定义组件内部asyncData方法来触发相应的ajax获取数据

if (Component.asyncData) {

 return Component.asyncData({

 store,

 route: router.currentRoute

 })

}

ログイン後にコピー

以下是完整的服务端代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

import { createApp } from &#39;./app&#39;

import Q from &#39;q&#39;

export default context => {

 return new Q.Promise((resolve, reject) => {

 const { app, router, store } = createApp()

 router.push(context.url)

 router.onReady(() => {

  const matchedComponents = router.getMatchedComponents()

  if (!matchedComponents.length) {

  return reject({ code: 404 })

  }

  // 对所有匹配的路由组件调用 `asyncData()`

  Q.all(matchedComponents.map(Component => {

  if (Component.asyncData) {

   return Component.asyncData({

   store,

   route: router.currentRoute

   })

  }

  })).then(() => {

  // 在所有预取钩子(preFetch hook) resolve 后,

  // 我们的 store 现在已经填充入渲染应用程序所需的状态。

  // 当我们将状态附加到上下文,

  // 并且 `template` 选项用于 renderer 时,

  // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。

  context.state = store.state

  resolve(app)

  }).catch(reject)

 }, reject)

 })

}

ログイン後にコピー

4、脚手架其他目录介绍:

到这里src下面主要的几个文件代码已经编写完成,接下里介绍下整个项目的目录结构如下:

主要几个文件介绍如下:

  1. build 主要存放webpack打包配置文件

  2. dist webpack打包后生成的目录

  3. log 使用pm2监控进程存放的日志文件目录

  4. server.js node服务器启动文件

  5. pmlog.json pm2配置文件

server.js入口文件编写

我们还需要编写在服务端启动服务的代码server.js,我们会使用到部分node原生提供的api,片段代码如下:

1

2

3

4

5

6

7

const Vue = require(&#39;vue&#39;)

const express = require(&#39;express&#39;)

const path = require(&#39;path&#39;)

const LRU = require(&#39;lru-cache&#39;)

const { createBundleRenderer } = require(&#39;vue-server-renderer&#39;)

const fs = require(&#39;fs&#39;)

const net = require(&#39;net&#39;)

ログイン後にコピー

大致思路是,引入前端模版页面index.template.html,使用express启动服务,引入webpack打包项目代码的dist文件,引入缓存模块(这里不做深入介绍,后期会单独详细介绍),判断端口是否被占用,自动启动其他接口服务。

引入前端模版文件并且设置环境变量为production,片段代码如下:

1

2

const template = fs.readFileSync(&#39;./src/index.template.html&#39;, &#39;utf-8&#39;)

const isProd = process.env.NODE_ENV === &#39;production&#39;

ログイン後にコピー

vue-server-renderer插件的具体使用,通过读取dist文件夹下的目录文件,来创建createBundleRenderer函数,并且使用LRU来设置缓存的时间,通过判断是生产环境还是开发环境,调用不同的方法,代码片段如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

const resolve = file => path.resolve(__dirname, file)

function createRenderer (bundle, options) {

 return createBundleRenderer(bundle, Object.assign(options, {

 template,

 cache: LRU({

  max: 1000,

  maxAge: 1000 * 60 * 15

 }),

 basedir: resolve(&#39;./dist&#39;),

 runInNewContext: false

 }))

}

let renderer;

let readyPromise

if (isProd) {

 const bundle = require(&#39;./dist/vue-ssr-server-bundle.json&#39;)

 const clientManifest = require(&#39;./dist/vue-ssr-client-manifest.json&#39;)

 renderer = createRenderer(bundle, {

 clientManifest

 })

} else {

 readyPromise = require(&#39;./build/setup-dev-server&#39;)(server, (bundle, options) => {

 renderer = createRenderer(bundle, options)

 })

}

ログイン後にコピー

使用express启动服务,代码片段如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

const server = express();

 

//定义在启动服务钱先判断中间件中的缓存是否过期,是否直接调用dist文件。

const serve = (path, cache) => express.static(resolve(path), {

 maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0

})

server.use(&#39;/dist&#39;, serve(&#39;./dist&#39;, true))

server.get(&#39;*&#39;, (req, res) => {

 const context = {

 title: &#39;hello&#39;,

 url: req.url

 }

 renderer.renderToString(context, (err, html) => {

 if (err) {

  res.status(500).end(&#39;Internal Server Error&#39;)

  return

 }

 res.end(html)

 })

})

ログイン後にコピー

判断端口是否被占用,片段代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

function probe(port, callback) {

 let servers = net.createServer().listen(port)

 let calledOnce = false

 let timeoutRef = setTimeout(function() {

  calledOnce = true

  callback(false, port)

 }, 2000)

 timeoutRef.unref()

 let connected = false

 servers.on(&#39;listening&#39;, function() {

  clearTimeout(timeoutRef)

 

  if (servers)

   servers.close()

 

  if (!calledOnce) {

   calledOnce = true

   callback(true, port)

  }

 })

 servers.on(&#39;error&#39;, function(err) {

  clearTimeout(timeoutRef)

 

  let result = true

  if (err.code === &#39;EADDRINUSE&#39;)

   result = false

 

  if (!calledOnce) {

   calledOnce = true

   callback(result, port)

  }

 })

}

const checkPortPromise = new Promise((resolve) => {

 (function serverport(_port) {

  let pt = _port || 8080;

  probe(pt, function(bl, _pt) {

   // 端口被占用 bl 返回false

   // _pt:传入的端口号

   if (bl === true) {

    // console.log("\n Static file server running at" + "\n\n=> http://localhost:" + _pt + &#39;\n&#39;);

    resolve(_pt);

   } else {

    serverport(_pt + 1)

   }

  })

 })()

 

})

checkPortPromise.then(data => {

 uri = &#39;http://localhost:&#39; + data;

 console.log(&#39;启动服务路径&#39;+uri)

 server.listen(data);

});

ログイン後にコピー

到这里,基本的代码已经编写完成,webpack打包配置文件基本和官方保持不变,接下来可以尝试启动本地的项目服务,这里简要的使用网易严选首页作为demo示例,结果如下:

第三部分 mockjs和axios配合使用

1、前言

上一节大致介绍了服务端和客户端入口文件代码内容,现在已经可以正常运行你的后端渲染脚手架了,这一节,跟大家分享下如何使用axios做ajax请求,如何使用mockjs做本地假数据,跑通本地基本逻辑,为以后前后端连调做准备。

2、前期准备

需要用npm安装axios,mockjs依赖包,由于mockjs只是代码开发的辅助工具,所以安装的时候我会加--save-dev来区分,具体可以根据自己的需求来定,当然,如果有mock服务平台的话,可以直接走mock平台造假数据,本地直接访问mock平台的接口,例如可以使用阿里的Rap平台管理工具生成。

1

2

npm install axios --save

npm install mockjs --save-dev

ログイン後にコピー

3、简要介绍axios

其他请求方式,代码示例如下:

1

2

3

4

5

6

7

axios.request(config);

axios.get(url[,config]);

axios.delete(url[,config]);

axios.head(url[,config]);

axios.post(url[,data[,config]]);

axios.put(url[,data[,config]])

axios.patch(url[,data[,config]])

ログイン後にコピー

具体详细可以点击查看axios基本使用介绍

api.js完整代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

import axios from &#39;axios&#39;

import qs from &#39;qs&#39;

import Q from &#39;q&#39;

/**

 * 兼容 不支持promise 的低版本浏览器

 */

require(&#39;es6-promise&#39;).polyfill();

import C from &#39;../conf&#39;

 

axios.defaults.headers.post[&#39;Content-Type&#39;] = &#39;application/x-www-form-urlencoded; charset=UTF-8&#39;

axios.defaults.withCredentials = true

 

function ajax(url, type, options) {

 

 return Q.Promise((resolve, reject) => {

 axios({

  method: type,

  url: C.HOST + url,

  params: type === &#39;get&#39; ? options : null,

  data: type !== &#39;get&#39; ? qs.stringify(options) : null

  })

  .then((result) => {

  if (result && result.status === 401) {

   // location.href = &#39;/views/401.html&#39;

  }

  if (result && result.status === 200) {

   if (result.data.code === 200) {

   resolve(result.data.data);

   } else if (result.data.code === 401) {

   reject({

    nopms: true,

    msg: result.data.msg

   });

   } else {

   reject({

    error: true,

    msg: result.data.msg

   });

   }

  } else {

   reject({

   errno: result.errno,

   msg: result.msg

   });

  }

  })

  .catch(function(error) {

  console.log(error, url);

  });

 })

}

 

const config = {

 get(url, options) {

 const _self = this;

 return Q.Promise((resolve, reject) => {

  ajax(url, &#39;get&#39;, options)

  .then((data) => {

   resolve(data);

  }, (error) => {

   reject(error);

  });

 })

 },

 

 post(url, options) {

 const _self = this;

 return Q.Promise((resolve, reject) => {

  ajax(url, &#39;post&#39;, options)

  .then((data) => {

   resolve(data);

  }, (error) => {

   reject(error);

  });

 })

 },

 

 put(url, options) {

 const _self = this;

 return Q.Promise((resolve, reject) => {

  ajax(url, &#39;put&#39;, options)

  .then((data) => {

   resolve(data);

  }, (error) => {

   reject(error);

  });

 })

 },

 

 delete(url, options) {

 const _self = this;

 return Q.Promise((resolve, reject) => {

  ajax(url, &#39;delete&#39;, options)

  .then((data) => {

   resolve(data);

  }, (error) => {

   reject(error);

  });

 })

 },

 

 jsonp(url, options) {

 const _self = this;

 return Q.Promise((resolve, reject) => {

  ajax(url, &#39;jsonp&#39;, options)

  .then((data) => {

   resolve(data);

  }, (error) => {

   reject(error);

  });

 })

 }

};

 

export default config;

ログイン後にコピー

mockjs项目基本配置如下:

1、在public下新建conf.js全局定义请求url地址,代码如下:

1

2

3

4

module.exports = {

 HOST: "http://www.xxx.com",

 DEBUGMOCK: true

};

ログイン後にコピー

2、在views/index根目录下新建conf.js,定义组件mock的请求路径,并且定义是否开始单个组件使用mock数据还是线上接口数据,代码如下:

1

2

3

4

5

6

7

8

9

10

const PAGEMOCK = true;

const MODULECONF = {

 index: {

 NAME: &#39;首页&#39;,

 MOCK: true,

 API: {

  GET: &#39;/api/home&#39;,

 }

 }

};

ログイン後にコピー

3、在组件内部定义mockjs来编写mock假数据,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

import Mock from &#39;mockjs&#39;;

const mData = {

 index: {

 API: {

  GET: {

  "code": 200,

  "data": {

   "pin": &#39;wangqi&#39;,

   "name": &#39;王奇&#39;

  }

  }

 }

 }

}

ログイン後にコピー

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在微信小程序中如何实现流程进度样式?

在vue中使用vue-cli如何搭建helloWorld

layui中有关取值传值方面的问题

详细解答vue的变化对组件有什么影响?

在vue-scroller中如何标记记录滚动位置

以上がvue-ssr を使用してサーバー側レンダリングを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート