Vue2 + JSX使用總結

什么是JSX

摘自 React 官方:
它被稱為 JSX蒋纬,是一個 JavaScript 的語法擴展萌壳。我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應該呈現出它應有交互的本質形式未蝌。JSX 可能會使人聯想到模板語言晌柬,但它具有 JavaScript 的全部功能。

Vue 什么時候應當使用JSX

這里說的是應當绑嘹,而不是必須稽荧。因為在絕大多數情況下,模板語法都能勝任工腋,只不過寫起來看著不太好看而已姨丈。或者使用模板語法擅腰,那寫起來恐怕不是一般的長蟋恬,而且閱讀會費勁很多。

下面我們來看下應當使用JSX而不是模板語法的情況:

假設現在有如下需求:
封裝一個機遇ant-design-vue的輸入框組件趁冈,組件要求有如下功能
1.傳入form屬性歼争,布爾值,組件自動套上a-form-model-item標簽渗勘,并接收相應的屬性
2.placeholderTip 屬性沐绒,布爾值,組件自動擋套上a-tooltip旺坠,顯示值為placeholder
3.傳入span乔遮,數字,并且大于0小于24取刃,自動套上a-col標簽
4.如果都沒傳京办,那就只渲染a-input標簽
5.如果同時傳1雷激,2板鬓,3中兩個以上的屬性秧倾,那么包裹順序為,從外到里依次是a-col,a-form-model-item,a-tooltip

讓我們先用模板語法實現下這個組件 input.vue

<!--input 輸入框-->
<template>
  <div>
<!--    先判斷是否有span-->
    <a-col v-if="span > 0 && span<24" :span="span">
      <!--    先判斷是否有form-->
      <a-form-model-item  v-if="form" :label="label" :prop="prop">
        <!--    先判斷是否有placeholderTip-->
        <a-tooltip v-if="placeholderTip" placement="topLeft" :title="placeholder">
          <a-input v-bind="$attrs"
                   :value="value"
                   @change="inputChange"
          />
        </a-tooltip>
        <!--    都沒有崩侠,只渲染a-input額-->
        <a-input v-else
                 v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-form-model-item>
      <!--    如果沒有form漆魔,判斷是否有placeholderTip-->
      <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>
      <!--    都沒有,只渲染a-input額-->
      <a-input v-else
               v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-col>
    <!--    先判斷是否有form-->
    <a-form-model-item  v-else-if="form" :label="label" :prop="prop">
      <!--    先判斷是否有placeholderTip-->
      <a-tooltip v-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>
      <!--    都沒有,只渲染a-input額-->
      <a-input v-else
               v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-form-model-item>
    <!--    如果沒有form有送,判斷是否有placeholderTip-->
    <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
      <a-input v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-tooltip>
<!--    都沒有淌喻,只渲染a-input額-->
    <a-input v-else
              v-bind="$attrs"
             :value="value"
             @change="inputChange"
    />
  </div>
</template>
<script>
export default {
  name: "Input",
  props: {
    value: [String, Number], // 值
    placeholderTip: Boolean, // 輸入框 placeholder提示
    form: Boolean, // 是否使用form-item標簽包裹
    label: String, // 標簽
    prop: String, // 校驗的prop
    placeholder:String,//提示
    span:Number,//span
  },
  methods: {
    /**
     * 輸入框改變
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
}
</script>

從上面代碼我們可以看出僧家,有好幾段看起來一樣的代碼雀摘,但是我們卻不好抽離出來“斯埃或者說并不能完全剝離阵赠。
比如這段

 <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>

在代碼中被用了4次,看著是可以剝離出去的肌稻,抽成一個新的子組件清蚀,然而接著你發(fā)現,他里面的

  <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />

這個卻是在父組件有單獨使用爹谭。而且就算剝離出去枷邪,模板里面的多個相同的判斷,v-else-if="placeholderTip" 和 v-if="placeholderTip"也無法減少诺凡。

下面我們用jsx來實現一下

<script>
export default {
  name: "Input",
  props: {
    value: [String, Number], // 值
    placeholderTip: Boolean, // 輸入框 placeholder提示
    form: Boolean, // 是否使用form-item標簽包裹
    label: String, // 標簽
    prop: String, // 校驗的prop
    placeholder:String,//提示
    span:Number,//span
  },
  methods: {
    /**
     * 輸入框改變
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
    /**
     * 渲染form-item節(jié)點
     * @param child
     * @returns {JSX.Element}
     */
    renderFormItem (child) {
      const { label, prop} = this
      return <a-form-model-item label={label} prop={prop}>
        {child}
      </a-form-model-item>
    },
    /**
     * 渲染輸入框組件
     * @returns {JSX.Element}
     */
    renderInputDom(){
      return <a-input attrs={this.$attrs}
                     value={this.value}
                     onChange={this.inputChange}
      />
    },
  },
  render (createElement, context) {
    const { placeholderTip, form,span } = this
    const hasSpan = typeof span === "number" && span>0 && span<24
    const inputChild = placeholderTip ? <a-tooltip placement="topLeft" title={this.placeholder}>
      { this.renderInputDom()}
    </a-tooltip> : this.renderInputDom()
    if(form){
      return hasSpan ? <a-col span={span}>{this.renderFormItem(inputChild)}</a-col> : this.renderFormItem(inputChild)
    }
    return hasSpan ? <a-col span={span}>{inputChild}</a-col> : inputChild
  }
}
</script>

看下兩者的不同:
首先就從代碼行數來說东揣,用模板91行,去掉模板里面的注釋腹泌,那也還有80行嘶卧,而用jsx,不到60行
其次凉袱,使用jsx芥吟,我們將渲染a-input和a-form-model-item抽離成渲染函數,是否有a-tooltip 和 a-col則使用三元運算符配合专甩。在需要的地方調用相應的渲染函數钟鸵,相比模板語法的直接復制標簽,jsx維護性更好涤躲。

上面這個例子也許還不能看出jsx的重要性携添。下面說個復雜點的需求。
1.一個表單頁面篓叶,表單項是動態(tài)的
2.頁面渲染哪個表單組件(輸入框還是下拉框烈掠,或者單選,復選框等)缸托,是根據服務器返回的數據指明的值渲染的
3.頁面每行排幾個表單元素左敌,是動態(tài)的,根據服務器返回的值和表單元素本身一些特性來決定(比如多文本輸入框俐镐,富文本矫限,直接要求占整行)
4.表單元素排列的順序先后,由數據數組的下標決定

這個需求,例子就不寫了叼风。如果用模板語法取董,那會更糟糕。

那我們什么時候應當使用jsx无宿,而不是模板茵汰?
1.頁面的渲染有比較多的條件,而且這些條件又不在同一層孽鸡,有交叉蹂午,嵌套等情況
2.頁面元素是動態(tài)的,元素間排列組合是動態(tài)的

當然彬碱,對于vue的開發(fā)者來說豆胸,一般的業(yè)務開發(fā),還是模板為主巷疼,用起來更簡單晚胡。至于jsx,除了上面說的動態(tài)表單外嚼沿,組件封裝可能會用的相對較多估盘。

jsx用法總結

對于使用vue-cli創(chuàng)建的項目,jsx是自帶的,我們不需要安裝啥東西伏尼。如果么有使用vue-cli創(chuàng)建項目忿檩,這里假設已經安裝了@vue/babel-preset-jsx插件

  • 1 如何使用jsx替代template標簽渲染dom?
    使用模板
<template>
    <div>
        <div>測試</div>
    </div>
</template>

使用jsx爆阶。render函數用于渲染html燥透,在methods的方法里面,也可以直接return html標簽辨图。在.vue文件中班套,需要寫在script標簽里面,在js文件或者jsx文件中故河,則不用script標簽

jsx返回標簽吱韭,可以簡單理解為拼串,在大括號{}里面可以寫js代碼

<script>
export default {
    methods:{
    /**
    渲染子元素
    **/
        renderChild(){
            return <div>測試</div>
        }
    },
    render(){
        return <div>{ this.renderChild() }</div>
    }
}
</script>

2 如何書寫屬性鱼的,特別是值是動態(tài)變化的屬性
使用模板

<template>
    <div>
        <div :title="$route.name">測試</div>
    </div>
</template>

jsx理盆。有變化的地方就是用大括號{},至于大括號里面凑阶,那就是js代碼猿规。所以下面的示例title={this.$route.name},也可寫成 title={this.getName()},類似這樣宙橱,在getName函數里面return 出值就好姨俩。

<script>
export default {
    /**
    渲染name
    **/
        getName(){
            return this.$route.name
            //假設組件最終需要的是組件或者標簽蘸拔,這里還可以類似這樣
            //return <div>{this.$route.name}</div>
        }
    render(){
        return <div title={this.$route.name}>測試</div>
        //return <div title={this.getName()}>測試</div>
    }
}
</script>

這里重點說下class和style。由于class和style寫法相對比較多环葵,同樣的jsx也可以有多種形式
測試樣式效果圖


image.png

樣式

.default999{
  color:#999999;
  background: blueviolet;
}
.border-red{
  border:solid 1px red;
  margin-bottom: 20px;
}
.yellow-bk{
  background: yellow;
}
.red{
  color:red;
}
.green{
  color:green;
}
.edit{
  box-shadow: 1px 1px 1px #2b96ff;
}
.view{
  box-shadow: 2px 2px 1px #8cc5ff;
}
.bold-font{
  font-weight: bold;
}
.line-through{
  text-decoration: line-through;
}

使用模板

<template>
  <div>
    <div :class="{'yellow-bk':$route.query.isEdit === 'true'}">yellow-bk</div>
    <div class="default999" :class="{'yellow-bk':$route.query.isEdit === 'true'}">yellow-bk default999</div>
    <div class="border-red" :class="$route.query.isEdit === 'true' ? 'yellow-bk' : 'default999'">yellow-bk | default / border-red</div>
    <div class="border-red" :class="[$route.query.id === 'xxx' ? 'red' : 'green', editStyle]">red | green / bold-font | line-through /border-red</div>
    <div class="border-red" :style="{
        'margin-bottom':$route.query.isEdit === 'true' ? '8px' : '10px',
        'display':$route.query.isEdit === 'true' ? 'block' : 'flex'
      }">style 1 /border-red</div>
    <div class="border-red" :style="errStyle">style 2 /border-red / errStyle</div>
  </div>
</template>
<script>
export default {
  data (){
    return {
      errStyle:{
        color:"red",
        background:"#85ce61"
      }
    }
  },
  computed:{
    editStyle(){
      return this.$route.query.isEdit === "false" ? "bold-font" : "line-through"
    }
  },
  methods: {
    /**
     * 輸入框改變
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
}
</script>

style其實就是一個對象调窍,所以不管怎么變,只要得到一個對象或者返回一個對象即可
class花樣要多點张遭,模板有對象形式邓萨,三元運算符形式,數組形式帝璧,默認類名先誉。當然也可以用函數返回

jsx

<script>
export default {
  data (){
    return {
      errStyle:{
        color:"red",
        background:"#85ce61"
      }
    }
  },
  computed:{
    editStyle(){
      return this.$route.query.isEdit === "false" ? "bold-font" : "line-through"
    }
  },
  methods: {
    /**
     * 輸入框改變
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
  render(){
    return   <div>
      <div class={{'yellow-bk':this.$route.query.isEdit === 'true'}}>yellow-bk</div>
      <div class={{ 'yellow-bk': this.$route.query.isEdit === 'true',default999:true }}>yellow-bk/default999</div>
      <div class={this.$route.query.isEdit === 'true' ? 'yellow-bk border-red' : 'default999 border-red'}>yellow-bk  /border-red</div>
      <div class={[this.$route.query.id === 'xxx' ? 'red' : 'green', this.editStyle,'border-red']}>red | green / bold-font | line-through /border-red </div>
      <div class="border-red" style={{
        'margin-bottom':this.$route.query.isEdit === 'true' ? '8px' : '10px',
        'display':this.$route.query.isEdit === 'true' ? 'block' : 'flex'
      }}>style 1 /border-red</div>
      <div class="border-red" style={this.errStyle}>style 2 /border-red / errStyle</div>
    </div>
  }
}
</script>

模板語法可以使用兩個class,一個正常使用湿刽,一個變量形式 的烁,如

<span class="red" :class={blue:isEdit}></span>

jsx只能寫一個class,上面需要寫成

<span :class={blue:isEdit,red:true}></span>

3 指令
在jsx里面诈闺,指令變成小駝峰渴庆,例如v-model可變?yōu)関Model

3.1 自定義指令
模板語法

<template>
  <div>
    <div v-default></div>
    <div v-default:指令參數默認值></div>
    <div v-default="{name:'動態(tài)數據默認值'}"></div>
    <div v-default:指令參數默認值="{name:'動態(tài)數據默認值'}"></div>
  </div>

</template>
<script>
export default {
  directives:{
    default:{
      // 當被綁定的元素插入到 DOM 中時……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定義指令"
      }
    }
  }
}
</script>

代碼運行后結果如圖:


image.png

jsx

<script>
export default {
  directives:{
    default:{
      // 當被綁定的元素插入到 DOM 中時……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定義指令"
      }
    }
  },
  render(createElement, context) {
    return   <div>
      <div vDefault></div>
      <div vDefault:指令參數默認值></div>
      <div vDefault={{name:'動態(tài)數據默認值'}}></div>
      <div vDefault:指令參數默認值={{name:'動態(tài)數據默認值'}}></div>
    </div>
  }
}
</script>

上面這段代碼看似沒問題,但是運行后雅镊,我們發(fā)現結果如下:


image.png

發(fā)現多了個true襟雷,也就是指令沒有傳值的時候,默認為true仁烹,想要實現模板的效果耸弄,還需更改下,vDefault={undefined}卓缰,下面是更改后的代碼

<script>
export default {
  name:"Input",
  directives:{
    default:{
      // 當被綁定的元素插入到 DOM 中時……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定義指令"
      }
    }
  },
  render(createElement, context) {
    return   <div>
      <div vDefault={undefined}></div>
      <div vDefault:指令參數默認值={undefined}></div>
      <div vDefault={{name:'動態(tài)數據默認值'}}></div>
      <div vDefault:指令參數默認值={{name:'動態(tài)數據默認值'}}></div>
    </div>
  }
}
</script>

3.2 內置指令
這些指令有 v-html计呈,v-if,v-for征唬,v-text捌显,v-show,v-model总寒,v-bind扶歪,v-on,v-slot等摄闸。其中只有少部分適用于駝峰形式

3.2.1 適用于駝峰形式的指令:v-show善镰,v-model,v-on(在事件綁定處單獨說明)
以表單雙向數據綁定的v-model舉例
模板語法

<template>
<a-input v-model="value">
</template>
<script>
    export default{
        data(){
            return {
                value:"",//值
                }
        }
    }
</script>

使用jsx

<script>
    export default{
        data(){
            return {
                value:"",//值
                }
        }年枕,
        render(){
            return <a-input vModel={this.value}/>
            }
    }
</script>

修飾符
模板語法

<input v-model.trim="value"/>

jsx,使用_分隔修飾符

<input vModel_trim={this.value} />

3.2.2 不適用與駝峰形式的指令炫欺。內置指令大部分都不適用于駝峰形式,除v-slot放插槽處單獨說明外画切,下面一一列舉竣稽。

3.2.2.1 v-html
我們先用v-html來試下使用駝峰形式的例子
模板語法

<template>
  <div v-html="'自定義html'"></div>
</template>

按照上面的寫法使用jsx

<script>
export default {
  render(createElement, context) {
    return   <div>
      <div vHtml={"自定義html"}></div>
    </div>
  }
}
</script>

寫好后,我們運行,發(fā)現報錯
vue.runtime.esm.js?c320:619 [Vue warn]: Failed to resolve directive: html
(found in)


image.png

就是說html不是一個指令毫别。@vue/babel-preset-jsx給出的標準寫法是使用domPropsInnerHTML

<script>
export default {
  name:"Input",
  render(createElement, context) {
    return   <div>
      <div domPropsInnerHTML={"自定義html"}></div>
    </div>
  }
}
</script>

3.2.2.2 v-text

模板語法

<template>
  <div>
    <div v-text="text"></div>
  </div>
</template>

<script>
export default {
  data(){
    return{
      text:"vText文字"
    }
  }
}
</script>

jsx語法娃弓,使用domPropsInnerText

<script>
export default {
  name: 'JsxExample',
  data(){
    return{
      text:"vText文字"
    }
  },
  render() {
    return   <div>
      <div domPropsInnerText={this.text}></div>
    </div>
  }
}
</script>

3.2.2.3 v-if
這恐怕是最簡單的了,v-if就是if else 語法
使用模板

<template>
    <div>
        <div v-if="$route.query.id === 'xxx'">測試</div>
        <div v-else>else渲染</div>
    </div>
</template>

jsx
使用 domPropsInnerText

<script>
export default {
    render(){
        if(this.$route.query.id === 'xxx'){
            return <div>測試</div>
        }
        return <div>else渲染</div>
    }
}
</script>

或者

<script>
export default {
  render(){
    return <div>{this.$route.query.id === 'xxx' ? "測試" : "else渲染"}</div>
  }
}
</script>

3.2.2.4 v-for
使用模板

<template>
  <div>
    <div v-for="(item,index) in list" :key="index">
       <span>{{item + index}}</span>
     </div>
  </div>
</template>
<script>
export default {
  data (){
    return {
      list:["測試","測試","測試","測試","測試","測試","測試"]
    }
  },
}
</script>

使用jsx

v-for 的jsx習慣使用map方法和reduce方法岛宦。最終的結果就是得到一個由dom節(jié)點組成的數組台丛。所以除了習慣性的map和reduce方法以外,理論上可以遍歷的方法都可以使用砾肺。下面分別使用map,reduce和for 循環(huán)來實現挽霉。

<script>
export default {
  data (){
    return {
      list:["測試","測試","測試","測試","測試","測試","測試"],//數據列表
    }
  },
  methods:{
    /**
     * 使用map
     * @returns {JSX.Element}
     */
    renderDomUseMap(){
      return <div>
        {
          this.list.map((item,index)=><div><span>{item + index} use map</span></div>)
        }
      </div>
    },
    /**
     * 使用reduce
     * @returns {JSX.Element}
     */
    renderDomUseReduce(){
      return <div>
        {
          this.list.reduce((result,current,index)=>{
            result.push(<div><span>{current + index} use reduce</span></div>)
            return result
          },[])
        }
      </div>
    },
        /**
     * 使用for循環(huán)
     * @returns {[]}
     */
    renderDomUseFor(){
      let listDom = []
      for(let i=0;i<this.list.length;i++){
        listDom.push(<p>{this.list[i] + i} use for</p>)
      }
      return <div>
        {listDom}
      </div>
    }
  },
  render(createElement, context) {
    // return this.renderDomUseMap()
     // return this.renderDomUseFor()
    return this.renderDomUseReduce()
  }
}
</script>

3.2.2.5 v-bind="$attrs"
封裝組件的時候,為了能全部集成我們組件內依賴的某個組件的屬性变汪,比如我們封裝一個自定義功能的輸入框侠坎,希望能全部基礎a-input的屬性,又不想去全部吧屬性定義一遍裙盾。這時候會用到v-bind="$attrs"
我們先用模板語法定義一個輸入框組件实胸,組件名字my-input.vue。這里的輸入框基于ant-design-vue 的a-input組件

<template>
  <div>
    <a-input :value="value" v-bind="$attrs" @change="inputChange"/>
  </div>
</template>

<script>
export default {
name:"MyInput",
  props:{
    value:String,//值
  },
  methods:{
    /**
     * 點擊
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e.target.value)
      this.$emit("change",e.target.value)
    }
  }
}
</script>

然后我們在父級頁面引用番官,這里父級頁面為home-view.vue

<template>
  <div class="home">
    <my-input v-model="inputValue" style="width: 300px;margin:0 auto;"/>
  </div>
</template>

<script>
import MyInput from '@/components/my-input.vue'
export default {
  name: 'HomeView',
  components: {
    MyInput
  },
  data(){
    return{
      inputValue:"",//值
    }
  }
}
</script>

代碼運行結果界面


image.png

現在我們在父級組件引用標簽處加上我們組件內并沒有定義的屬性庐完,addon-before,雖然我們沒有定義徘熔,但是a-inpu攜帶該屬性门躯,且my-input組件使用了v-bind="$attrs"

 <my-input v-model="inputValue" addon-before="Http://" style="width: 300px;margin:0 auto;"/>

加上后運行效果如下


image.png

jsx語法
下面用jsx實現v-bind="$attrs"

<script>
export default {
  name: 'MyInput',
  props:{
    value:String,//值
  },
  methods:{
    /**
     * 點擊
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e.target.value)
      this.$emit("change",e.target.value)
    }
  },
  render() {
    return   <div>
      <a-input value={this.value} vOn:change={this.inputChange}  attrs={this.$attrs} />
    </div>
  }
}
</script>

只需在a-input標簽上加上 attrs={this.$attrs} 即可

4 如何綁定事件
4.1 普通事件綁定
模板語法

<template>
  <div>
    <div @click="bindEvent">綁定事件</div>
    <a-input @change="inputChange" v-model="value"></a-input>
  </div>
</template>
<script>
export default {
  name:"Input",
  data(){
    return{
      value:"",//值
    }
  },
  methods:{
    /**
     * 綁定事件
     */
    bindEvent(e){
      console.log(e)
    },
    /**
     * 輸入事件
     * @param e
     */
    inputChange(e){
      console.log(e)
    }

  }
}
</script>


jsx

<script>
export default {
  name:"Input",
  data(){
    return{
      value:"",//值
    }
  },
  methods:{
    /**
     * 綁定事件
     */
    bindEvent(e){
      console.log(e)
    },
        /**
     * 綁定事件
     */
    bindEventByVon(e){
      console.log(e)
    },
    /**
     * 輸入事件
     * @param e
     */
    inputChange(e){
      console.log(e)
    },
    input(e){
      this.value = e.target.value
    }

  },
  render(){
    return   <div>
      <div onClick={this.bindEvent}>綁定事件</div>
       <div vOn:click={this.bindEventByVon}>v-on指令形式綁定事件</div>
    <a-input onChange={this.inputChange} vModel={this.value} />
  </div>
  }
}
</script>

結合事件說下v-model。由于v-model是由屬性 value和事件input組成酷师,因此 v-model除了如上述示例使用vModel以外讶凉,還可以分開寫,如下

 <a-input onChange={this.inputChange} value={this.value} onInput={e=>this.value=e.target.value}/>

4.2 綁定事件時傳遞參數
在模板語法中窒升,我們可以隨意如下書寫

<div>
    <a-input @input="input()"/>
    <a-button @click="submit('form')">按鈕</a-button>
</div>

使用jsx時缀遍,按照模板語法的思路和習慣,我們可能會如下書寫

<div>
    <a-input onInput={this.input()}/>
    <a-button onClick={this.submit('form')}>按鈕</a-button>
</div>

這時候會發(fā)現饱须,頁面剛加載事件就被調用了域醇。如果把模板語法看成是在頁面寫html的話,寫jsx就是通過javascript創(chuàng)建頁面元素蓉媳,所以this.input()就是直接調用了該函數譬挚,所以不能寫括號,需要寫出this.input酪呻,也就是不需要調用减宣,因為事件需要某些因素條件才能出發(fā)。那同理玩荠,我們也不能寫成this.submit('form')漆腌,這樣函數就會直接被調用了贼邓。但是事件確實需要傳參的話,就需要套在一個匿名函數里面調用闷尿,如下

<div>
    <a-input onInput={()=>this.input()}/>
    <a-button onClick={()=>this.submit('form')}>按鈕</a-button>
</div>

4.3 事件修飾符
在vue里面塑径,有些很好用得事件修飾符,比如@click.stop @click.13等填具。jsx里面修飾符用_連接
模板語法

<template>
  <div>
    <input @click.stop.prevent="click" />
  </div>
</template>

<script>
export default {
  methods:{
    /**
     * 點擊
     */
    click(){
      console.log("點擊")
    }
  }
}
</script>

jsx

<script>
export default {
  name: 'JsxExample',
  methods:{
    click(){
      console.log("click")
    }
  },
  render() {
    return   <div>
      <input vOn:click_stop_prevent={this.click} />
    </div>
  }
}
</script>

4.4 v-on="$listeners"
和v-bind="$attrs"類似功能统舀,v-on="$listeners"可以讓子組件繼承所有我們依賴的組件的事件

模板語法

<template>
  <div>
    <a-input :value="realValue" v-bind="$attrs" v-on="$listeners" @input="inputEvent" />
  </div>
</template>

<script>
export default {
  props:{
    value:[InputEvent,String],//值
  },
  data(){
    return{
      realValue:"",//真實的值
    }
  },
  watch:{
    value:function (e){
      this.realValue = (typeof e === "string" || !e) ? e : e.target.value
    }
  },
  methods:{
    inputEvent(e){
      console.log(e)
      this.$emit("input",e)
    }
  }
}
</script>

這里順便講下基于ant-design-vue和基于element-ui的輸入框使用v-on="$listeners"時的一些小區(qū)別。
ant-design-vue 的 a-input 的 input事件反出的是event事件劳景,但是value屬性接收的是字符串或數字誉简。因此不能直接將prop的value賦值給 a-input,需要單獨做處理后盟广,見上面代碼的 watch監(jiān)聽闷串。使用v-on="$listeners"的情況下,直接將prop的value賦值給 a-input,會重新觸發(fā)$listeners里面的input或者change事件衡蚂,造成接收值不準確窿克,報錯骏庸。

使用element-ui就不存在這個問題毛甲,因為element-ui的input事件直接返回value值,而不是event事件具被。使用element-ui可以如下:

<template>
  <div>
    <el-input :value="value" v-bind="$attrs" v-on="$listeners" @input="inputEvent" />
  </div>
</template>

<script>
export default {
  props:{
    value:[String],//值
  },
  methods:{
    inputEvent(value){
      console.log(value)
      this.$emit("input",value)
    }
  }
}
</script>

那如何驗證v-on="$listeners"生效呢玻募?我們在父級組件綁定一個沒有直接聲明的事件即可。這里以ant-design-vue 的 a-input舉例一姿。ant-design-vue的a-input組件有個回車事件pressEnter七咧。

父級組件HomeView.vue代碼

<template>
  <div class="home">
    <my-input v-model="inputValue" addon-before="Http://" style="width: 300px;margin:0 auto;" @pressEnter="pressEnter"/>
  </div>
</template>

<script>
import MyInput from '@/components/jsx-example.vue'

export default {
  name: 'HomeView',
  components: {
    MyInput
  },
  data(){
    return{
      inputValue:"",//值
    }
  },
  methods:{
    /**
     * 按下回車鍵
     */
    pressEnter(e){
      console.log(e)
    },
  }
}
</script>

運行后在輸入框按回車健,我們可以看到pressEnter事件成功打印了值

v-on="$listeners"的jsx語法叮叹。使用on監(jiān)聽

<script>
export default {
  name: 'MyInput',
  props:{
    value:[String,InputEvent],//值
  },
  data(){
    return{
      realValue:"",//真實的值
    }
  },
  watch:{
    value:function (e){
      this.realValue = (typeof e === "string" || !e) ? e : e.target.value
    }
  },
  methods:{
    /**
     * 點擊
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e)
    }
  },
  render() {
    return   <div>
      <a-input value={this.realValue}
               attrs={this.$attrs}
               vOn:change={this.inputChange}
               on={this.$listeners}
      />
    </div>
  }
}
</script>

既然可以用on屬性艾栋,那我們在jsx監(jiān)聽事件時,也可以直接在on里面書寫蛉顽。如下

  render() {
    return   <div>
      <a-input value={this.realValue}
               attrs={this.$attrs}
               on={{
                 change:this.inputChange,
                 ...this.$listeners
               }}
      />
    </div>
  }

5 插槽
插槽包括父組件使用jsx和子組件使用jsx蝗砾,默認插槽,具名插槽以及作用域插槽携冤。

5.1 默認插槽與具名插槽
我們先從簡單的例子開始悼粮,創(chuàng)建一個my-slot組件,使用模板語法曾棕,組件里面包括默認插槽和具名插槽
my-slot.vue

<template>
  <div>
    <div>
      <slot name="top"></slot>
    </div>
    <slot></slot>
    <div>
      <slot name="bottom"></slot>
    </div>
  </div>
</template>

然后我們在父級組件,HomeView.vue同樣使用模板語法使用插槽扣猫,代碼如下

<template>
  <div class="home">
    <my-slot>
      默認插槽
      <template #top>
        頂部插槽內容
      </template>
      <template #bottom>
        底部部插槽內容
      </template>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'
export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

或者使用vue比較老的插槽使用語法slot屬性,該屬性在vue 2.6.0版本后被廢棄

<template>
  <div class="home">
    <my-slot>
      默認插槽
      <div slot="top">
        頂部插槽內容
      </div>
      <div slot="bottom">
        底部部插槽內容
      </div>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'
export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

新建一個AboutView.vue翘地,作為新的父級組件申尤,使用jsx語法

按jsx-vue2示例的寫法

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot>
        <div slot="top">
          頂部插槽內容
        </div>
        默認插槽
        <div slot="bottom">
          底部部插槽內容
        </div>
      </my-slot>
  </div>
  }
}
</script>

父級使用jsx語法使用插槽還是比較簡單的癌幕,和模板語法沒啥區(qū)別,甚至和模板語法被廢棄的slot屬性完全一樣昧穿。

接下來我們對my-slot.vue進行jsx改造序芦。jsx里面,子組件使用this.$slots接收插槽粤咪,默認插槽的名字是default谚中。代碼如下

<script>
export default {
  name: 'MySlot',
  render() {
    const slots = this.$slots
    console.log(slots)
    return    <div>
      <div>
        {slots.top}
      </div>
      {slots.default}
      <div>
        {slots.bottom}
      </div>
    </div>
  }
}
</script>

5.2 作用域插槽
作用域插槽,就是父級組件可以使用子組件通過prop傳遞過來的變量的插槽寥枝。我們先將模板語法的my-slot定義的插槽改造成作用域插槽

<template>
  <div>
    子組件原本內容
    <div>
      <slot name="testScopeSlot" :user="user"></slot>
    </div>
  </div>
</template>
<script>
export default {
  data(){
    return{
      user:{
        name:"張三"
      },//用戶信息
    }
  }
}
</script>

相應的宪塔,對HomeView.vue做相應的改造,以便能夠接收使用user

<template>
  <div class="home">
    <my-slot>
      <template #testScopeSlot="{user}">
        作用域插槽內容:{{user.name}}
      </template>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

若父級組件使用vue 2.6.0后廢棄的語法囊拜,如下

<template>
  <div class="home">
    <my-slot>
      <div slot="testScopeSlot" slot-scope="{user}">
        作用域插槽內容:{{user.name}}
      </div>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

對AboutView.vue進行改造某筐,以便能使用jsx語法接收和使用my-slot的user變量

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            testScopeSlot: ({user}) => {
              return `作用域插槽內容:${user.name}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

這里相對之前的都比較難于理解,用slot slot-scope已經不管用了冠跷。父組件想要讀到子組件通過插槽返出的變量南誊,需要在子組件標簽上掛載scopedSlots屬性。scopedSlots是一個對象蜜托,里面包含了子組件定義的各個插槽抄囚,以名字為鍵名,鍵值是一個函數橄务。默認插槽名字仍然是default幔托。本示例定義的插槽名字是testScopeSlot,testScopeSlot的值是函數蜂挪,函數的參數是對象重挑,對象里包含user,即子組件返出的變量名棠涮。

下面我們使用jsx改造my-slot的作用域插槽

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      user:{
        name:"張三"
      },//用戶信息
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    console.log(scopedSlots)
    // const testScopeSlotDom = scopedSlots.testScopeSlot({user:this.user})
    // console.log(testScopeSlotDom)
    return  <div>
      子組件原本內容
      <div>
        {scopedSlots.testScopeSlot({user:this.user})}
    </div>
  </div>
  }
}
</script>

image.png

由于testScopeSlot是一個函數谬哀,因此我們只需要執(zhí)行testScopeSlot函數即可,然后將use作為函數的參數傳遞就行严肪。這里有點繞史煎,可以這樣反過來理解,父級組件定義了一個函數诬垂,函數接收一個對象參數劲室,對象中包含user屬性,將這個函數傳遞到子組件结窘,子組件執(zhí)行這個函數很洋,并將子組件變量作為參數傳遞給函數,子組件執(zhí)行函數后隧枫,函數將相應的結果return出去喉磁,被父組件接收谓苟,然后父組件處理,用于顯示协怒。

下面我們將最初定義的默認插槽和具名插槽都改成作用域插槽試試涝焙。更改后的my-slot

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      topInfo:"我是頂部插槽數據",//頂部插槽
      defaultInfo:"我是默認插槽數據",//頂部插槽
      bottomInfo:"我是頂部插槽數據",//頂部插槽
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    const {topInfo,defaultInfo,bottomInfo} = this
    return   <div>
      <div>
        {scopedSlots.top({topInfo})}
      </div>
      {scopedSlots.default({defaultInfo})}
      <div>
        {scopedSlots.bottom({bottomInfo})}
      </div>
    </div>
  }
}
</script>

相應的,我們更改AboutView.vue文件

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            top: ({topInfo}) => {
              return `作用域插槽內容:${topInfo}`
            },
            default: ({defaultInfo}) => {
              return `作用域插槽內容:${defaultInfo}`
            },
            bottom: ({bottomInfo}) => {
              return `作用域插槽內容:${bottomInfo}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

運行結果


image.png

按照vue默認定義的作用域插槽數據孕暇,參數是一個對象形式仑撞。因此我們在子組件執(zhí)行函數時,需要按對象形式傳遞妖滔,如 { topInfo } 隧哮。既然是我們自己傳遞參數,那我們是不是可以更改下參數傳遞形式座舍,如下 my-slot.vue

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      topInfo:"我是頂部插槽數據",//頂部插槽
      defaultInfo:"我是默認插槽數據",//頂部插槽
      bottomInfo:"我是頂部插槽數據",//頂部插槽
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    const {topInfo,defaultInfo,bottomInfo} = this
    return   <div>
      <div>
        {scopedSlots.top(topInfo)}
      </div>
      {scopedSlots.default(defaultInfo)}
      <div>
        {scopedSlots.bottom(bottomInfo)}
      </div>
    </div>
  }
}
</script>

然后相應的 AboutView.vue做更改

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            top: (topInfo) => {
              return `作用域插槽內容:${topInfo}`
            },
            default: (defaultInfo) => {
              return `作用域插槽內容:${defaultInfo}`
            },
            bottom: (bottomInfo) => {
              return `作用域插槽內容:${bottomInfo}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

這里把傳遞和接收參數都改成字符串沮翔,運行結果相同。這里也提現了jsx在某種情況下的優(yōu)勢曲秉,相比模板語法采蚀,jsx能更靈活的控制代碼邏輯。

參考:vue2-jsx: https://github.com/vuejs/jsx-vue2

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末承二,一起剝皮案震驚了整個濱河市榆鼠,隨后出現的幾起案子,更是在濱河造成了極大的恐慌矢洲,老刑警劉巖璧眠,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異读虏,居然都是意外死亡,警方通過查閱死者的電腦和手機袁滥,發(fā)現死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門盖桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人题翻,你說我怎么就攤上這事揩徊。” “怎么了嵌赠?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵塑荒,是天一觀的道長。 經常有香客問我姜挺,道長齿税,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任炊豪,我火速辦了婚禮凌箕,結果婚禮上拧篮,老公的妹妹穿的比我還像新娘。我一直安慰自己牵舱,他們只是感情好串绩,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芜壁,像睡著了一般礁凡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慧妄,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天把篓,我揣著相機與錄音,去河邊找鬼腰涧。 笑死韧掩,一個胖子當著我的面吹牛,可吹牛的內容都是我干的窖铡。 我是一名探鬼主播疗锐,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼费彼!你這毒婦竟也來了滑臊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤箍铲,失蹤者是張志新(化名)和其女友劉穎雇卷,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體颠猴,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡关划,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了翘瓮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贮折。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖资盅,靈堂內的尸體忽然破棺而出调榄,到底是詐尸還是另有隱情,我是刑警寧澤呵扛,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布每庆,位于F島的核電站芹务,受9級特大地震影響京景,放射性物質發(fā)生泄漏。R本人自食惡果不足惜瞬雹,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凤价。 院中可真熱鬧鸽斟,春花似錦、人聲如沸利诺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慢逾。三九已至立倍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侣滩,已是汗流浹背口注。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留君珠,地道東北人寝志。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像策添,于是被迫代替她去往敵國和親材部。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容