讓你也學會某開源平臺的vuex模塊化源碼應用

先告訴閱讀本篇文章的同窗本人水平有限,菜鳥一隻:smile:。javascript

  • 繼上次分享了在vue官方文檔的一些小細節,不少大佬說基本操做,不夠深,多看看源碼
  • 其實本人也只是個小菜鳥,來分享的也是本身學習過程,源碼解說不了,不過最近恰好在看
  • 而後今天分享的內容是某開源框架裏的vuex應用,會上代碼,而不是教什麼是mutation什麼是getters,怎麼用action等等。

提示:本篇文章不適合沒用過vuex的新人或已經在大型項目中熟練使用vuex的大佬閱讀前端

首先我這篇分享基於某開快速開發平臺的源碼,大概花了2天專門整理了一下它前端項目中的vuex應用模式,可是我要先給大家講個我以爲蠻搞笑的事情,大家會很感興趣。vue

首先,這個框架不是我選的……,我只是被選用來填坑,固然本人不介意,該框架開源4個月,可是官方文檔從開始到如今幾乎是沒法吐槽的「簡潔」,因此開發過程基本上都是本身查源碼。java

在上個月的某一天該框架發佈了一個小版本,可是在版本日誌中出現了明顯的連接放錯了,找不到修復該問題後的正確使用連接,介於我對於該框架文檔簡陋的體驗已久且我很關注這個問題,我在他們論壇裏發了貼,吐槽版本日誌連接都放錯了。結果次日:node

在這裏插入圖片描述
nice!我去看了看以前錯的版本日誌,更nice!:smile:把我帖子刪了的同時,默默還把本身的錯誤改了回來。(帖子除了這個標題,我還發了一句「更新的版本日誌的第幾個錯誤的連接放錯了,進去打開找不到演示說明」大概這意思,沒有任何惡語相向)

好了,故事結束了,仍是開心的說代碼吧! sql

在這裏插入圖片描述

首先各位能夠看一看封面圖(封面圖展現不全已替換在文章中插入),是我總結的大概該項目vuex應用結構,咱們一點一點講解。vuex

index模塊

main引入store就不用說了,不過須要注意這個地方引入的是路徑的方式json

//main.js

import store from './store/'
複製代碼

在這裏插入圖片描述
由於引入方式是使用了路徑,因此會默認加載路徑中的index.js(這個地方可能不少人就不理解,當引入方式爲路徑時,將其視爲一個CommonJs包處理,查找規則依次爲目錄下爲index.js,index.json,index.node),index的內容是:

import app from './modules/app'
import user from './modules/user'
import permission from './modules/permission'
import getters from './getters'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    app,
    user,
    permission
  },
  getters
})
複製代碼

這部份內容就很簡單,將store分紅了幾個模塊,在index中統一export出去供全局調用。api

module(store模塊)

建議對照個人封面圖看這一部分緩存

這一部分,實際上是就是咱們常看的入門教學中的state.js文件,將其根據業務拆分紅了app.js(全局狀態配置),permission.js(存放路由,包含對路由作操做的actions),user.js(存放用戶信息,包含登陸接口和登陸處理我的信息的actions),結合代碼分別來介紹一下里面的內容應用:

app.js

import Vue from 'vue'
import {
  SIDEBAR_TYPE,
  DEFAULT_THEME,
  DEFAULT_LAYOUT_MODE
} from "@/store/mutation-types"

const app = {
  state: {
    sidebar: {
      opened: true,
      withoutAnimation: false
    },
    device: 'desktop',
    theme: '',
    layout: '',
    contentWidth: '',
    fixedHeader: false,
    fixSiderbar: false,
    autoHideHeader: false,
    color: null,
    weak: false,
    multipage: false //默認多頁籤模式
  },
  mutations: {
    SET_SIDEBAR_TYPE: (state, type) => {
      //將系統sidebar爲展開或是摺疊狀態
      state.sidebar.opened = type
      //修改state狀態以後,使用localstorage存儲state相應的變量,
      //解決vuex狀態刷新丟失問題
      Vue.ls.set(SIDEBAR_TYPE, type)//ls爲vue-ls的使用方式,至關於setLocalstorage
    },
    CLOSE_SIDEBAR: (state, withoutAnimation) => {
      Vue.ls.set(SIDEBAR_TYPE, true)
      state.sidebar.opened = false
      state.sidebar.withoutAnimation = withoutAnimation
    },
    TOGGLE_DEVICE: (state, device) => {
      state.device = device
    },
    TOGGLE_THEME: (state, theme) => {
      // setStore('_DEFAULT_THEME', theme)
      Vue.ls.set(DEFAULT_THEME, theme)
      state.theme = theme
    }
    
    ·······
    
  },
  actions: {
  	//action中存放的都是對mutations的異步操做,很簡單
    setSidebar: ({ commit }, type) => {
      //異步觸發mutations的SET_SIDEBAR_TYPE
      commit('SET_SIDEBAR_TYPE', type)
    },
    CloseSidebar({ commit }, { withoutAnimation }) {
      commit('CLOSE_SIDEBAR', withoutAnimation)
    },
    ToggleDevice({ commit }, device) {
      commit('TOGGLE_DEVICE', device)
    },
    ToggleTheme({ commit }, theme) {
      commit('TOGGLE_THEME', theme)
    }
    
    ·······
    
  }
}

export default app

複製代碼

該文件中就是常見的state,mutations,actions三部分,而其中開始處引用了mutation-types文件,裏面存放的只是一些統一的字符串變量名,用於在localstorage中存貯時的鍵名,幾乎都是大寫,能夠先貼出來,後面不單獨講解:

export const ACCESS_TOKEN = 'Access-Token'
export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
export const DEFAULT_THEME = 'DEFAULT_THEME'
export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
export const DEFAULT_COLOR = 'DEFAULT_COLOR'
export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK'
export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
export const DEFAULT_FIXED_SIDEMENU= 'DEFAULT_FIXED_SIDEMENU'
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
export const DEFAULT_MULTI_PAGE = 'DEFAULT_MULTI_PAGE'
export const USER_NAME = 'Login_Username'
export const USER_INFO = 'Login_Userinfo'
export const USER_AUTH = 'LOGIN_USER_BUTTON_AUTH'
export const SYS_BUTTON_AUTH = 'SYS_BUTTON_AUTH'

複製代碼

每一個state幾乎都是同樣的,後面只會在每一個對象的第一個變量或者方法中作簡單註釋,應該有vuex應用基礎的就能夠看懂,仍是很簡單的。 能夠看一下對SET_SIDEBAR_TYPE的調用:

mounted () {
	//系統初始化,讀取localstorage的SIDEBAR_TYPE值調用SET_SIDEBAR_TYPE
	store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))
}
複製代碼

思路比較簡單,將全部要存儲的state在localstorage中存儲時,使用系通通一對應的字符串名字建立鍵值對存儲,對相應的state進行操做的mutations方法中也進行localstorage更新。

permission.js

permission中存放的是路由信息,以及根據角色對路由進行操做的方法,該框架使用的是前端只存放基礎路由。

//router.config裏面存放基礎路由
import { asyncRouterMap, constantRouterMap } from "@/config/router.config"

/** * 過濾帳戶是否擁有某一個權限,並將菜單從加載列表移除 */
function hasPermission(permission, route) {
  if (route.meta && route.meta.permission) {
    let flag = -1
    for (let i = 0, len = permission.length; i < len; i++) {
      flag = route.meta.permission.indexOf(permission[i])
      if (flag >= 0) {
        return true
      }
    }
    return false
  }
  return true
}

/** * 單帳戶多角色時,使用該方法可過濾角色不存在的菜單 */
// eslint-disable-next-line
function hasRole(roles, route) {
  if (route.meta && route.meta.roles) {
    return route.meta.roles.indexOf(roles.id)
  } else {
    return true
  }
}

function filterAsyncRouter(routerMap, roles) {
  const accessedRouters = routerMap.filter(route => {
    if (hasPermission(roles.permissionList, route)) {
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, roles)
      }
      return true
    }
    return false
  })
  return accessedRouters
}


const permission = {
  state: {
    routers: constantRouterMap,//基礎路由
    addRouters: []//根據用戶角色判斷後要添加的剩餘路由
  },
  mutations: {
    SET_ROUTERS: (state, data) => {
    //添加路由,併合並完整路由到routers變量,實際應用加載的路由爲routers 
      state.addRouters = data
      state.routers = constantRouterMap.concat(data)
    }
  },
  actions: {
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        const { roles } = data
        let accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
        resolve()
      })
    },
    // 動態添加主界面路由,須要緩存
    UpdateAppRouter({ commit }, routes) {
      return new Promise(resolve => {
        let routelist = routes.constRoutes;
        resolve()
      })
    }
  }
}

export default permission
複製代碼

而後看一下對路由state的操做應用,對於路由的操做沒有直接調用mutations中的SET_ROUTERS,而是採用了異步的UpdateAppRouter去更新:

let constRoutes = [];
  constRoutes = generateIndexRouter(menuData);
  // 添加主界面路由
  store.dispatch('UpdateAppRouter',  { constRoutes }).then(() => {
    // 根據roles權限生成可訪問的路由表
    // 動態添加可訪問路由表
    router.addRoutes(store.getters.addRouters)
    const redirect = decodeURIComponent(from.query.redirect || to.path)
    if (to.path === redirect) {
      // hack方法 確保addRoutes已完成 
      next({ ...to, replace: true })
    } else {
      // 跳轉到目的路由
      next({ path: redirect })
    }
  })
複製代碼

但願你能夠仔細閱讀一下這段代碼,其中在router(此處router指的是vue的router而不是router變量)的addRoutes時,所添加的路由是經過store的getters方法,而後方便理解也放一下getters的內容,後面不會單獨講解:

import Vue from 'vue'
import { USER_INFO} from "@/store/mutation-types"
const getters = {
  device: state => state.app.device,
  theme: state => state.app.theme,
  color: state => state.app.color,
  token: state => state.user.token,
  avatar: state => {state.user.avatar = Vue.ls.get(USER_INFO).avatar; return state.user.avatar},
  username: state => state.user.username,
  nickname: state => {state.user.realname = Vue.ls.get(USER_INFO).realname; return state.user.realname},
  welcome: state => state.user.welcome,
  permissionList: state => state.user.permissionList,
  userInfo: state => {state.user.info = Vue.ls.get(USER_INFO); return state.user.info},
  addRouters: state => state.permission.addRouters//獲取用戶權限所擁有的route
}

export default getters
複製代碼

getters內容更簡單,就是從各個module中的state中獲取變量。

整個對路由的操做就很清晰了:

  1. 整個路由操做過程就比較清晰了,經過調用routestate的異步方法UpdateAppRouter去操做SET_ROUTERS設置state中的addRouters和routers變量(即用戶所擁有的權限路由),
  2. 而後使用vueRouter的router.addRoutes('要添加的路由')方法去補全完整路由,要添加的路由則經過store的getters方法獲取route中的的addRouters變量。

user.js

user.js裏面存放主要是用戶的登陸以及登錄後對token和用戶信息在vuex中的處理,我以爲這部分能夠給不少人的項目中借鑑,放上完整代碼:

import Vue from 'vue'
import { login, logout } from "@/api/login" //登陸登出api調用方法
import { ACCESS_TOKEN, USER_NAME,USER_INFO,USER_AUTH,SYS_BUTTON_AUTH } from "@/store/mutation-types"
import { welcome } from "@/utils/util"

const user = {
  state: {
    token: '', //用戶token
    username: '',
    realname: '',
    welcome: '',
    avatar: '',
    permissionList: [],
    info: {}
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      //setToken在state中
      state.token = token
    },
    SET_NAME: (state, { username, realname, welcome }) => {
      state.username = username
      state.realname = realname
      state.welcome = welcome
    },
    SET_AVATAR: (state, avatar) => {
      state.avatar = avatar
    },
    SET_PERMISSIONLIST: (state, permissionList) => {
      state.permissionList = permissionList
    },
    SET_INFO: (state, info) => {
      state.info = info
    },
  },

  actions: {
    // 登陸
    Login({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        //調用登陸接口
        login(userInfo).then(response => {
          if(response.code =='200'){
            const result = response.result
            const userInfo = result.userInfo
            //在localstorage和state中存儲token等信息,固然localstorage設置了有效期
            Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
            Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
            Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
            commit('SET_TOKEN', result.token)
            commit('SET_INFO', userInfo)
            commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname, welcome: welcome() })
            commit('SET_AVATAR', userInfo.avatar)
            resolve(response)
          }else{
            reject(response)
          }
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 獲取用戶信息
    GetPermissionList({ commit }) {
      return new Promise((resolve, reject) => {
        let v_token = Vue.ls.get(ACCESS_TOKEN);
        let params = {token:v_token};
        queryPermissionsByUser(params).then(response => {
          const menuData = response.result.menu;
          const authData = response.result.auth;
          const allAuthData = response.result.allAuth;
          //Vue.ls.set(USER_AUTH,authData);
          sessionStorage.setItem(USER_AUTH,JSON.stringify(authData));
          sessionStorage.setItem(SYS_BUTTON_AUTH,JSON.stringify(allAuthData));
          if (menuData && menuData.length > 0) {
            commit('SET_PERMISSIONLIST', menuData)
          } else {
            reject('getPermissionList: permissions must be a non-null array !')
          }
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 登出
    Logout({ commit, state }) {
      return new Promise((resolve) => {
        let logoutToken = state.token;
        //清除state和localstorage中的token等信息
        commit('SET_TOKEN', '')
        commit('SET_PERMISSIONLIST', [])
        Vue.ls.remove(ACCESS_TOKEN)
        //console.log('logoutToken: '+ logoutToken)
        logout(logoutToken).then(() => {
          resolve()
        }).catch(() => {
          resolve()
        })
      })
    },

  }
}

export default user

複製代碼

而後咱們看一下對它的代碼調用:

import {mapActions} from "vuex"
 
 ······
 
 methods: {
	...mapActions(["Login", "Logout"]),
	
	······
	
	handleSubmit() {
        let that = this
        let loginParams = {
          remember_me: true
        };
        // 使用帳戶密碼登錄
        that.form.validateFields(['username', 'password', 'inputCode'], {force: true}, (err, values) => {
           if (!err) {
             loginParams.username = values.username
             loginParams.password = md5(values.password)
             loginParams.password = values.password
             //此處的Login方法即調用的user.js中的異步actions的Login()
             that.Login(loginParams).then((res) => {
               that.loginSuccess()
             }).catch((err) => {
               console.log('err',err)
               that.requestFailed(err);
             })
           }
         })
      },
 }
 
複製代碼

在用戶登陸這塊,不太同樣的處理是,它沒有在咱們的登陸頁面methods中寫登陸邏輯,而是將登陸接口和登錄後的操做統一寫在了actions方法中,登陸頁面表單驗證經過以後,直接調用vuex的action去調用登陸接口,在登陸接口中作user的state的相關處理。

這樣,整個框架裏對vuex的各個部分應用和結構基本清晰了,若是還有串聯不太起來的地方建議結合個人封面圖和文件路徑截圖查看一下,若是對vuex有過必定應用,可是沒有進行模塊化的開發者,我以爲你看完會對以前本身的一些思考有一些靈光。

固然在最後仍是要說一下本篇文章不太適用於:

  • 沒有使用過vuex,不知道state ,mutation ,getters,action 等等怎麼用的開發者(建議能夠結合官方文檔去看看別人的解讀,我就是這麼入門的)
  • 對在項目中已經總結出本身一套vuex策略的大佬,也不要笑話我啦:satisfied:,能夠提些建議給我更多啓發

另外本人寫文章通常都是分享本身學習的探索路程,可能對更多剛入門前端或者正從入門到進階前端開發者更有用些,歡迎點贊鼓勵和評論指正:v::v:

若是反饋不錯,下次分享一篇本身使用Egg.js+Mysql+Vue本身博客的全路程,由於本身第一次用node框架寫完整的項目,MySql也是第一次用,因此到時候會附上不少第一次用Egg和MySql遇到的問題,服務器部署問題和新手直接上手的代碼.

相關文章
相關標籤/搜索