天天看點

Vue.js 的一些小技巧

給 props 屬性設定多個類型

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

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

複制

比如一個

<my-button>

上暴露了一個

width

屬性,我們既可以傳

100px

,也可以傳

100

<!-- 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>           

複制

使用:

<!-- 在其他元件中使用 -->
<template>
  <my-button :width="100px"></my-button>
  <!-- or -->
  <my-button :width="100"></my-button>
</template>           

複制

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-if 一起使用

由于

v-for

v-if

渲染優先級更高,是以有時候可以一起使用。下面兩種常見的情況下會傾向于把

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>           

複制

關于以上兩點不明白的地方可以參見 Vue 風格指南。

混合

如果好多元件都共用到一些像

props

data

methods

等,可以單獨抽出來放到

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>           

複制

不用每個頁面都寫一遍

props

methods

了。

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
  • 封裝一些高階元件。元件裡面嵌套元件就是高階元件,前提是要滿足元件三要素:

    props

    event

    slot

  • 用于處理一些複雜的邏輯判斷。如果我們一個元件裡面有很多

    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

作用域插槽。

[email protected]

版本以前叫

scope

,之後的版本用

slot-scope

将其代替。除了 scope 隻可以用于

<template>

元素,其它和

slot-scope

都相同。

用過 Element 元件的同學都知道,當我們在使用

<el-table>

的時候會看到如下代碼:

[email protected] 的版本:

<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

[email protected]

<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>

,是以就有必要這麼做了。

作者:gongph

連結:https://juejin.im/post/5be01d0ce51d450700084925

來源:掘金

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。