Vue JSX、自定義 v-model

​博客地址:https://ainyi.com/92

最初用到 JSX,就是做這個博客的時候。iview 表格組件,不支持像 element 那樣直接寫 html 代碼渲染,只能通過 render 函數渲染,也就是 JSX 語法

這個說起來不陌生,JSX 是 react 框架的老本行了,玩 react 的同學肯定對這個也玩的很溜(最近在公司做的某些項目也是 react)
那我還是記錄一下在 Vue JSX 的使用吧

JSX 定義

JSX 是一種 JavaScript 的語法擴展,多運用於 React 架構中。JSX = Javascript + XML,即在 Javascript 裏面寫 XML,即具備 Javascript 的靈活性,又有 html 的語義化和直觀性

應用場景

有人說,Vue 的模板語法簡單易上手,實現的功能也幾乎足夠。JSX 不好上手,寫起來代碼量也多,用來幹啥呢
那你就忽略了 JavaScript 的靈活性了

函數式組件

簡單說一下函數式組件
函數式組件就是函數是組件。使用過 React 的同學,應該不會對函數式組件感到陌生
函數式組件,我們可以理解爲沒有內部狀態沒有生命週期鉤子函數沒有 this(不需要實例化的組件)

在日常開發中,經常會開發一些純展示性的業務組件,比如一些詳情頁面,列表界面等,它們有一個共同的特點是隻需要將外部傳入的數據進行展現,不需要有內部狀態,不需要在生命週期鉤子函數裏面做處理,這時候你就可以考慮使用函數式組件

export default {
  // 通過配置 functional 屬性指定組件爲函數式組件
  functional: true,
  // 組件接收的外部屬性
  props: {
    avatar: {
      type: String
    }
  },
  /**
   * 渲染函數
   * @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>
  }
}

使用函數式組件的原因:

  1. 最主要最關鍵的原因是函數式組件不需要實例化,無狀態,沒有生命週期,所以渲染性能要好於普通組件
  2. 函數式組件結構比較簡單,代碼結構更清晰

函數式組件與普通組件的區別

  1. 函數式組件需要在組件上聲明functional
  2. 函數式組件不需要實例化,所以沒有 this,this通過render函數的第二個參數來代替
  3. 函數式組件沒有生命週期鉤子函數,不能使用計算屬性、watch 等等
  4. 函數式組件不能通過 $emit 對外暴露事件,調用事件只能通過context.listeners.click的方式調用外部傳入的事件
  5. 因爲函數式組件是沒有實例化的,所以在外部通過ref去引用組件時,實際引用的是 HTMLElement
  6. 函數式組件的props可以不用顯示聲明,所以沒有在props裏面聲明的屬性都會被自動隱式解析爲 prop,而普通組件所有未聲明的屬性都被解析到 $attrs 裏面,並自動掛載到組件根元素上面(可以通過 inheritAttrs 屬性禁止)

模板語法聲明函數式組件

在 Vue2.5 之前,使用函數式組件只能通過 JSX 的方式,在之後可以通過模板語法來聲明函數式組件

<!-- 在template 上面添加 functional屬性 -->
<template functional>
  <img :src="props.avatar ? props.avatar : 'default-avatar.png'" />
</template>
<!-- 上面第 6 條,可以省略聲明 props -->

瞭解 createElement

學習 JSX 之前,先了解 createElement
字面意思,創建元素 大名鼎鼎的虛擬DOM應該都知道吧,就是它的返回值 => 插播一個以前寫過的VNode傳送門:virtual DOM

關於 createElement 方法,有三個參數:

  1. 第一個參數主要用於提供 dom 的 html 內容,類型可以是字符串、對象或函數。比如 “div” 就是創建一個 div 標籤
  2. 第二個參數(類型是對象)主要用於設置這個 dom 的一些樣式、屬性、傳的組件的參數、綁定事件之類,具體可以參考 官方文檔 裏這一小節的說明
  3. 第三個參數(類型是數組,數組元素類型是 VNode)主要用於說是該節點下有其他結點的話,就放在這裏

使用例子:

export default {
 methods: {
    $_handleChangeUser(value) {
      this.formInline.user = value
    }
  },
  render(h) {
    return h(
      'ElForm',
      {
        props: {
          inline: true,
          model: this.formInline
        },
        staticClass: 'demo-form-inline'
      },
      [
        h(
          'ElFormItem',
          {
            props: {
              label: '用戶名'
            }
          },
          [
            h('ElInput', {
              props: {
                value: this.formInline.user
              },
              attrs: {
                placeholder: '請輸入用戶名'
              },
              on: {
                input: this.$_handleChangeUser
              }
            })
          ]
        )
      ]
    )
  }
}

看起來寫法十分複雜,若頁面上這麼多元素,頻繁使用 createElement 方法難免代碼臃腫,這時就應該使用 JSX 代替 createElement 了

JSX

我們再來用 JSX 語法重新實現上面的代碼

methods: {
  $_handleInputUser(value) {
    this.formInline.user = value
  }
},
render(h) {
  return (
    <el-form inline model={this.formInline} class="demo-form-inline">
      <el-form-item label="用戶名">
        <el-input
          value={this.formInline.user}
          onInput={this.$_handleInputUser}
          placeholder="請輸入用戶名"
        ></el-input>
      </el-form-item>
    </el-form>
  )
}

跟 react 一模一樣了
將 h 作爲 createElement 的別名 是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的
從 Vue 的 Babel 插件的 3.4.0 版本開始,我們會在以 ES2015 語法聲明的含有 JSX 的任何方法和 getter 中 (不是函數或箭頭函數中) 自動注入const h = this.$createElement,這樣就可以去掉 (h) 參數了。對於更早版本的插件,如果 h 在當前作用域中不可用,應用會報錯

Vue JSX 中指令的使用

我們使用 Vue 模板語法,指令用的爽歪歪,像 v-model, v-if, v-for, @, 插槽等等
但是,這些都在 JSX 中無法使用。那麼如何實現相同的功能呢

注意:新版 vue-cli4 中,已經默認集成了 JSX 語法對 v-model 的支持,可以直接使用 <input v-model={this.value}>
如果你的項目比較老,也可以安裝插件 babel-plugin-jsx-v-model 來進行支持

自定義 v-model

v-model 是 Vue 提供的一個語法糖,它本質上是由 value 屬性 + input 事件組成的(都是原生的默認屬性)
所以在 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>
  }
}

封裝組件如下:

子組件

<template>
  <div>
    <input :value="value" @change="$_handleChange" />
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {}
  },
  methods: {
    $_handleChange(e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>

父組件

<template>
  <div class="home">
    <krry-input v-model="say"></krry-input>
  </div>
</template>

<script>
export default {
  name: 'Home',
  components: {
    KrryInput: () => import('@/components/KrryInput')
  },
  data() {
    return {
      say: 'haha'
    }
  }
}
</script>

再次重申:新版 vue-cli4 中,已經默認集成了 JSX 語法對 v-model 的支持,可以直接使用 <input v-model={this.value}>

el-form 的 :model 屬性

注意 el-form 的 :model 屬性,在 JSX 中是這樣寫的 props={{ model: this.data }},比較特別

<el-form
  ref="form"
  labelWidth="140px"
  rules={this.rules}
  props={{ model: this.data }}>
  <el-form-item label="券碼編號: ">
    <el-input v-model={this.data.voucherCode}></el-input>
  </el-form-item>
<el-form>

v-if | v-for

這兩個就比較常見,搞 react 都知道,v-if 可以使用三元表達式,true or false 來渲染組件;v-for 就使用 map 方法來實現

const list = ['宮', '商', '角', '徵', '羽']
return (
  <ul>
    { list.map(ele => <li>{ele}</li>) }
  </ul>
)

v-html | v-text

在 JSX 裏面,如果要設置 dom 元素的 innerHTML,就用到 domProps

export default {
  data() {
    return {
      content: '<div>這是我的自定義的 html 元素</div>'
    }
  },
  render() {
    // v-html 指令在 JSX 的寫法是 domPropsInnerHTML
    return <div domPropsInnerHTML={this.content}></div>
  }
}

v-text 就沒啥好說的了,<div domPropsInnerText={this.content}></div>
還不如直接使用 <div>{this.content}</div>

監聽事件

監聽事件想到用 onChange, onClick
需要注意的是,傳參數不能使用 onClick={this.removePhone(params)},這樣子會每次 render 的時候都會自動執行一次方法
應該使用 bind,或者箭頭函數來傳參

<button type="button" onClick={this.handleClick.bind(this, 11)}></button>
<button type="button" onClick={() => this.handleClick(11)}></button>

除此之外,還可以使用對象的方式去監聽事件

render() {
  return (
    <el-input
      value={this.content}
      on={{
        focus: this.$_handleFocus,
        input: this.$_handleInput
      }}
      nativeOn={{
        click: this.$_handleClick
      }}
    ></el-input>
  )
}

使用範圍

不僅僅在 render 函數裏面使用 JSX,而且還可以在 methods 裏面返回 JSX,然後在 render 函數裏面調用這個方法
JSX 還可以直接賦值給變量

methods: {
  $_renderFooter() {
    return (
      <div>
        <el-button>確定</el-button>
        <el-button>取消</el-button>
      </div>
    )
  }
},
render() {
  const buttons = this.$_renderFooter()
  return (
    <el-dialog visible={this.visible}>
      <div>彈窗內容</div>
      <template slot="footer">{buttons}</template>
    </el-dialog>
  )
}

​博客地址:https://ainyi.com/92

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章