Vue之狀態管理(vuex)與接口調用

Vue之狀態管理(vuex)與接口調用

一,介紹與需求

 1.1,介紹

1,狀態管理(vuex)

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。php

狀態管理核心html

  1. state裏面就是存放項目中須要多組件共享的狀態
  2. mutations就是存放更改state裏狀態的方法
  3. getters就是從state中派生出狀態,好比將state中的某個狀態進行過濾而後獲取新的狀態。
  4. actions就是mutation的增強版,它能夠經過commit mutations中的方法來改變狀態,最重要的是它能夠進行異步操做
  5. modules顧名思義,就是當用這個容器來裝這些狀態仍是顯得混亂的時候,咱們就能夠把容器分紅幾塊,把狀態和管理規則分類來裝。這和咱們建立js模塊是一個目的,讓代碼結構更清晰。

2,axios

axios 是一個基於Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端,它自己具備如下特徵:vue

  • 從瀏覽器中建立 XMLHttpRequest
  • 從 node.js 發出 http 請求
  • 支持 Promise API
  • 攔截請求和響應
  • 轉換請求和響應數據
  • 取消請求
  • 自動轉換JSON數據
  • 客戶端支持防止 CSRF/XSRF

  axios並無install 方法,因此是不能使用vue.use()方法的。node

解決方法有不少種:webpack

  • .結合 vue-axios使用
  • axios 改寫爲 Vue 的原型屬性

使用 vue-axiosios

在主入口文件main.js中引用es6

1 import axios from 'axios'
2 import VueAxios from 'vue-axios'
3 Vue.use(VueAxios,axios);

axios 改寫爲 Vue 的原型屬性web

在主入口文件main.js中引用ajax

1  import axios from 'axios'
2  Vue.prototype.$axios= axios;

3,vue-resource

vue-resource是Vue.js的一款插件,它能夠經過XMLHttpRequest或JSONP發起請求並處理響應。vuex

vue-resource還提供了很是有用的inteceptor功能,使用inteceptor能夠在請求前和請求後附加一些行爲,好比使用inteceptor在ajax請求時顯示loading界面。

vue-resource的請求API是按照REST風格設計的,它提供了7種請求API:

  • get(url, [options])
  • head(url, [options])
  • delete(url, [options])
  • jsonp(url, [options])
  • post(url, [body], [options])
  • put(url, [body], [options])
  • patch(url, [body], [options])

 vue-resource再也不繼續維護,推薦你們使用 axios 。

 1.2,需求

若是數據還有其餘組件複用,可放在vuex
若是須要跨多級組件傳遞數據,可放在vuex
須要持久化的數據(如登陸後用戶的信息),可放在vuex
跟當前業務組件強相關的數據,能夠放在組件內

二,狀態數據管理

vue項目搭建與部署

第一步:在開發環境下安裝vuex

1 cnpm install vuex --save-dev

第二步:引用vuex,並實例化vuex狀態庫

創建一個store文件夾,創建一個index.js。在index.js中引入vue和vuex,日誌等

 1 import Vue from 'vue'
 2 import Vuex from 'vuex'
 3 
 4 //每次修改state都會在控制檯打印log
 5 import createLogger from 'vuex/dist/logger'
 6 Vue.use(Vuex)
 7 
 8 
 9 
10 export default new Vuex.Store({
11     actions:{},
12     getters:{},
13     state:{},
14     mutations:{},
15 })

第三步:store文件夾下建立state.js文件

這是咱們初始化數據的 ,也是以後咱們存數據的地方

1 const state = {
2    userInfo: {},
3    MenuList: {}
4 }
5  export default state;

第四步:store文件夾下建立mutations.js文件

提交 mutations 是更改 vuex中 store 中狀態的 惟一方法

 1 import * as types from './mutation-types'
 2 import roleTokencate from "../caches/roleTokencate";
 3 
 4 const mutations = {
 5   /*
 6   * 登陸
 7    */
 8   [types.SET_USERINFO](state, userInfo) {
 9     console.log(types.SET_USERINFO, userInfo)
10     roleTokencate(userInfo); //存入緩存
11    
12     state.userInfo = userInfo
13   },
14    /*
15   * 獲取菜單列表
16    */
17   [types.SET_MENULIST](state, data={}) {
18     state.MenuList = data
19   }
20 }
21 
22 export default mutations
建立mutation-types.js文件,主要類型區分
1 export const SET_USERINFO = "userInfo";//登陸返回的用戶信息
2 export const SET_MENULIST = "MenuList";//返回的菜單列表

第五步:store文件夾下建立getters.js文件

vue 的計算屬性

1 export const userInfo = state => state.userInfo
2 export const MenuList = state => state.MenuList

第六步:store文件夾下建立actions.js文件

Action 提交的是 mutation,而不是直接變動狀態。 Action 能夠包含任意異步操做。 

 1 import * as types from './mutation-types'
 2 import { getCurrUserMenu } from "./../services/auth";
 3 
 4 /**
 5  * 登陸 獲取用戶信息
 6  * @param context:與 store 實例具備相同方法和屬性的 context 對象
 7  * @param Object:需管理的數據
 8  */
 9 export function getUserInfoSync (context,Object) {//2.接受dispatch傳遞過來的方法和參數
10     //處理異步操做
11     setTimeout(()=>{
12         //3.經過commit提交一個名爲getParam的mutation
13         //action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象,所以你能夠調用 context.commit 提交一個 mutation
14         context.commit(types.SET_USERINFO,Object)
15     },1000)
16 };
17 
18 /**
19  * 獲取菜單列表
20  * @param context:與 store 實例具備相同方法和屬性的 context 對象
21  * @param Object:需管理的數據
22  */
23 export function getMenuListSync (context,Object) {
24         context.commit(types.SET_MENULIST,Object)
25 }

第七步:store文件夾下建立modules文件夾

對應模塊js文件中,這裏我使用的home.js文件中編寫state、actions和mutations ,getters 等

vuex自帶模塊化方法,爲namespaced:true。經過對模塊進行命名空間設置,就能分模塊進行管理。

 1 const state = {
 2     initInfo: 'hello jackson'
 3 }
 4 const getters = {
 5     initInfo(state, getters) {
 6         return state.initInfo
 7     }
 8 }
 9 const actions = {
10     getInfo({commit, state},data) {
11         console.log('getInfo==',data)
12         commit('updateInitInfo', 'getInfo')
13     }
14 }
15 const mutations = {
16     updateInitInfo(state, string) {
17         state.initInfo = string
18         console.log('home update', string)
19     }
20 }
21     
22 export default {
23     namespaced: true,
24     state,
25     getters,
26     actions,
27     mutations
28 }

第八步:在開發環境下,開啓嚴格模式

引入日誌打印:

1  //每次修改state都會在控制檯打印log
2 import createLogger from 'vuex/dist/logger'

判斷是不是開發環境

1 const debug = process.env.NODE_ENV !== 'production'

添加到Vuex.Store庫中

1 export default new Vuex.Store({
2 
3  ...
4 
5     strict: debug, // 當debug=true時開啓嚴格模式(性能有損耗)
6    plugins: debug ? [createLogger()] : []
7 })

第九步:將上面建立的文件與方法,引入的第二步建立的store入口文件index.js中

 1 import Vue from 'vue'
 2 import Vuex from 'vuex'
 3 import * as actions from './actions'
 4 import * as getters from './getters'
 5 import state from './state'
 6 import mutations from './mutations'
 7 import home from './modules/home'
 8 
 9 //每次修改state都會在控制檯打印log
10 import createLogger from 'vuex/dist/logger'
11 Vue.use(Vuex)
12 
13 const debug = process.env.NODE_ENV !== 'production'
14 
15 export default new Vuex.Store({
16     actions,
17     getters,
18     state,
19     mutations,
20     modules: {
21         home,
22     },
23     strict: debug, // 當debug=true時開啓嚴格模式(性能有損耗)
24     plugins: debug ? [createLogger()] : []
25 })

第十步:在項目的入口文件main.js中註冊使用

 1 import 'amfe-flexible'
 2 // The Vue build version to load with the `import` command
 3 // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
 4 import Vue from 'vue'
 5 // By default we import all the components.
 6 // Only reserve the components on demand and remove the rest.
 7 // Style is always required.
 8 import VueResource from 'vue-resource'
 9 
10 import App from './App'
11 import router from '../router'
12 import store from '../store'
13 Vue.config.productionTip = false
14 Vue.use(VueResource)
15 /* eslint-disable no-new */
16 new Vue({
17   el: '#app',
18   router,
19   store,
20   template: '<App/>',
21   components: { App }
22 })

第十一步:在xxx.vue中使用

1,設置數據

(1),調用actions中的方法

1  this.$store.dispatch('getUserInfoSync',res.data.data[0])

(2),調用mutations中的方法

引入mapMutations 

1 import { mapMutations } from "vuex";

使用

 1 import { mapMutations } from "vuex";
 2 export default {
 3   name: "Login",
 4   data() {
 5     return {
 6       loginCode: undefined,
 7       password: undefined,
 8       isEye: true,
 9       isChecked: true
10     };
11   },
12   mounted() {
13   
14   },
15   methods: {
16    
17     // 登錄
18     loginAction() {
19       let loginData = {
20         loginCode: this.$data.loginCode,
21         password: this.$data.password
22       };
23       if (this.$data.isChecked) {
24         loginRememberCate(loginData);
25       }
26       //不簡寫login
27       this.$http(login(loginData))
28         //es6寫法 .then()部分
29         .then(res => {
30           console.log(res.data);
31           if (res.data.httpCode === 200) {
32             if (res.data.data && res.data.data.length > 0) {
33                this.setUserInfo(res.data.data[0]);
34             }
35           }
36         })
37         .catch(err => {
38  39           console.log("錯誤信息==", err.data);
40         });
41     },
42       ...mapMutations({
43        setUserInfo: "SET_USERINFO"
44      })
45   }
46 };

2,獲取數據

 引入mapGetters

1 import {mapGetters} from 'vuex'

使用

1  computed:{
2    ...mapGetters([
3      'userInfo','MenuList'
4   ])
5 },
6   mounted() {
7     console.log('this.userInfo==',this.userInfo);
8     console.log('this.MenuList==',this.MenuList);
9   },

三,接口調用

3.1,axios訪問接口

第一步:安裝axios

1 cnpm install axios --save

第二步:引入axios並封裝

QS是axios庫中帶的,不須要咱們再npm安裝一個

 1 import axios from 'axios'
 2 import QueryString from 'qs';
 3 
 4 function checkHttpStatus(response) {
 5   if (response.status >= 200 && response.status < 300) {
 6     return response;
 7   }
 8   const error = new Error(response.statusText);
 9   error.response = response;
10   error.code = response.status;
11   throw error;
12 }
13 
14 function getResult(json) {
15   if (json.status === 200) {
16     let result = { result: json.data.data };
17     return result;
18   }
19 }
20 /**
21       * 通用配置
22       * @param url:接口地址
23       * @param options:配置
24       * @param return{*}
25       */
26 function request(url = '', options = {}, cache) {
27   // debugger
28   console.info('request ' + url);
29   let data;
30   let contentType;
31   if (typeof cache === 'function') {
32     data = cache();
33     if (data) {
34       return Promise.resolve(data);
35     }
36   }
37   data = options.data;
38   delete options.data;
39   contentType = options.contentType;
40   delete options.contentType;
41   const opts = {
42     method: 'POST',
43     url,
4445     ...options
46   };
47   opts.headers = {
48     ...opts.headers,
49   };
50   if (opts.method === 'GET') {
51     url = url.split('?');
52     url = url[0] + '?' + QueryString.stringify(url[1] ? { ...QueryString.parse(url[1]), ...data } : data);
53     opts.headers['content-type'] = contentType ? contentType : 'application/json'; //
54   } else {
55     opts.headers['content-type'] = contentType ? contentType : 'application/json'; //
56     opts.data= contentType === 'application/x-www-form-urlencoded' ? serialize(data) : JSON.stringify(data);
57   }
58   // 支持處理緩存
59   const handleCache = data => {
60     typeof cache === 'function' && cache(data.result);
61     return data;
62   };
63 
64   return axios(opts)
65     .then(checkHttpStatus)
66     .then(getResult)
67     .then(handleCache)
68     .catch(err => ({ err }));
69 }
70 export default request;
71 

第三步:使用axios

1 import requestAxios from './requestAxios';
2 import { POST, PUT } from '../utils/const';
3 
4 /*
5 ***獲取可訪問菜單***
6 */
7 export function getCurrUserMenu(data) {
8   return requestAxios('/api/v1/yingqi/user/getCurrUserMenu', { data, method: PUT });
9 }

在store中actions中調用接口

 1 import { getCurrUserMenu } from "./../services/auth";
 2 
 3 export function getMenuListAxiosSync (context,payload) {
 4     getCurrUserMenu(payload.data)
 5     .then(action => {
 6         // alert('action中調用封裝後的axios成功');
 7         payload.getResult(action.result)
 8         console.log('action中調用封裝後的axios成功',action.result)
 9         context.commit(types.SET_MENULIST, action.result)
10     })
11 }

3.2,vue-resource訪問接口

第一步:安裝vue-resource

1 cnpm install vue-resource --save

第二步:在入口文件中引入使用

1 import VueResource from 'vue-resource'
2 Vue.use(VueResource)

封裝共同訪問參數

 1 /**
 2       * 通用配置
 3       * @param url:接口地址
 4       * @param options:配置
 5       * @param return{*}
 6       */
 7 export function request(url, options) {
 8   //      // post 傳參數 須要加 {emulateJSON:true}
 9   //      this.$http.post('in.php',{a:1,b:2},{emulateJSON:true}).then( (res) => {
10   //         console.log(res.data)
11   //   } )
12 
13   //  // get傳參數 須要 {params: {你傳的值}}
14   //  this.$http.get('getin.php',{params: {a:1,b:2}}).then( (res) => {
15   //       console.log(res.data)
16   //  })
17 
18   //  // jsonp 傳參數
19   //  this.$http.jsonp("https://sug.so.360.cn/suggest",{params:{word:'a'}}).then( (res)=>{
20   //       console.log(res.data.s)
21   //  })
22   let data;
23   let contentType;
24   data = options.data;
25   delete options.data;
26   contentType = options.contentType;
27   delete options.contentType;
28   const opts = {
29     method: 'POST',
30     url,
31     emulateJSON: true,
32     ...options
33   };
34   opts.headers = {
35     ...opts.headers,
36   };
37   opts.headers['content-type'] = contentType ? contentType : 'application/json'; //
38   opts.body = contentType === 'application/x-www-form-urlencoded' ? serialize(data) : JSON.stringify(data);
39 
40   return opts;
41 }
42 export default request;

第三步:使用

1 import request from './request';//request
2 import { POST, PUT } from '../utils/const';
3 /*
4 ***登錄***
5 */
6 export function login(data) {
7   return request('/api/v1/yingqi/user/login', { data, method: POST });
8 }

在xxx.vue中調用接口

 1 import { login } from "../../services/auth";
 2 ...
 3  
 4 this.$http(login(loginData))
 5         //es6寫法 .then()部分
 6         .then(res => {
 7           console.log(res.data);
 8           if (res.data.httpCode === 200) {
 9             if (res.data.data && res.data.data.length > 0) {
10               console.log("res.data.data", res.data.data[0]);
11             }
12           }
13         })
14         .catch(err => {
15   16           console.log("錯誤信息==", err.data);
17         });
18 
19 ...

3.3,axios攔截器interceptors訪問接口

第一步:建立axios實例

1 const service = axios.create({
2   baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
3   withCredentials: true, // send cookies when cross-domain requests
4   timeout: 50000 // request timeout
5 })

第二步:請求攔截器

 1 service.interceptors.request.use(
 2   config => {
 3     // 發送請求以前作配置 通常配置token
 4    //config.headers['X-Token'] = getToken()
 5     return config
 6   },
 7   error => {
 8     // 請求異常
 9     console.log(error) // for debug
10     return Promise.reject(error)
11   }
12 )

第三步:返回攔截器

 1 service.interceptors.response.use(
 2   /**
 3    * 若是您想得到諸如頭信息或狀態信息
 4    * Please return  response => response
 5   */
 6 
 7   response => {
 8     const res = response.data
 9     if (res.code !== 0) {//狀態碼錯誤/異常時處理
10       return Promise.reject(res.message || 'error')
11     } else {//請求成功返回
12       return res
13     }
14   },
15   error => {//接口返回異常
16     console.log('err' + error) // for debug
17     return Promise.reject(error)
18   }
19 )

第四步:調用封裝

 1 export function login(data) {
 2   return request({
 3     url: '/api/v1/yingqi/user/login',
 4     method: 'post',
 5     data: {
 6       loginCode: data.loginCode,
 7       password: data.password
 8     }
 9   })
10 }

第五步:在狀態管理裏調用

 1 const actions = {
 2   // user login
 3   login({ commit }, userInfo) {
 4     const { loginCode, password } = userInfo
 5     return new Promise((resolve, reject) => {
 6       login({ loginCode: loginCode.trim(), password: password }).then(response => {
 7         const { data } = response
 8         commit('SET_TOKEN', data.token)
 9         resolve()
10       }).catch(error => {
11         reject(error)
12       })
13     })
14   },
15 }

第六步:在頁面調用actions

 1  handleLogin() {
 2 // this.loginForm= {loginCode: 'loginCode', password: '123456'},
 3       this.$refs.loginForm.validate(valid => {
 4         if (valid) {
 5           this.loading = true
 6           this.$store.dispatch('user/login', this.loginForm)
 7             .then(() => {
 8               //路由跳轉 9               this.loading = false
10             })
11             .catch(() => {
12               this.loading = false
13             })
14         } else {
15           console.log('error submit!!')
16           return false
17         }
18       })
19     }

四,常見問題

1,刷新後,在vuex中的數據會丟失

vuex 刷新 數據丟失問題

解決思路: localStorage 本地存儲

解決辦法:

mutations.js 裏面存數據,不用每一個組件都存一次

 1 import * as types from './mutation-types'
 2 import roleTokencate from "../caches/roleTokencate";
 3 import commonCache from "../caches/commonCache";
 4 
 5 const mutations = {
 6   /*
 7   * 登陸
 8    */
 9   [types.SET_USERINFO](state, userInfo) {
10     console.log(types.SET_USERINFO, userInfo)
11     roleTokencate(userInfo); //存入緩存
12     commonCache(types.SET_USERINFO,userInfo); //存入緩存 防止數據丟失
13     state.userInfo = userInfo
14   },
15 }
16 
17 export default mutations

在state.js 裏面 加入如下代碼 :

1 import commonCache from "../caches/commonCache";
2 
3 ...
4 
5   for (var item in state) {
6     let getCacheData = commonCache(item);//從緩存中獲取數據
7     getCacheData ? (state[item] = typeof getCacheData ==='string'?JSON.parse(getCacheData):getCacheData) : false;//防止頁面刷新vuex中的數據丟失
8   }

2,axios請求https後臺接口時,老是走error

axios開發環境配置代理請求https後臺接口時,若是是ip地址,例如thinkjs後臺接口地址https:127.0.0.1:8080,就會老是走error,沒法正常獲取後臺接口的返回值;可是若是是域名的話,則無這種狀況。

解決思路:target默認狀況下,不接受運行在HTTPS上,且使用了無效證書的後端服務器。若是你想要接受, 則需設置secure爲false;

解決辦法:在配置代理proxyTable裏添加以下屬性

1  // 是否驗證SSL證書
2   secure: false,

完整的配置以下:

 1  proxyTable: {
 2       "/api": {
 3         // 傳遞給http(s)請求的對象
 4         target: "http://127.0.0.1:8880",
 5          // 是否將主機頭的源更改成目標URL
 6          changeOrigin: true,
 7          // 是否代理websocket
 8          ws: true,
 9          // 是否驗證SSL證書
10          secure: false,
11          // 重寫set-cookie標頭的域,刪除域名
12          cookieDomainRewrite: '',
13          // 代理響應事件
14          //onProxyRes: onProxyRes,
15          // 重寫目標的url路徑
16          pathRewrite: {
17           '^/api' : '/api'
18         }
19       }
代理響應事件
 1 /**
 2  * 過濾cookie path,解決同域下不一樣path,cookie沒法訪問問題
 3  * (實際上不一樣域的cookie也共享了)
 4  * @param proxyRes
 5  * @param req
 6  * @param res
 7  */
 8 function onProxyRes (proxyRes, req, res) {
 9   let cookies = proxyRes.headers['set-cookie']
10   // 目標路徑
11   let originalUrl = req.originalUrl
12   // 代理路徑名
13   let proxyName = originalUrl.split('/')[1] || ''
14   // 開發服url
15   let server = configDev.servers[proxyName]
16   // 後臺工程名
17   let projectName = server.substring(server.lastIndexOf('/') + 1)
18   // 修改cookie Path
19   if (cookies) {
20       let newCookie = cookies.map(function (cookie) {
21           if (cookie.indexOf(`Path=/${projectName}`) >= 0) {
22               cookie = cookie.replace(`Path=/${projectName}`, 'Path=/')
23               return cookie.replace(`Path=//`, 'Path=/')
24           }
25           return cookie
26       })
27       // 修改cookie path
28       delete proxyRes.headers['set-cookie']
29       proxyRes.headers['set-cookie'] = newCookie
30   }
31 }
1 secure: false,  // 若是是https接口,須要配置這個參數

如需完整代碼,請先留言評論加關注

相關文章
相關標籤/搜索