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接口,须要配置这个参数

如需完整代码,请先留言评论加关注

原文出处:https://www.cnblogs.com/jackson-yqj/p/10303364.html

相关文章
相关标签/搜索