zoukankan      html  css  js  c++  java
  • Vue之状态管理(vuex)与接口调用

    Vue之状态管理(vuex)与接口调用

    一,介绍与需求

     1.1,介绍

    1,状态管理(vuex)

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

    状态管理核心

    1. state里面就是存放项目中需要多组件共享的状态
    2. mutations就是存放更改state里状态的方法
    3. getters就是从state中派生出状态,比如将state中的某个状态进行过滤然后获取新的状态。
    4. actions就是mutation的加强版,它可以通过commit mutations中的方法来改变状态,最重要的是它可以进行异步操作
    5. modules顾名思义,就是当用这个容器来装这些状态还是显得混乱的时候,我们就可以把容器分成几块,把状态和管理规则分类来装。这和我们创建js模块是一个目的,让代码结构更清晰。

    2,axios

    axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

    • 从浏览器中创建 XMLHttpRequest
    • 从 node.js 发出 http 请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据
    • 取消请求
    • 自动转换JSON数据
    • 客户端支持防止 CSRF/XSRF

      axios并没有install 方法,所以是不能使用vue.use()方法的。

    解决方法有很多种:

    • .结合 vue-axios使用
    • axios 改写为 Vue 的原型属性

    使用 vue-axios

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

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

    axios 改写为 Vue 的原型属性

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

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

    3,vue-resource

    vue-resource是Vue.js的一款插件,它可以通过XMLHttpRequest或JSONP发起请求并处理响应。

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

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

  • 相关阅读:
    docker compose 笔记
    一个简单的计划
    译Node.js应用的持续部署
    Javascript中的字典和散列
    施耐德保护调试技巧
    施耐德Sepam 40系列备自投逻辑
    请随时告诉自己
    顺其自然
    启用
    我们能做的是......
  • 原文地址:https://www.cnblogs.com/jackson-yqj/p/10303364.html
Copyright © 2011-2022 走看看