學會使用Vue JSX,一車老乾媽都是你的

君自前端來,應知前端事。 需求時時變,bug改不完。javascript

連續幾篇文章,每篇都有女神,被掘友給吐槽了,今天不提了女神了,反正女神都是別人的(扎心了)。這兩天小編看了騰訊與老乾媽的事情,晚上饅頭夾老乾媽吃起來都感受很帶勁。今天這篇文章將給你們小編在項目中使用JSX的一些實戰經驗。其實通常狀況下寫Vue仍是比較推薦template的寫法的,可是有時候咱們真的須要更靈活的去作一些功能,這時候就須要用到JSX了。php

介紹一下JSX

JSX 簡介

JSX是一種Javascript的語法擴展,JSX = Javascript + XML,即在Javascript裏面寫XML,由於JSX的這個特性,因此他即具有了Javascript的靈活性,同時又兼具html的語義化和直觀性。html

應用場景

爲了讓你們更方便的去理解JSX的做用及用法,小編先爲你們羅列了幾個可能會用到JSX的應用場景。前端

在消息框內添加html

在開發過程當中,常常會用到消息框,使用消息框可能的一種寫法是這樣的vue

Message.alert({
 messge: '肯定要刪除?',  type: 'warning' }) 複製代碼

可是有時候產品或UI但願message能夠自定義一些樣式,這時候你可能就須要讓Message.alert支持JSX了(固然也可使用插槽/html等方式解決)java

Message.alert({
 // 此處使用了JSX  messge: <div>肯定要刪除<span style="color:red">學習子君Vue系列文章</span>的筆記?</div>,  type: 'warning' }) 複製代碼

函數式組件

在小編前面的文章實戰技巧,Vue原來還能夠這樣寫中介紹了爲何要使用函數式組件,及函數式組件與普通組件的區別。雖然在Vue.2.5以後,函數式組件也可使用模板語法,但使用JSX可能會更方便一些(我的理解)c++

export default {
 // 經過配置functional屬性指定組件爲函數式組件  functional: true,  /**  * 渲染函數  * @param {*} h  * @param {*} context 函數式組件沒有this, props, slots等都在context上面掛着  */  render(h, context) {  const { props } = context  if (props.avatar) {  return <img src={props.avatar}></img>  }  return <img src="default-avatar.png"></img>  } } 複製代碼

一個表單的需求

爲了方便快速開發管理系統,小編對所使用的UI庫中的表單進行了二次封裝,封裝以後的效果以下(僅供參考):web

<template>
 <custom-form v-model="formData" :fields="fields" /> </template> <script> export default {  data() {  return {  formData: {},  fields: Object.freeze([  {  label: '字段1',  props: 'field1',  type: 'input'  },  {  label: '字段2',  props: 'field2',  type: 'number'  }  ])  }  } } </script>  複製代碼

這樣封裝以後,定義表單時,只須要定義簡單的JSON便可快速完成表單開發,但有時候會有一些特殊的需求,好比但願能夠給輸入框後面加一個按鈕或者圖標之類的,這時候就須要考慮使用JSX去處理了面試

{
 label: '字段2',  props: 'field2',  type: 'number',  // 會渲染到表單元素後面  renderSuffix() {  return <button onClick={this.$_handleSelect}>選擇</button>  } } 複製代碼

其餘一些場景

好比咱們一條數據須要根據狀態不一樣,定義不一樣的展示方式,這時候你可能會想到用策略模式,這時候若是將每個策略都寫成一個JSX,那麼就不須要針對每個策略定義一個單文件組件了。固然若是你說,我就喜歡用JSX,那麼全部的場景你均可以用。vue-cli

爲了您閱讀後面的內容時得到更好的閱讀體驗,建議您先點擊一下電腦左側或手機下方的大拇指圖標,讓頁面色彩變得更豐富。

學習JSX,先了解一下createElement

提到JSX,不可避免的就要提到createElement,當你看完本節,你會發現,奇怪的知識又增多了。

Vue編譯後的代碼看createElement

你是否看過寫的Vue代碼通過編譯以後的樣子,好比下面這段代碼

<template>
 <div>我是子君,個人公衆號是<span class="emphasize">前端有的玩</span></div> </template>  複製代碼

小編對這段代碼進行編譯以後,獲得下面這段代碼

function () {
 var e = this,  // e._self._c 對應源碼裏面的createElement  t = e._self._c;  // 返回了一個 createElement('div',[])  return t("div", [  // e._v 對應源碼裏面的createTextVNode  e._v("我是子君,個人公衆號是"),  t("span", { staticClass: "emphasize" }, [e._v("前端有的玩")]),  ]); } 複製代碼

經過對上面的代碼進行分析,不難發現,Vue模板中的每個元素編譯以後都會對應一個createElement,那麼這個createElement究竟是什麼,嗯,這個你面試的時候也許已經提到過了。

那麼什麼是createElement

不管是Vue仍是React,都存在createElement,並且做用基本一致。可能你對createElement不是很瞭解,函數名翻譯過來就是增長一個元素,但他的返回值你必定知道。createElement函數返回的值稱之爲虛擬節點,即VNode,而由VNode扎堆組成的樹即是大名鼎鼎,面試必問的虛擬DOM

createElement函數的參數,在這裏小編偷個懶抄一下Vue官方文檔

// @returns {VNode}
createElement(  // {String | Object | Function}  // 一個 HTML 標籤名、組件選項對象,或者  // resolve 了上述任何一種的一個 async 函數。必填項。  'div',   // {Object}  // 一個與模板中 attribute 對應的數據對象。可選。  {  // (詳情見下一節)  },   // {String | Array}  // 子級虛擬節點 (VNodes),由 `createElement()` 構建而成,  // 也可使用字符串來生成「文本虛擬節點」。可選。  [  '先寫一些文字',  createElement('h1', '一則頭條'),  createElement(MyComponent, {  props: {  someProp: 'foobar'  }  })  ] ) 複製代碼

從上面能夠看出createElement一共有三個參數,三個參數分別是

  • 第一個參數是須要渲染的組件,能夠是組件的標籤,好比div;或者是一個組件對象,也就是你每天寫的export default {};亦或者能夠是一個異步函數。

  • 第二個參數是這個組件的屬性,是一個對象,若是組件沒有參數,能夠傳null(關於組件的屬性,下文將依次介紹)

  • 第三個參數是這個組件的子組件,能夠是一個字符串(textContent)或者一個由VNodes組成的數組

createElement寫一個組件吧

表單示例

假設咱們須要開發一個下面這樣的表格(element-ui的)

用模板代碼去開發

若是咱們用模板代碼去開發這個表單,那麼代碼大概就長這樣

<el-form :inline="true" :model="formInline" class="demo-form-inline">
 <el-form-item label="審批人">  <el-input v-model="formInline.user" placeholder="審批人"></el-input>  </el-form-item>  <el-form-item label="活動區域">  <el-select v-model="formInline.region" placeholder="活動區域">  <el-option label="區域一" value="shanghai"></el-option>  <el-option label="區域二" value="beijing"></el-option>  </el-select>  </el-form-item>  <el-form-item>  <el-button type="primary" @click="onSubmit">查詢</el-button>  </el-form-item> </el-form> 複製代碼

createElement去實現

若是咱們直接將上面的代碼轉換爲用createElement去實現,那麼代碼將會是這樣的

export default {
 methods: {  $_handleChangeUser(value) {  this.formInline.user = value  }  },  render(createElement) {  return createElement(  'ElForm',  {  props: {  inline: true,  model: this.formInline  },  staticClass: 'demo-form-inline'  },  [  createElement(  'ElFormItem',  {  props: {  label: '審批人'  }  },  [  createElement('ElInput', {  props: {  value: this.formInline.user  },  attrs: {  placeholder: '審批人'  },  on: {  input: this.$_handleChangeUser  }  })  ]  ),  createElement(  'ElFormItem',  {  props: {  label: '活動區域'  }  },  [  createElement(  'ElSelect',  {  props: {  value: this.formInline.region,  placeholder: '活動區域'  }  },  [  createElement('ElOption', {  props: {  label: '區域一',  value: 'shanghai'  }  }),  createElement('ElOption', {  props: {  label: '區域二',  value: 'beijing'  }  })  ]  )  ]  ),  createElement('ElFormItem', null, [  createElement(  'ElButton',  {  props: {  type: 'primary'  },  on: {  click: this.$_handleSubmit  }  },  '查詢'  )  ])  ]  )  } } 複製代碼

看到上面的代碼,你可能會驚呼,代碼好多啊,好痛苦,想當年發明JSX的人剛開始每天也是寫createElement,寫的直掉頭髮,太痛苦了,而後就使勁撓頭,當額頭鋥光發亮的時候,終於想到了一種新的語法,就是JSX。今後以後,頭髮呼呼的又長回來了(本段純屬虛構)。

看到上面代碼,你會發現有一個render函數,這個函數叫作渲染函數,至關於經過createElementJSX去實現功能的主入口方法。並且你熟悉的v-model也沒見了,而是用value + input代替了。

是時候使用JSX代替createElement

看到上面用createElement去實現組件,太麻煩了,別說工做效率提升了,就是那些嵌套能夠嵌套正確就很讚了,因此咱們須要用JSX去簡化整個邏輯。

methods: {
 $_handleInputUser(value) {  this.formInline.user = value  },  $_handleChangeRegion(value) {  this.formInline.region = value  },  $_handleSubmit() {} },  /**  *將 h 做爲 createElement 的別名是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的。從 Vue 的 Babel 插件的 3.4.0 *版本開始,咱們會在以 ES2015 語法聲明的含有 JSX 的任何方法和 getter 中 (不是函數或箭頭函數中) 自動注入  *const h = this.$createElement,這樣你就能夠去掉 (h) 參數了。對於更早版本的插件,若是 h 在當前做用域中不可用,應用會拋錯。  */ render(h) {  return (  <ElForm inline model={this.formInline} class="demo-form-inline">  <ElFormItem label="審批人">  <ElInput  value={this.formInline.user}  onInput={this.$_handleInputUser}  placeholder="審批人"  ></ElInput>  </ElFormItem>  <ElFormItem label="活動區域">  <ElSelect  value={this.formInline.region}  onChange={this.$_handleChangeRegion}  placeholder="活動區域"  >  <ElOption label="區域一" value="shanghai"></ElOption>  <ElOption label="區域二" value="beijing"></ElOption>  </ElSelect>  </ElFormItem>  <ElFormItem>  <ElButton type="primarty" onClick={this.$_handleSubmit}>  查詢  </ElButton>  </ElFormItem>  </ElForm>  )  } 複製代碼

看了上面的代碼,你們其實會發現用JSXtemplate的語法都屬於xml的寫法,並且也比較像,但實質上仍是有許多區別的,下面小編將爲你們一一分析

沒有v-model怎麼辦,還有其餘指令能夠用嗎?

當你選擇使用JSX的時候,你就要作好和指令說拜拜的時候了,在JSX中, 你惟一可使用的指令是v-show,除此以外,其餘指令都是不可使用的,有沒有感到很慌,這就對了。不過呢,換一個角度思考,指令只是Vue在模板代碼裏面提供的語法糖,如今你已經能夠寫Js了,那些語法糖用Js均可以代替了。

v-model

v-modelVue提供的一個語法糖,它本質上是由 value屬性(默認) + input事件(默認)組成的,若是對自定義v-model不瞭解的同窗建議閱讀小編的文章進行了解 絕對乾貨~!學會這些Vue小技巧,能夠早點下班和女神約會了。 因此,在JSX中,咱們即可以迴歸本質,經過傳遞value屬性並監聽input事件來實現數據的雙向綁定

export default {
 data() {  return {  name: ''  }  },  methods: {  // 監聽 onInput 事件進行賦值操做  $_handleInput(e) {  this.name = e.target.value  }  },  render() {  // 傳遞 value 屬性 並監聽 onInput事件  return <input value={this.name} onInput={this.$_handleInput}></input>  } } 複製代碼

經小編測試,在新版腳手架vue-cli4中,已經默認集成了對v-model的支持,你們能夠直接使用<input v-model={this.value}>,若是你的項目比較老,也能夠安裝插件babel-plugin-jsx-v-model來進行支持

一樣的,在JSX中,對於.sync也須要用屬性+事件來實現,以下代碼所示:

export default {
 methods: {  $_handleChangeVisible(value) {  this.visible = value  }  },  render() {  return (  <ElDialog  title="測試.sync"  visible={this.visible}  on={{ 'update:visible': this.$_handleChangeVisible }}  ></ElDialog>  )  } } 複製代碼

v-if 與 v-for

在模板代碼裏面咱們經過v-for去遍歷元素,經過v-if去判斷是否渲染元素,在jsx中,對於v-for,你可使用for循環,array.map來代替,對於v-if,可使用if語句,三元表達式等來代替

循環遍歷列表

const list = ['java', 'c++', 'javascript', 'c#', 'php']
return (  <ul>  {list.map(item => {  return <li>{item}</li>  })}  </ul> ) 複製代碼

使用條件判斷

const isGirl = false
return isGirl ? <span>小妹,哥哥教你寫Vue</span> : <span>鳥你幹啥</span> 複製代碼

v-bind

在模板代碼中,咱們通常經過 v-bind:prop="value":prop="value"來給組件綁定屬性,在JSX裏面寫法也相似

render() {
 return <input value={this.name}></input> } 複製代碼

v-html 與 v-text

在說v-htmlv-text以前,咱們須要先了解一下Vue中的屬性,Vue中的屬性一共分爲三種,第一種是你們寫bug時候最經常使用的props,即組件自定義的屬性;第二種是attrs,是指在父做用域裏面傳入的,但並未在子組件內定義的屬性。第三種比較特殊,是domProps,經小編不徹底測試,在Vue中,domProps主要包含三個,分別是innerHTML,textContent/innerTextvalue

  • v-html: 在模板代碼中,咱們用v-html指令來更新元素的innerHTML內容,而在JSX裏面,若是要操縱組件的innerHTML,就須要用到domProps

    export default {
     data() {  return {  content: '<div>這是子君寫的一篇新的文章</div>'  }  },  render() {  // v-html 指令在JSX的寫法是 domPropsInnerHTML  return <div domPropsInnerHTML={this.content}></div>  } } 複製代碼
  • v-text: 看了上面的v-html,你是否是當即就想到了v-textJSX的寫法domPropsInnerText,是的,你沒有想錯

    export default {
     data() {  return {  content: '這是子君寫的一篇新的文章的內容'  }  },  render() {  return <div domPropsInnerText={this.content}></div>  } } 複製代碼

    但實際上咱們不須要使用domPropsInnerText,而是將文本做爲元素的子節點去使用便可

    <div>{this.content}</div> 複製代碼

實際上,對於domProps,只有innerHTML才須要使用domPropsInnerHTML的寫法,其餘使用正常寫法便可

我還要監聽事件呢

監聽事件與原生事件

當咱們開發一個組件以後,通常會經過this.$emit('change')的方式對外暴露事件,而後經過v-on:change的方式去監聽事件,很遺憾,在JSX中你沒法使用v-on指令,但你將解鎖一個新的姿式

render() {
 return <CustomSelect onChange={this.$_handleChange}></CustomSelect>  } 複製代碼

JSX中,經過on + 事件名稱的大駝峯寫法來監聽,好比事件icon-click,在JSX中寫爲onIconClick

有時候咱們但願能夠監聽一個組件根元素上面的原生事件,這時候會用到.native修飾符,有點絕望啊,修飾符也是不能用了,但好在也有替代方案,以下代碼

render() {
 // 監聽下拉框根元素的click事件  return <CustomSelect nativeOnClick={this.$_handleClick}></CustomSelect>  } 複製代碼

監聽原生事件的規則與普通事件是同樣的,只須要將前面的on替換爲nativeOn

除了上面的監聽事件的方式以外,咱們還可使用對象的方式去監聽事件

render() {
 return (  <ElInput  value={this.content}  on={{  focus: this.$_handleFocus,  input: this.$_handleInput  }}  nativeOn={{  click: this.$_handleClick  }}  ></ElInput>  )  } 複製代碼

事件修飾符

和指令同樣,除了個別的以外,大部分的事件修飾符都沒法在JSX中使用,這時候你確定已經習慣了,確定有替代方案的。

  • .stop : 阻止事件冒泡,在JSX中使用event.stopPropagation()來代替

  • .prevent:阻止默認行爲,在JSX中使用event.preventDefault() 來代替

  • .self:只當事件是從偵聽器綁定的元素自己觸發時才觸發回調,使用下面的條件判斷進行代替

    if (event.target !== event.currentTarget){
     return } 複製代碼
  • .enterkeyCode: 在特定鍵觸發時才觸發回調

    if(event.keyCode === 13) {
     // 執行邏輯 } 複製代碼

除了上面這些修飾符以外,尤大大爲了照顧咱們這羣CV仔,仍是作了一點優化的,對於.once,.capture,.passive,.capture.once,尤大大提供了前綴語法幫助咱們簡化代碼

render() {
 return (  <div  on={{  // 至關於 :click.capture  '!click': this.$_handleClick,  // 至關於 :input.once  '~input': this.$_handleInput,  // 至關於 :mousedown.passive  '&mousedown': this.$_handleMouseDown,  // 至關於 :mouseup.capture.once  '~!mouseup': this.$_handleMouseUp  }}  ></div>  )  } 複製代碼

對了,還有插槽

插槽就是子組件中提供給父組件使用的一個佔位符,插槽分爲默認插槽,具名插槽和做用域插槽,下面小編依次爲你帶來每種在JSX中的用法與如何去定義插槽。

默認插槽

  • 使用默認插槽

使用element-uiDialog時,彈框內容就使用了默認插槽,在JSX中使用默認插槽的用法與普通插槽的用法基本是一致的,以下代碼所示:

render() {
 return (  <ElDialog title="彈框標題" visible={this.visible}>  {/*這裏就是默認插槽*/}  <div>這裏是彈框內容</div>  </ElDialog>  )  } 複製代碼
  • 自定義默認插槽

    Vue的實例this上面有一個屬性$slots,這個上面就掛載了一個這個組件內部的全部插槽,使用this.$slots.default就能夠將默認插槽加入到組件內部

    export default {
     props: {  visible: {  type: Boolean,  default: false  }  },  render() {  return (  <div class="custom-dialog" vShow={this.visible}>  {/**經過this.$slots.default定義默認插槽*/}  {this.$slots.default}  </div>  )  } } 複製代碼

具名插槽

  • 使用具名插槽

    有時候咱們一個組件須要多個插槽,這時候就須要爲每個插槽起一個名字,好比element-ui的彈框能夠定義底部按鈕區的內容,就是用了名字爲footer的插槽

render() {
 return (  <ElDialog title="彈框標題" visible={this.visible}>  <div>這裏是彈框內容</div>  {/** 具名插槽 */}  <template slot="footer">  <ElButton>肯定</ElButton>  <ElButton>取消</ElButton>  </template>  </ElDialog>  )  } 複製代碼
  • 自定義具名插槽

在上節自定義默認插槽時提到了$slots,對於默認插槽使用this.$slots.default,而對於具名插槽,可使用this.$slots.footer進行自定義

render() {
 return (  <div class="custom-dialog" vShow={this.visible}>  {this.$slots.default}  {/**自定義具名插槽*/}  <div class="custom-dialog__foolter">{this.$slots.footer}</div>  </div>  )  } 複製代碼

做用域插槽

  • 使用做用域插槽

    有時讓插槽內容可以訪問子組件中才有的數據是頗有用的,這時候就須要用到做用域插槽,在JSX中,由於沒有v-slot指令,因此做用域插槽的使用方式就與模板代碼裏面的方式有所不一樣了。好比在element-ui中,咱們使用el-table的時候能夠自定義表格單元格的內容,這時候就須要用到做用域插槽

    data() {
     return {  data: [  {  name: '子君'  }  ]  }  },  render() {  return (  {/**scopedSlots即做用域插槽,default爲默認插槽,若是是具名插槽,將default該爲對應插槽名稱便可*/}  <ElTable data={this.data}>  <ElTableColumn  label="姓名"  scopedSlots={{  default: ({ row }) => {  return <div style="color:red;">{row.name}</div>  }  }}  ></ElTableColumn>  </ElTable>  )  } 複製代碼
  • 自定義做用域插槽

    使用做用域插槽不一樣,定義做用域插槽也與模板代碼裏面有所不一樣。加入咱們自定義了一個列表項組件,用戶但願能夠自定義列表項標題,這時候就須要將列表的數據經過做用域插槽傳出來。

    render() {
     const { data } = this  // 獲取標題做用域插槽  const titleSlot = this.$scopedSlots.title  return (  <div class="item">  {/** 若是有標題插槽,則使用標題插槽,不然使用默認標題 */}  {titleSlot ? titleSlot(data) : <span>{data.title}</span>}  </div>  )  } 複製代碼

只能在render函數裏面使用JSX

固然不是,你能夠定義method,而後在method裏面返回JSX,而後在render函數裏面調用這個方法,不只如此,JSX還能夠直接賦值給變量,好比下面這段代碼

methods: {
 $_renderFooter() {  return (  <div>  <ElButton>肯定</ElButton>  <ElButton>取消</ElButton>  </div>  )  }  },  render() {  const buttons = this.$_renderFooter()  return (  <ElDialog visible={this.visible}>  <div>這裏是一大坨內容</div>  <template slot="footer">{buttons}</template>  </ElDialog>  )  } 複製代碼

小編,你忘了指令

對不起,我如今補上。

基礎用法

雖然大部份內置的指令沒法直接在JSX裏面使用,可是自定義的指令能夠在JSX裏面使用,就拿element-uiv-loading指令來講,能夠這樣用

render() {
 /**  * 一個組件上面可使用多個指令,因此是一個數組  * name 對應指令的名稱, 須要去掉 v- 前綴  * value 對應 `v-loading="value"`中的value  */  const directives = [{ name: 'loading', value: this.loading }]  return (  <div  {...{  directives  }}  ></div>  )  } 複製代碼

修飾符

有些指令還可使用修飾符,好比上例中的v-loading,你能夠經過修飾符指定是否全屏遮罩,是否鎖定屏幕的滾動,這時候就須要這樣寫 v-loading.fullscreen.lock = "loading"

render() {
 /**  * modifiers指定修飾符,若是使用某一個修飾符,則指定這個修飾符的值爲 true  * 不使用能夠設置爲false或者直接刪掉  */  const directives = [  {  name: 'loading',  value: this.loading,  modifiers: { fullscreen: true, lock: false }  }  ]  return (  <div  {...{  directives  }}  ></div>  )  } 複製代碼

結語

不要吹滅你的靈感和你的想象力; 不要成爲你的模型的奴隸。 ——文森特・梵高

若是喜歡小編,能夠關注下面的公衆號,瞭解更多幹貨。同時,也能夠掃描下方二維碼加羣(公衆號裏面也有加羣方式)

本文使用 mdnice 排版

相關文章
相關標籤/搜索