【愣錘筆記】基於vue的進階散點乾貨

vue的開發如日中天,愈來愈多的開發者投入了vue開發大軍中。但願本文中的一些vue散點,能在實際項目中靈活運用,切實地爲咱們解決一些難點問題。前端

插槽

// 組件調用時使用插槽
<todo-list-item v-for="(item, index) in list" :key="index">
    <template v-slot:theNameOfSlot="theSlotProps">
      <span>{{item}}-{{theSlotProps.checked}}</span>
    </template>
</todo-list-item>

// todo-list-item組件的定義
<li>
    <input type="checkbox" v-model="checked">
    
    // name定義插槽名
    // :checked="checked"經過bind屬性的方法,使得父組件在使用插槽時能夠讀取到如今分發出去的數,例如這樣父組件就能夠讀取到這個分發出的checked值
    <slot name="theNameOfSlot" :checked="checked"></slot>
</li>
複製代碼

注意點:vue

  • 父組件的做用域是父組件的,若是想獲取自組件的插槽數據,則須要自組件的插件將數據分發出去
  • 2.6版本以後綁定插槽名稱,只能在template模板上,v-slot:插槽名,v-slot:theNameOfSlot="theSlotProps"屬性值爲slot分發給父組件的數據

依賴注入

// 在一個組件上設置注入的屬性,能夠是對象,也能夠是函數返回一個對象
provide: {
    parentProvide: {
      msg: 'hello world'
    }
},

// 在其任意層級的子節點能夠獲取到父節點注入的屬性
inject: [
    'parentProvide'
]
複製代碼

依賴注入的屬性是沒法修改的,若是須要在祖孫組件中監聽注入的屬性變化,須要在祖宗組件中的注入屬性爲this, 即把祖宗屬性做爲注入屬性往下傳遞。node

// 注意這裏注入時使用的是函數返回的對象
provide () {
    return {
      parentProvide: this
    }
},

// 接收注入的屬性並能夠直接修改,修改後祖宗的這個屬性值也會變化
inject: [
    'parentProvide'
  ],
methods: {
    updataParentMsg () {
      this.parentProvide.msg = '重置了'
    }
},
複製代碼

依賴注入很好的解決了在跨層級組件直接的通訊問題,在封裝高級組件的時候會很經常使用。webpack

實現簡易的vuex

// 封裝
import Vue from 'vue'
const Store = function (options = {}) {
  const {state = {}, mutations = {}} = options
  this._vm = new Vue({
    data: {
      $$state: state
    }
  })
  this._mutations = mutations
}

Store.prototype.commit = function (type, payload) {
  if (this._mutations[type]) {
    this._mutations[type](this.state, payload)
  }
}

Object.defineProperties(Store.prototype, {
  state: {
    get () {
      return this._vm._data.$$state
    }
  }
})

export default { Store }


// main.js,使用
// 首先導入咱們封裝的vuex
import Vuex from './min-store/index'

// 簡易掛載
Vue.prototype.$store = new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
    getterCount: state => state.count
  },
  mutations: {
    updateCount (state) {
      state.count ++
    }
  }
})

// 頁面使用
computed: {
    count () {
      return this.$store.state.count
    }
  },
  methods: {
    addCount () {
      this.$store.commit('updateCount')
    }
},
複製代碼

vuex-getter的注意點

// getter第一個參數是state,第二個參數是其餘getters,模塊中的getter第三個參數是根狀態
const getters = {
  count: state => state.count,
  
  // 例如能夠返回getters.count * 2
  otherCount: (state, getters) => getters.count * 2,
  
  // 跟狀態
  otherCount: (state, getters, rootState) => rootState.someState,
}


// 輔助函數
import { mapGetters } from 'vuex'

computed: {
    ...mapGetters([
        'count',
        'otherCount'
    ])
},

// 模塊加命名空間以後,car是當前getter所在的文件名
// 若是父級也有命名空間,則須要加上父級的命名空間,例如`parentName/car`:
computed: {
    ...mapGetters('car', [
        'count',
        'otherCount'
    ])
},

// 若是mapGetters中的值來自於多個模塊,能夠用對象的形式分別定義:
...mapGetters({
    'count': 'car/count',
    'otherCount': 'car/otherCount',
    'userName': 'account/userName'
})

// 也能夠寫多個mapGetters
computed: {
    ...mapGetters('account', {
          'userName': 'userName'
    }),
    ...mapGetters('car', [
      'count',
      'otherCount'
    ])
}
複製代碼

mapGetter的參數用數組的形式,書寫更簡潔方便,可是在須要從新命名getters等狀況下則沒法實現,此時能夠換成對象的書寫方式。git

vuex-mutations注意點

推薦使用常量替代 mutation 事件類型:在store文件夾中新建mutation-types.js文件,將全部的mutation事件類型以常量的形式定義好。es6

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// 引用時,經過es6的計算屬性命名的方式引入事件類型
import * as types from '../mutation-types'

const mutations = {
  [types.UPDATE_USERINFO] (state, userInfo) {
    state.count = userInfo
  }
}

// 使用mapMutations
import { mapMutations } from 'vuex'
// 常量+命名空間的mutation貌似無法經過像mapGetters的同樣用法,只能經過this.$store.commit的方式提交。
this[`account/${types.UPDATE_USERINFO}`]('xiaoming')
// 因此我的以爲這種狀況,仍是用action去觸發mutation
複製代碼

常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然github

vetur

生成簡易vue模板的快捷鍵:scaffoldweb

靈活的路由配置

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/user',
      title: '我的中心',
      component: { render: h => h('router-view') },
      children: [
        {
          path: '/user/index',
          name: 'user-dashboard',
          title: '我的中心',
          meta: {
            // 其餘meta信息
          },
          component: () =>
            import(/* webpackChunkName: user */ '@/views/dashboard/index')
        },
        {
          path: '/user/car',
          name: 'user-car',
          title: '個人汽車',
          meta: {
            // 其餘meta信息
          },
          component: () =>
            import(/* webpackChunkName: user */ '@/views/car/index')
        }
      ]
    }
  ]
})
複製代碼
  • () => import() 自動代碼分割的異步組件
  • () => import(/* webpackChunkName: user */ '@/views/dashboard/index') 將文件打包到一個模塊,例如這裏的寫法能夠將dashboard的index打包到user模塊中
  • render: h => h('router-view') 能夠用render函數靈活的建立user模塊的入口文件,省去了入口文件的編寫。

spa中的頁面刷新

spa應用的刷新咱們不能採起reload的方式。因此須要另闢蹊徑。vue-router

方案一:當路由當query部分變化時,配router-view的key屬性,路由是會從新刷新的。vuex

<router-view :key="$route.path">

this.$router.replace({
    path: this.$route.fullPath,
    query: {
        timestamp: Date.now()
    }
})
複製代碼

此種方法的弊端是url平白無故多了一個參數,不是很好看。

方案二:新建一個redirect空頁面,刷新就從當前頁面跳轉到redirect頁面,而後在redirect頁面理解replace跳轉回來。

// redirect頁面
<script>
export default {
  name: 'redirect',
  // 在路由進入redirect前當即返回原頁面,從而達到刷新原頁面的一個效果
  beforeRouteEnter (to, from, next) {
    next(vm => {
      vm.$router.replace({
        path: to.params.redirect
      })
    })
  },
  // 渲染一個空內容
  render(h) {
    return h()
  }
}
</script>

// 跳轉方法,咱們能夠封裝在utils公共函數中
// 在utils/index.js文件中:

import router from '../router'

/**
 * 刷新當前路由
 */
export const refreshCurrentRoute = () => {
  const { fullPath } = router.currentRoute
  router.replace({
    name: 'redirect',
    params: {
      redirect: fullPath
    }
  })
}
複製代碼

自定義指令實現最細粒度的權限控制(組件/元素級別的)

爲了擴展,咱們在src下新建directives指令文件夾,而後新建index.js、account.js

src/directives/index.js:

import account from './account'

const directives = [
  account
]

const install = Vue => directives.forEach(e => e(Vue))

export default { install }
複製代碼

src/directives/account.js

import { checkAuthorization } from '../utils/index'
/**
 * 導出和權限相關的自定義指令
 */
export default Vue => {
  /**
   * 自定義權限指令
   * @description 當用戶有權限才顯示當前元素
   */
  Vue.directive('auth', {
    inserted (el, binding) {
      if (!checkAuthorization(binding.value)) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  })
}
複製代碼

最後簡單附上工具函數,具體的根據項目實際狀況而定:

/**
 * 檢測用戶是否擁有當前的權限
 * @param { Array } auth 待檢查的權限
 */
export const checkAuthorization = (auth = []) => {
  if (!Array.isArray(auth)) {
    throw TypeError('請檢查參數類型:Excepted Array,got ' + Object.prototype.toString.call(auth).slice(8, -1))
  }
  return store.state.account.authorization.some(e => auth.includes(e))
}
複製代碼

自定義權限組件實現最細粒度的權限控制(組件/元素級別的)

// 自定義權限組件
<script>
import { check } from "../utils/auth";
export default {
  // 因爲該組件筆記簡單,不須要狀態/週期等,能夠將其設置爲函數式組件
  // 即無狀態/無響應式數據/無實例/無this
  functional: true,
  // 接收的用於權限驗證的參數
  props: {
    authority: {
      type: Array,
      required: true
    }
  },
  // 因爲函數式組件缺乏實例,故引入第二個參數做爲上下文
  render(h, context) {
    const { props, scopedSlots } = context;
    return check(props.authority) ? scopedSlots.default() : null;
  }
};
</script>

// main.js中註冊爲全局組件
import globalAuth from './components/authorization'

Vue.component('global-auth', globalAuth)

// 使用
<global-auth :authorization="['user']">asdasdas</global-auth>
複製代碼

監聽某個元素的尺寸變化,例如div

// 安裝resize-detector
cnpm i --save resize-detector

// 引入
import { addListener, removeListener } from 'resize-detector'

// 使用
addListener(this.$refs['dashboard'], this.resize) // 監聽
removeListener(this.$refs['dashboard'], this.resize) // 取消監聽

// 通常咱們會對回調函數進行去抖
methods: {
    // 這裏用了lodash的debounce
    resize: _.debounce(function (e) {
      console.log('123')
    }, 200)
}
複製代碼

resize-detector傳送門

提高編譯速度

能夠經過在本地環境使用同步加載組件,生產環境異步加載組件

// 安裝插件
cnpm i babel-plugin-dynamic-import-node --save-dev

// .bablerc文件的dev增長配置
"env": {
    // 新增插件配置
    "development": {
      "plugins": ["dynamic-import-node"]
    }
    // 其餘的內容
    ……
}

// 而後路由文件的引入依舊可使用以前的異步加載的方式
component: () => import('xxx/xxx')
// 經過註釋可使多個模塊打包到一塊兒
component: () => import(/* user */ 'xxx/xxx')
複製代碼

該方式修改本地環境和生產環境的文件加載方式,對代碼的侵入性最小,在不須要的時候直接刪去.bablerc文件中的配置就好,而不須要修改文件代碼 github傳送門

參考

1.《Vue開發實戰》視頻,做者:唐金州 地址: 傳送門
2. D2admin基於vue的中後臺框架, 傳送門

END

百尺竿頭、日進一步 我是愣錘,一名前端愛好者 歡迎交流、批評

相關文章
相關標籤/搜索