在vue中使用jsx語法

fantasy525發表於2018-05-19

什麼是JSX?

JSX就是Javascript和XML結合的一種格式。React發明了JSX,利用HTML語法來建立虛擬DOM。當遇到<,JSX就當HTML解析,遇到{就當JavaScript解析.

我為什麼要在vue中用JSX?

想折騰一下唄,開玩笑.最開始是因為近期在學習react,在裡面體驗了一把jsx語法,發現也並沒有別人說的很難受的感覺啊,於是就想嘗試在vue中也試下,廢話不多說,先來用程式碼來看下兩者的區別吧.

ps:vue中大部分場景是不需要用render函式的,還是用模板更簡潔直觀.

使用template

// item.vue
<template>
 <div>
   <h1 v-if="id===1">
     <slot></slot>
   </h1>
   <h2 v-if="id===2">
     <slot></slot>
   </h2>
   <h3 v-if="id===3">
     <slot></slot>
   </h3>
   <h4 v-if="id===4">
     <slot></slot>
   </h4>
 </div>
</template>

<script>
   export default {
       name: "item",
       props:{
         id:{
           type:Number,
           default:1
         }
       }
   }
</script>
複製程式碼

item元件中就是接收父元件傳過來的id值來顯示不同的h標籤,v-if可以說用到了"極致",而且寫了很多個冗餘的slot

使用render函式和jsx

// item.vue
<script>
   export default {
       name: "item",
       props:{
         id:{
           type:Number,
           default:1
         }
       },
     render(){
         const hText=`
                       <h${this.id}>${this.$slots.default[0].text}</h${this.id}>
                     `
       return <div domPropsInnerHTML={hText}></div>
     }
   }
</script>
複製程式碼

再加上父元件來控制props的值。父元件不做對比還用傳統的template格式,

// list.vue
<template>
 <div>
   <h-title :id="id">Hello World</h-title>
   <button @click="next">下一個</button>
 </div>
</template>

<script>
 import Title from './item'

 export default {
   name: "list",
   data() {
     return {
       id:1
     }
   },
   components: {
     "h-title":Title
   },
   methods:{
     next(){
       ++this.id
     }
   }
 }
</script>
複製程式碼

執行後頁面就渲染出了h1 or h2 or h3標籤,同時slot也只有一個,點選切換props的值,也會顯示不同的h標籤。第二種寫法雖然不是很直接,但是省去了很多冗餘程式碼,頁面一下清爽了很多。

沒了v-if,v-for,v-model怎麼辦?

不要著急,這些指令只是黑魔法,用js很容易實現。

  • v-if
  render(){
       return (
         <div>
           {this.show?'你帥':'你醜'}
         </div>
       )
     }
複製程式碼

寫三元表示式只能寫簡單的,那麼複雜的還得用if/else

   render(){
        let ifText
        if(this.show){
            ifText=<p>你帥</p>
        }else{
            ifText=<p>你醜</p>
        }
        return (
          <div>
            {ifText}
          </div>
        )
      }

複製程式碼
  • v-for
     data(){
        return{
          show:false,
          list:[1,2,3,4]
        }
      },
      render(){
        return (
          <div>
            {this.list.map((v)=>{
              return <p>{v}</p>
            })}
          </div>
        )
      }
複製程式碼

在jsx中{}中間是沒辦法寫if/for語句的只能寫表示式,所以就用map來當迴圈,用三元表示式來當判斷了

  • v-model

    最近在幫公司面試招人發現v-model很多人都不知道語法糖是什麼?然後有些人說我可以用原生js實現,但是他們竟然不知道在vue中怎麼實現,好吧,兩個點:傳值和監聽事件改變值。

    <script>
    export default {
        name: "item",
      data(){
        return{
          show:false,
          list:[1,2,3,4],
          text:'',
        }
      },
      methods:{
        input(e){
          this.text=e.target.value
        }
      },
      render(){
        return (
          <div>
            <input type="text" value={this.text} onInput={this.input}/>
            <p>{this.text}</p>
          </div>
        )
      }
    }
</script>
複製程式碼

怎麼用自定義元件?

很簡單,只需要匯入進來,不用再在components屬性宣告瞭,直接寫在jsx中比如

<script>
  import HelloWolrd from './HelloWorld'
    export default {
      name: "item",
      render(){
        return (
            <HelloWolrd/>
        )
      }
    }
</script>
複製程式碼

事件,class,style,ref等等怎麼繫結?

來看下面的寫法

render (h) {
  return (
    <div
      // normal attributes or component props.
      id="foo"
      // DOM properties are prefixed with `domProps`
      domPropsInnerHTML="bar"
      // event listeners are prefixed with `on` or `nativeOn`
      onClick={this.clickHandler}
      nativeOnClick={this.nativeClickHandler}
      // other special top-level properties
      class={{ foo: true, bar: false }}
      style={{ color: 'red', fontSize: '14px' }}
      key="key"
      ref="ref"
      // assign the `ref` is used on elements/components with v-for
      refInFor
      slot="slot">
    </div>
  )
}
複製程式碼

上面有個地方需要注意,當給自定義元件繫結事件時用nativeOnClick,而模板格式是用 @click.native,另外當用到給函式式元件繫結事件時就有點小坑了下面說。

JSX中的函式式元件

函式式元件無狀態,無this例項,下面是vue文件中提到的一段話:

因為函式式元件只是一個函式,所以渲染開銷也低很多。然而,對持久化例項的缺乏也意味著函式式元件不會出現在 Vue devtools 的元件樹裡。

我個人理解因為沒了狀態(data),少了很多響應式的處理,還有生命週期等過程會提高速度和減少記憶體佔用吧?

函式式元件也可以在模板格式中用只需要這樣

<template functional>

</template
複製程式碼

那jsx中的函式式元件呢?也很簡單隻需增加配置functional: true就可以了 那函式式元件沒有了this 例項怎麼繫結事件怎麼獲取props呢?

元件需要的一切都是通過上下文傳遞,包括:

  • props : 提供所有 prop 的物件
  • children: VNode 子節點的陣列
  • slots: 返回所有插槽的物件的函式
  • data:傳遞給元件的資料物件,並將這個元件作為第二個引數傳入 createElement

上面我只列舉了部分屬性,這些是非函式式元件的東西,對於函式式元件 vue 增加了context物件,需要作為render(h,context) 第二個引數傳入,this.$slots.default更新為context.children props原本是直接掛在this上的,現在變為context.props掛在了context.props上。this.data變為了context.data

需要注意的是對於函式式元件,沒有被定義為prop的特性不會自動新增到元件的根元素上,意思就是需要我們手動新增到元件根元素了,看個例子吧

//父元件
 ...省略無關程式碼
 render(){
      return (
        <Item data={this.data} class="large"/>
      )
    }
//Item.vue元件
export default {
    functional:true,
      name: "item",
      render(h,context){
        return (
          <div class="red" >
            {context.props.data}
          </div>
        )
      }
    }
複製程式碼

上面程式碼期待的是.large類名傳入到了Item的根元素上,但是其實沒有。我們需要增加點東西

// Item.vue
export default {
    functional:true,
      name: "item",
      render(h,context){
        return (
          <div class="red" {...context.data}>
            {context.props.data}
          </div>
        )
      }
    }
複製程式碼

注意到,通過展開運算子把所有的屬性新增到了根元素上,這個context.data就是你在父元件給子元件增加的屬性,他會跟你在子元素根元素的屬性智慧合併,現在.large類名就傳進來了。這個很有用,當你在父元件給子元件繫結事件時就需要這個了。下面說一個關於繫結事件的小坑

向 createElement 通過傳入 context.data 作為第二個引數,我們就把 my-functional-button 上面所有的特性和事件監聽器都傳遞下去了。事實上這是非常透明的,那些事件甚至並不要求 .native 修飾符

上面是vue官網的一段話,然而我看了一遍就忽略了一句很重要的話,就是最後一句,他說不需要.native修飾符了?好先看程式碼

// 父元件
 methods:{
      show(){
        alert('你好')
      }
    },
    render(){
      return (
        <Item data={this.data} onNativeClick={this.show} class="large"/>
      )
    }
複製程式碼

上面程式碼乍一看沒毛病,自定義元件用onNativeClick嘛,結果就是不會彈窗。唉,最後讀了幾遍剛才vue文件中的解釋,才發現原來函式式元件不需要.native修飾符,對於template格式肯定一下就反應過來了,但是jsx的話,好吧,把上面的onNativeClick重新改為onClick就好了。

現有專案哪些功能可以用jsx代替呢?

這個其實跟最開始我例舉的例子很像。我在專案中用它來幹掉了滿屏的v-if/v-else 由於我的業務是pad上的,需求是一套試卷有幾十道題目,要求一屏只顯示一道題目,點選下一題顯示下一個題,思路也比較簡單:

  1. 用一個num變數表示當前正在展示的題目索引
  2. 每次點選下一題按鈕時num++
  3. 用v-if來判斷 num===1,num===2這樣來決定展示哪個。

這一寫,模板裡面好多啊,由於我們的題目每道題的模板可能都不一樣,所以沒辦法迴圈,只能手寫全部。之前考慮過用動態元件來切換,但是放棄了,因為沒有if直觀啊。

下面看怎麼用jsx優化一下

//父元件
  export default {
    name: "list",
    data() {
      return {
       data:'我是函式式元件',
        id:1,
         tests:{
          1:<div><span>第一道題</span></div>,
          2:<div><section>第二道題</section></div>,
          3:<div><p>第三道題</p></div>
        }
      }
    },
    methods:{
      next(){
        ++this.id
      }
    },
    render(){
      return (
       <div>
         <Item data={this.tests[this.id]} class="large"/>
         <button onClick={this.next}>下一題</button>
       </div>
      )
    }
  }
複製程式碼

上面每道題目的結構都不一致

 //子元件,只接受資料展示,用函式式元件
<script>
  export default {
  functional:true,
    name: "item",
    render(h,context){
      return (
        <div class="red" {...context.data}>
          {context.props.data}
        </div>
      )
    }
  }
</script>
複製程式碼

上面沒有用任何if/else判斷就完成了功能,這裡用jsx我覺得比較合適,不知道各位大佬有什麼其他思路?

最後

總結一下吧,我們平時開發還是多用temlate因為直觀簡潔,各種指令用著很方便,等你覺得用template寫出的程式碼看著很冗餘,或者想自己控制渲染邏輯比如迴圈,判斷等等時可以考慮用JSX。另外推薦大家多用函式式元件提高效能。

第一次寫文章,希望各位花時間看了的大佬覺得哪個說的不太嚴謹還需多多包涵,提出意見我都接受。

參考資料

vue 渲染函式&jsx

babel-plugin-transform-vue-jsx

相關文章