Vue.js 實用技巧(二)
觀眾老爺們好,如果你看過上一篇文章就會知道,我們這個系列是介紹 Vue.js 實踐中的一些技巧,合理的運用還是能提升一些開發效率的。今天介紹 jsx 的特殊用法和 Vue.js 的新功能 —— $props。
技巧一:任何地方都能寫 jsx
寫過 Vue jsx 的都知道,通常我們需要將 jsx 寫在 render(h) {} 中。但是有些情況下我們想在其他方法里也能寫 jsx,例如上篇文章的一個 Element 組件的例子。
const h = this.$createElementthis.$notify({ title: "GitHub", message: h("div", [ h("p", "[GitHub] Subscribed to ElemeFE/element notifications"), h("el-button", {}, "已讀") ])})
調用 notification 服務,顯示一段自定義的內容。這段代碼並沒有寫在 render 里,但是其實我們也能寫成 jsx,例如:
{ methods: { showNotify() { const h = this.$createElement this.$notify({ title: "GitHub", message: ( <div> <p>[GitHub] Subscribed to ElemeFE/element notification</p> <el-button>已讀<el-button> <div>) }) } }}
使用 babel-plugin-transform-vue-jsx 插件,這段代碼可以正常運行。原理其實很簡單,vue-jsx 插件文檔里提到 render(h) 中的 h 其實就是 this.$createElement,那麼我們只需要在使用 jsx 的方法中聲明一下 h 就完成了。如果有用到 eslint,可以加上 // eslint-disable-line 忽略提示:
const h = this.$createElement // eslint-disable-line
實際上在最新發布的 babel-plugin-transform-vue-jsx 3.4.0 里已經不在需要手動聲明 h 變數,現在就可以愉快的寫 jsx 在組件里的任何地方。
技巧二:$props 的使用姿勢
Vue.js 2.2 中加入了一個新特性 —— $props。文檔只是很簡潔的介紹了是什麼但並沒有解釋有什麼用,那下面我給大家分享下哪些情況會需要這個屬性。
1. 繼承原生屬性
當開發表單組件時,不得不解決的問題是繼承原生組件的各種屬性。例如封裝一個 input 組件,要有原生的 placeholder 屬性,那麼我們的代碼可能是這樣:
<template> <div> <label>{{ label }}</label> <input @input="$emit("input", $event.target.value)" :value="value" :placeholder="placeholder"> </div></template><script> export default { props: ["value", "placeholder", "label"] }</script>
但是如果需要支持其他原生屬性就需要繼續寫模板內容:
<template> <div> <label>{{ label }}</label> <input @input="$emit("input", $event.target.value)" :value="value" :placeholder="placeholder" :maxlength="maxlength" :minlength="minlength" :name="name" :form="form" :value="value" :disabled="disabled" :readonly="readonly" :autofocus="autofocus"> </div></template><script> export default { props: ["label", "placeholder", "maxlength", "minlength", "name", "form", "value", "disabled", "readonly", "autofocus"] }</script>
如果還要設置 type,或者是要同時支持 textarea,那麼重複的代碼量還是很可怕的。但是換個思路,直接用 jsx 寫的話或許會輕鬆一些:
export default { props: ["label", "type", "placeholder", "maxlength", "minlength", "name", "form", "value", "disabled", "readonly", "autofocus"], render(h) { const attrs = { placeholder: this.placeholder, type: this.type // ... } return ( <div> <label>{ this.label }</label> <input { ...{ attrs } } /> </div> ) }}
在 Vue 的 vnode 中,原生屬性是定義在 data.attrs 中,所以上面 input 部分會被編譯成:
h("input", { attrs: attrs})
這樣就完成了原生屬性的傳遞,同理如果需要通過 type 設置 textarea,只需要加個判斷設置 tag 就好了。
h(this.type === "textarea" ? "textarea" : "input", { attrs })
目前為止我們還是需要定義一個 attrs 對象,但是所需要的屬性其實都已經定義在了 props 中,那麼能直接從 props 里拿到值豈不是更好?我們可以簡單的寫一個 polyfill 完成這件事。(實際上 Vue 2.2 中不需要你引入 polyfill,默認已經支持)
import Vue from "vue"Object.defineProperty(Vue.prototype, "$props", { get () { var result = {} for (var key in this.$options.props) { result[key] = this[key] } return result }})
原理很簡單,從 vm.$options.props 遍歷 key 後從 vm 中取值,現在我們就可以直接從 vm.$props 拿到所有 props 的值了。那麼我們的代碼就可以改成這樣:
render(h) { return ( <div> <label>{ this.label }</label> <input { ...{ attrs: this.$props } } /> </div> )}
如果你留意過 Vue 文檔介紹 v-bind 是可以傳對象 的話,那我們的代碼用 Vue 模板寫的話就更簡單了:
<template> <div> <label>{{ label }}</label> <input v-bind="$props"> </div></template>
2. 繼承自定義組件的屬性
$props 的功能遠不止於此。如果你需要基於上面的 input 組件封裝成另一個組件時,那麼我們要如何繼承它的屬性?
例如封裝一個帶校驗功能的 input 組件,代碼可能是這樣:
<template> <div> <XInput /> <div v-show="message && show-hit" class="hit">{{ message }}</div> </div></template><script> import XInput from "./input.vue" export default { components: { XInput }, props: { showHit: Boolean }, data () { return { message: "錯誤提示" } } }</script>
關鍵就是如何傳 XInput 的 props。其實只需要在當前組件的 props 中把 Xinput 的 props 複製一遍後,用 v-bind 就完成了。
<template> <div> <XInput v-bind="$props" /> <div v-show="message && show-hit" class="hit">{{ message }}</div> </div></template><script> import XInput from "./input.vue" export default { components: { XInput }, props: { showHit: Boolean, ...XInput.props }, data () { return { message: "錯誤提示" } } }</script>
或者用 Object.assign 也可以實現:
{ props: Object.assign({ showHit: Boolean }, XInput.props)}
以上就是 $props 的基本用法,如果你有其他看法或用法歡迎留言分享。好了這個系列的分享告一段落,所有例子的代碼我都放在了 vue-tricks 倉庫里。下次再見!
參考鏈接
vuejs/babel-plugin-transform-vue-jsx
Vue.js
$props · Issue #4571 · vuejs/vue
vuejs/babel-plugin-transform-vue-jsx
QingWei-Li/vue-tricks
推薦閱讀:
※極樂技術周報(第二十八期)
※Web Components 在 GitHub 中的應用
※天啦嚕!原來Chrome自帶的開發者工具還能這麼用!