Vue.js 你不知道的一些小技巧

面試官:MVVM 和 MVC 的區別是什麼?javascript

本身先想一分鐘。css

vue.js

關於上面的面試題的具體解釋,請移步這裏,本文不在累述。正文開始,下面列舉的一些小技巧有的或許你用過,有的或許你沒用過。無論有的沒的,但願你看完以後有所收穫吧。文筆和知識有限,不對的地方,請留言斧正!html

給 props 屬性設置多個類型

這個技巧在開發組件的時候用的較多,爲了更大的容錯性考慮,同時代碼也更加人性化:vue

export default {
  props: {
    width: {
      type: [String, Number],
      default: '100px'
    }
    // 或者這樣
    // width: [String, Number]
  }
}
複製代碼

好比一個 <my-button> 上暴露了一個 width 屬性,咱們既能夠傳 100px,也能夠傳 100java

<!-- my-button.vue -->
<template>
  <button :style="computedWidth">{{ computedWidth }}</button>
</template>

<script> export default { props: { width: [String, Number] }, computed: { computedWidth () { let o = {} if (typeof this.width === 'string') o.width = this.width if (typeof this.width === 'number') o.width = this.width + 'px' return o } } } </script>
複製代碼

使用:node

<!-- 在其餘組件中使用 -->
<template>
  <my-button width="100px"></my-button>
  <!-- or -->
  <my-button :width="100"></my-button>
</template>
複製代碼

禁止瀏覽器 Auto complete 行爲

有時候咱們輸入帳號密碼登陸後臺系統,瀏覽器會彈出是否保存你的登陸信息。咱們通常會點擊保存,由於下次再次登陸的時候會自動填充增長了咱們的用戶體驗,很好。git

但有時,當咱們開發某個某塊(好比新增用戶)時,點擊新增用戶按鈕,顯示彈框,不巧的是,在帳號,密碼輸入框中瀏覽器幫咱們填充上了。但這並非咱們想要的。因此,個人有效解決思路以下:github

  • 設置 <el-input/> 爲只讀模式
  • focus 事件中去掉只讀模式

代碼以下:面試

<el-input type="text" v-model="addData.loginName" readonly @focus="handleFocusEvent" />
複製代碼
...
methods: {
  handleFocusEvent(event) {
    event.target && event.target.removeAttribute('readonly')
  }
}
複製代碼

然而,ElementUI自帶的 auto-complete="off" 貌似並不生效。ajax

阻止 <el-form> 默認提交行爲

有時候咱們在用餓了麼組件 <el-form> 在文本框中鍵入 enter 快捷鍵的時候會默認觸發頁面刷新。咱們能夠加入以下代碼解決其默認行爲:

<el-form @submit.native.prevent>
  ...
</el-form>
複製代碼

使用 <el-scrollbar> 組件

Element 官方是沒有明確提供這個組件的,可是其源碼 中確實存在。關於如何使用我寫了一個Demo,你能夠狠狠的戳這裏查看示例,本文再也不贅述。

根據業務合併 el-table 中的行和列

最近在作項目的時候遇到一個需求:同一個帳號ID下有多個被分配的角色時並列顯示角色信息。因而就想到了 el-table 提供的合併方法 :span-method。但它對後臺數據格式是有要求的:

  • 若是後臺返回的數據是數組裏面嵌套數組的話,你就須要把裏面的數組按順序也拿到外面了
// 假如後臺返回的數據是下面這樣的
{
  data: [
    { id: 1, appkey: 1, name: 'a', list: [{ id: 11, appkey: 1, name: 'a-1'}, {id: 12, appkey: 1, name: 'a-2'}] }
    { id: 2, appkey: 2, name: 'b', list: [{ id: 21, appkey: 2, name: 'b-1'}, {id: 22, appkey: 2, name: 'b-2'}] }
  ]
}

// 整理事後的格式應該是這樣的
{
  data: [
    { id: 1, appkey: 1, name: 'a' },
    { id: 11, appkey: 1, name: 'a-1' },
    { id: 12, appkey: 1, name: 'a-2' },
    { id: 2, appkey: 2, name: 'b' },
    { id: 21, appkey: 2, name: 'b-1' },
    { id: 22, appkey: 2, name: 'b-2' }
  ]
}
複製代碼

下面是具體的處理流程:

<template>
  <el-table :data="formattedList" :span-method="handleColspanMethod" >
  ...
  </el-table>
</template>
<script> import Api from '@/api/assign' export default { data() { return { list: [], // 後臺返回的數據 formattedList:[], // 格式化後的數據 spanArr: [], // 保存要合併的行列數據 } }, created() { this.getList() }, methods: { getList() { Api.fetchList().then(response => { this.list = response.data.data // 格式化數據 this.formattedList = this.formatArray(this.list) // 獲取合併位置 this.getSpanArr() }) }, /** * 格式化數組 * {Array} sources 源數組 * {Array} arrayed 格式化後的數組 * return 返回格式化後的數組 */ formatArray: function f(sources, arrayed) { if (!sources) return [] const arr = arrayed || [] const len = sources.length for (let i = 0; i < len; i++) { const childs = sources[i].list || [] arr.push(sources[i]) if (childs.length > 0) { f(sources[i].list, arr) } } return arr }, /** * 獲取合併位置信息 */ getSpanArr() { // 重置 spanArr,由於翻頁的時候數據就變了 // 以前的數據若是不清空,其餘頁也會受影響 this.spanArr = [] const data = this.formattedList if (!data || data.length <= 0) return // 遍歷 for (let i = 0; i < data.length; i++) { if (i === 0) { this.spanArr.push(1) // 其實就是行索引 this.pos = 0 } else { // 若是當前對象和上一個對象的 appkey 相等 // 說明須要合併 if (data[i].appkey === data[i - 1].appkey) { this.spanArr[this.pos] += 1 this.spanArr.push(0) } else { this.spanArr.push(1) this.pos = i } } } }, /** * 處理跨行列合併 */ handleColspanMethod({ row, column, rowIndex, columnIndex}) { if (columnIndex < 2) { const _spa = this.spanArr[rowIndex] const _row = _spa ? _spa : 0 const _col = _row > 0 ? 1 : 0 return { rowspan: _row, colspan: _col } } } } } </script>
複製代碼

單文件樣式提取

若是 a.vueb.vue 都用到了下面的樣式:

.btn {
  color: silver
}
// 省略很長的樣式
...
複製代碼

能夠考慮把樣式提取到 styleName.scss/css 中,例如:

./components/common.scss

.btn {
  color: silver
}
// 省略很長的樣式
複製代碼

而後再在兩文件中引入,即:

<template>...</template>
<script>...</script>
<style lang="scss" src="./components/common.scss" scoped/> 複製代碼

是否是省了不少代碼呢?快去試試吧!

解決 vue-template-admin 單文件中背景圖片生產打包後路徑404問題

  • 找到 build/utils.js
  • 而後找到 generateLoaders 方法
  • if(options.extract){...} 中修改,即:
if (options.extract) {
  // 解決其打包背景圖片路徑問題
  loaders.push({
    loader: MiniCssExtractPlugin.loader,
    options: {
      publicPath: '../../'
    }
  })
} else {
  ...
}
複製代碼

記住,千萬別再你的 css 中這樣寫:

background: url("/new/static/images/assets/loginbackground.png");
複製代碼

由於 new 文件夾是後臺同窗幫你配置的,隨時都有可能被修改,一個地方還好,多個地方改起來就蛋疼了。

data 初始化

由於 props 要比 data 先完成初始化,因此咱們能夠利用這一點給 data 初始化一些數據進去,看代碼:

export default {
  data () {
    return {
      buttonSize: this.size
    }
  },
 props: {
   size: String
 }
}
複製代碼

除了以上,子組件的 data 函數也能夠有參數,且該參數是當前實例對象。全部咱們能夠利用這一點作一些本身的判斷。如,改寫上面的代碼:

export default {
  data (vm) {
    return {
      buttonSize: vm.size
    }
  },
 props: {
   size: String
 }
}
複製代碼

template

咱們在作 v-if 判斷的時候,能夠把判斷條件放在 template 組件上,最終的渲染結果將不包含 <template> 元素。

<template>
  <div class="box">
    <template v-if="isVal">
      <h2>...</h2>
    </template>
    <template v-else>
      <h2>...</h2>
    </template>
  </div>
</template>
複製代碼

v-for 也一樣適用。

Lifecycle hook

生命週期鉤子能夠是一個數組類型,且數組中的函數會依次執行。

export default {
 ...
 created: [
   function one () {
     console.log(1)
   },
   function two () {
     console.log(2)
   }
 ]
 ...
}
複製代碼

沒什麼用,知道就好了。事實上生命週期鉤子還能夠做用於 DOM 元素上,利用這一點,咱們能夠用父組件中的方法來初始化子組件的生命週期鉤子:

<!-- Child.vue -->
<template>
  <h3>I'm child!</h3>
</template>

<!-- Parent.vue -->
<template>
 <child @hook:created="handleChildCreated"></child>
</template>

<script> import Child from './child.vue' export default { components: [ Child ], methods: { handleChildCreated () { console.log('handle child created...') } } } </script>
複製代碼

其餘鉤子雷同,再也不贅述。

v-for

在用 v-for 遍歷數組的時候,咱們通常都會錯誤的這樣去作,舉個栗子:

v-for 和 v-if 放在同一個元素上使用:

<template>
  <ul class="items">
    <!-- 只有激活的用戶才能夠顯示 -->
    <li v-for="(user, index) in users" v-if="user.isActive" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>
複製代碼

因爲 v-forv-if 放在同一個元素上使用會帶來一些性能上的影響,官方給出的建議是在計算屬性上過濾以後再進行遍歷。因此平時開發不推薦一塊兒使用,知道有這回事便可,不至於面試時不知道。 關於爲何不推薦放在一塊兒使用,參見 避免-v-if-和-v-for-用在一塊兒

混合

若是好多組件都共用到一些像 propsdatamethods 等,能夠單獨抽出來放到 mixins 混合器中。好比,在用戶管理列表中使用。

分頁混合器:

// paging-mixin.vue
export default {
  props: {
    pageSize: 1,
    pageLength: 10,
    currentPage: 1
    total: 20
  },
  methods: {
    /** * 上一頁 */
    prevPage (page) {
      ...
    },
    /** * 下一頁 */
    nextPage (page) {
      ...
    }
    /** * 跳轉到當前頁 */
    currentPage (page) {
      ...
    }
  }
}
複製代碼

Users.vue:

<template>
  <div class="user-model">
    <my-table :data="users"></my-table>
    <my-paging :page-length="pageLength" :page-size="pageSize" :current-page="currentPage" :total="total">
    </my-paging>
  </div>
</template>

<script> import PagingMixin from '../mixins/paging-mixin.vue' export default { mixins: [PagingMixin], data () { return { users: [], pageLength: 10, pageSize: 1, currentPage: 1, total: 20 } } } </script>
複製代碼

不用每一個頁面都寫一遍 propsmethods 了。

render 函數

下面是一段簡單的 template 模板代碼:

<template>
  <div class="box">
    <h2>title</h2>
    this is content
  </div>
</template>
複製代碼

咱們用渲染函數來重寫上面的代碼:

export default {
  render (h) {
    let _c = h
    return _c('div', 
      { class: 'box'}, 
      [_c('h2', {}, 'title'), 'this is content'])
  }
}
複製代碼

事實上,Vue 會把模板(template)編譯成渲染函數(render),你能夠經過一個在線工具 實時查看編譯結果。上面的 template 模板會被編譯成以下渲染函數:

let render = function () {
  return _c('div',
    {staticClass:"box"},
    [_c('h2', [_v("title")]), _v("this is content")])
}
複製代碼

是否是很像? 正如官方說的,渲染函數比 template 更接近編譯器。若是用一個流程圖來解釋的話,大概是這個樣子:

template
    ↓
預編譯工具(vue-loader + vue-template-compile)
    ↓
  render
    ↓
resolve vnode
複製代碼

具體參見 Vue聲明週期圖示

渲染函數用處:

  • 開發組件庫,Element 源碼用的都是 render
  • 封裝一些高階組件。組件裏面嵌套組件就是高階組件,前提是要知足組件三要素:propseventslot
  • 用於處理一些複雜的邏輯判斷。若是咱們一個組件裏面有不少 v-if 判斷的話,用模板就顯得不合適了,這個時候能夠用渲染函數來輕鬆處理

errorCaptured

捕獲一個來自子孫組件的錯誤時被調用。有時候當咱們想收集錯誤日誌,卻不想把錯誤暴露到瀏覽器控制檯的時候,這頗有用。下面是個例子:

Child.vue

<template>
  <!-- 省略一些無關代碼 -->
</template>
<script> export default { mounted () { // 故意把 console 寫錯 consol.log('這裏會報錯!') } } </script>
複製代碼

Parent.vue

<template>
  <child></child>
</template>
<script> import Child from './Child.vue' export default { components: [ Child ], /** * 收到三個參數: * 錯誤對象、發生錯誤的組件實例 * 以及一個包含錯誤來源信息的字符串。 * 此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播。 */ errorCaptured (err, vm, info) { console.log(err) // -> ReferenceError: consle is not defined ... console.log(vm) // -> {_uid: 1, _isVue: true, $options: {…}, _renderProxy: o, _self: o,…} console.log(info) // -> `mounted hook` // 告訴咱們這個錯誤是在 vm 組件中的 mounted 鉤子中發生的 // 阻止該錯誤繼續向上傳播 return false } } </script>
複製代碼

關於 errorCaptured 更多說明,請移步官網->

v-once

經過 v-once 建立低開銷的靜態組件。渲染普通的 HTML 元素在 Vue 中是很是快速的,但有的時候你可能有一個組件,這個組件包含了大量靜態內容。在這種狀況下,你能夠在根元素上添加 v-once 特性以確保這些內容只計算一次而後緩存起來,就像這樣:

<template>
  <div class="box" v-once>
    <h2> 用戶協議 </h2>
    ... a lot of static content ...
  </div>
</template>
複製代碼

只渲染元素和組件一次。隨後的從新渲染,元素/組件及其全部的子節點將被視爲靜態內容並跳過。這能夠用於優化更新性能。關於 v-once 更多介紹,請移步官網->

slot-scope

做用域插槽。vue@2.5.0 版本之前叫 scope,以後的版本用 slot-scope 將其代替。除了 scope 只能夠用於 <template> 元素,其它和 slot-scope 都相同。

用過 Element 組件的同窗都知道,當咱們在使用 <el-table> 的時候會看到以下代碼:

Element@1.4.x 的版本:

<el-table-column label="操做">
  <template scope="scope">
  <el-button size="small" @click="handleEdit(scope.$index, scope.row)">編輯</el-button>
  <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">刪除</el-button>
  </template>
</el-table-column>
複製代碼

但在 2.0 以後的版本替換成了 slot-scope

Element@2.0.11:

<el-table-column label="操做">
  <template slot-scope="scope">
    <el-button size="mini" @click="handleEdit(scope.$index, scope.row)">編輯</el-button>
    <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">刪除</el-button>
  </template>
</el-table-column>
複製代碼

說白了,slot-scope 至關於函數的回調,我把結果給你,你想怎麼處理就怎麼處理,一切隨你:

function getUserById (url, data, callback) {
  $.ajax({
    url,
    data,
    success: function (result) {
      callback(result)
    }
  })
}

// 使用
getUserById('/users', { id: 1 }, function (response) {
  // 拿到數據並開始處理本身的頁面邏輯
})
複製代碼

下面咱們來簡單模擬下 <el-table> 組件內部是怎麼使用 slot-scope 的,看代碼:

模擬的 <el-table> 組件:

// 定義模板
let template = ` <ul class="table"> <li v-for="(item, index) in data" :key="index"> <!-- 我但願數據由調用者本身處理 --> <!-- 'row' 至關於變量名,隨便定義,好比 aaa,bbb 啥的 --> <slot :row="item"> <!-- 當使用者什麼都沒寫的時候,默認值纔會顯示--> {{ item.name }} </slot> </li> </ul> `
Vue.component('el-table', {
  template,
  props: {
    data: Array,
    default: []
  }
})
複製代碼

在你須要的地方使用 <el-table> 組件:

HTML:

<div id="app">
  <el-table :data="userData">
    <!-- 使用的時候能夠用 template -->
    <!-- `scope` 也是個變量名,隨便命名不是固定的,好比 foo, bar -->
    <template slot-scope="scope">
      <!-- 其中 `scope.row` 中的 row 就是咱們上邊定義的變量啦-->
      <!-- `scope.row`返回的是 `item` 對象 -->
      <template v-if="scope.row.isActived">
        <span class="red">{{ scope.row.name }}</span>
      </template>
      <template v-else>
        {{ scope.row.name }}
      </template>
    </template>
  </el-table>
</div>
複製代碼

JavaScript:

new Vue({
  el: '#app',
  data: {
    userData: [
      {id: 1, name: '張三', isActived: false},
      {id: 2, name: '李四', isActived: false},
      {id: 1, name: '王五', isActived: true},
      {id: 1, name: '趙六', isActived: false},
    ]
  }
})
複製代碼

CSS:

.red {
  color: red
}
複製代碼

你能夠狠狠的戳這裏查看上面的效果!最後,咱們再使用 render 函數來重構上面的代碼:

JavaScript:

// `<el-table>` 組件
Vue.component('el-table', {
  name: 'ElTable',
  render: function (h) {
    return h('div', { 
      class: 'el-table'
    }, this.$slots.default)
  },
  props: {
    data: Array
  }
})

// `<el-table-column>`
Vue.component('el-table-column', {
  name: 'ElTableColumn',
  render: function (h) {
    // 定義一個存放 li 元素的數組
    let lis = [], 
       // 獲取父組件中的 data 數組
       data = this.$parent.data
    // 遍歷數組,也就是上面的 `v-for`,生成 `<li>` 標籤
    // `this.$scopedSlots.default` 獲取的就是上面 slot-scope 做用於插槽的部分,
    // 並把 `{ row: item }` 傳給上面的 `scope` 變量
    data.forEach((item, index) => {
      let liEl = h('li', {
        key: item.id
      }, [ this.$scopedSlots.default({ row: item }) ])
      // 把生成的 li 標籤存到數組
      lis.push(liEl)
    })
    return h('ul', {
      class: 'el-table-column'
    }, lis)
  }
})
複製代碼

在你的頁面這樣來使用:

HTMl:

<div id="app">
  <el-table :data="list">
    <el-table-column>
      <template slot-scope="scope">
        <span class="red" v-if="scope.row.actived">{{ scope.row.name }}</span>
        <span v-else>{{ scope.row.name }}</span>
      </template>
    </el-table-column>
  </el-table>
</div>
複製代碼

JavaScript:

new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: '張三', actived: false },
      { id: 1, name: '李四', actived: false },
      { id: 1, name: '王五', actived: true },
      { id: 1, name: '趙六', actived: false },
    ]
  }
})
複製代碼

你能夠狠狠的戳這裏查看上面的效果!

疑問:咱們徹底能夠在 <li> 中進行邏輯判斷,爲何還要放到外面進行處理呢? 由於有時候咱們用的不是本身開發的組件,好比上面的 <el-table> ,因此就有必要這麼作了。

結尾!

囉嗦了這麼多,但願看到的同窗或多或少有點收穫吧。不對的地方還請留言指正,不勝感激。俗話說,三人行則必有我師! 但願更多熱衷於 Vue 的小夥伴能聚在一塊兒交流技術!下面是我維護的一個Q羣,歡迎掃碼進羣哦,讓咱們一塊兒交流學習吧。也能夠加我我的微信:G911214255 ,備註 掘金 便可。

Q1769617251
相關文章
相關標籤/搜索