Vue項(xiàng)目總結(jié)(5)-表單組件基礎(chǔ)

表單數(shù)據(jù)處理是一個(gè)項(xiàng)目中的必不可少的內(nèi)容,編寫(xiě)表單組件是完成復(fù)雜項(xiàng)目基礎(chǔ)。本文深入整理了Vue制作表單組件的各個(gè)方面,為后續(xù)制作復(fù)雜表單組件打好基礎(chǔ)。

處理簡(jiǎn)單數(shù)據(jù)類型

基本方法

Vue中用prop從父組件向子組件傳遞數(shù)據(jù)。

下面是一個(gè)簡(jiǎn)單的輸入組件MyInput.vue,定義了屬性message,并且通過(guò)v-model指令綁定到了html的input上。

<template>
  <div class="my-input">
    <input v-model="message" />
  </div>
</template>
<script>
export default {
  name: 'MyInput',
  props: ['message'],
  watch: {
    message: function() {
      console.log(this.message)
    }
  }
}
</script>

我們?cè)诟附M件HelloModel.vue中調(diào)用這個(gè)組件。

<template>
  <div class="hello">
    <my-input :message="message" />
    <div>message: {{ message }}</div>
  </div>
</template>

<script>
import MyInput from './MyInput'

export default {
  name: 'HelloModel',
  components: { MyInput },
  data() {
    return { message: 'hello' }
  }
}
</script>

程序是可以運(yùn)行的,但是當(dāng)修改數(shù)據(jù)時(shí)(屬性message的值已經(jīng)發(fā)生變化),Vue會(huì)發(fā)出warn警告。

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"

而且父組件中的message并不會(huì)被修改,這是因?yàn)閂ue的屬性是單向綁定。

所有的 prop 都使得其父子 prop 之間形成了一個(gè)單向下行綁定:父級(jí) prop 的更新會(huì)向下流動(dòng)到子組件中,但是反過(guò)來(lái)則不行。

警告的問(wèn)題很好解決,把prop的賦值給組件的數(shù)據(jù),并且將input綁定到這個(gè)數(shù)據(jù)上(這里在input上用v-model只是想說(shuō)明v-model不能直接用于簡(jiǎn)單類型的屬性)。

<template>
  <div class="my-input">
    <input v-model="innerMessage" />
  </div>
</template>
<script>
export default {
  name: 'MyInput',
  props: ['message'],
  data() {
    return {
      innerMessage: this.message // 用prop賦值
    }
  },
  watch: {
    innerMessage: function() {
      console.log(this.innerMessage)
    }
  }
}
</script>

如何將修改傳遞回父組件?捕獲input事件,發(fā)送給父組件,讓父組件自己去去修改。

MyInput.vue添加向父組件發(fā)送事件的代碼。

<template>
  <div class="my-input">
    <input v-model="innerMessage" @input="input" />
  </div>
</template>
<script>
export default {
  name: 'MyInput',
  props: ['message'],
  data() {
    return {
      innerMessage: this.message // 用prop賦值
    }
  },
  watch: {
    message: function() {
      console.log('myinput.message', this.message)
    },
    innerMessage: function() {
      console.log('myinput.innerMessage', this.innerMessage)
    }
  },
  methods: {
    input: function(event) {
      // 接收事件并轉(zhuǎn)發(fā)給父組件,注意參數(shù)是html的原始對(duì)象
      let newVal = event.target.value
      console.log('myinput.input', newVal)
      this.$emit('input', newVal)
    }
  }
}
</script>

HelloModel.vue添加接收子組件事件的代碼。

<template>
  <div class="hello">
    <my-input :message="message" @input="input" />
    <div>message: {{ message }}</div>
  </div>
</template>

<script>
import MyInput from './MyInput'

export default {
  name: 'HelloModel',
  components: { MyInput },
  data() {
    return { message: 'hello' }
  },
  methods: {
    input: function(newVal) {
      // 接收子組件傳遞的事件,注意參數(shù)不是event
      this.message = newVal
      console.log('parent.input', this.message)
    }
  }
}
</script>

用v-model簡(jiǎn)化代碼

既然原生的input可以用v-model實(shí)現(xiàn)雙向綁定,那么MyInput組件是否也可以支持呢?父組件中使用的時(shí)候簡(jiǎn)化為:

<my-input v-model="message" />

其實(shí)是可以的,但是MyInput要做改造,生成新的Myinput2.vue文件。

<template>
  <div class="my-input">
    <!-- 注意:這里不是v-model,改成了單向綁定 -->
    <input :value="value" @input="input" />
  </div>
</template>
<script>
export default {
  name: 'MyInput',
  props: ['value'], // 需要指定名稱為value的屬性
  methods: {
    input: function(event) {
      // 接收事件并轉(zhuǎn)發(fā)給父組件,注意參數(shù)是原始event對(duì)象
      let newVal = event.target.value
      console.log('myinput.input', newVal, event)
      this.$emit('input', newVal)
    }
  }
}
</script>

生成新的HelloModel2.vue文件。

<template>
  <div class="hello">
    <my-input v-model="message" />
    <div>message: {{ message }}</div>
  </div>
</template>

<script>
import MyInput from './MyInput2'

export default {
  name: 'HelloModel',
  components: { MyInput },
  data() {
    return { message: 'hello' }
  }
}
</script>

參考:

.sync修飾符簡(jiǎn)化

不進(jìn)行解釋了,直接看代碼MyInput3.vueHelloModel.vue

<template>
  <div class="my-input">
    <input :value="value" @input="input" />
  </div>
</template>
<script>
export default {
  name: 'MyInput',
  props: ['value'], // 需要指定名稱為value的屬性
  methods: {
    input: function(event) {
      let newVal = event.target.value
      console.log('myinput.input', newVal)
      // 和.sync修飾符匹配
      this.$emit('update:value', newVal)
    }
  }
}
</script>
<template>
  <div class="hello">
    <!-- 使用.sync修飾符 -->
    <my-input :value.sync="message" />
    <div>message: {{ message }}</div>
  </div>
</template>

<script>
import MyInput from './MyInput3'

export default {
  name: 'HelloModel',
  components: { MyInput },
  data() {
    return { message: 'hello' }
  }
}
</script>

參考:https://cn.vuejs.org/v2/guide/components-custom-events.html#sync-修飾符

補(bǔ)充說(shuō)明

v-model.sync能簡(jiǎn)化表單組件的代碼,它們有什么區(qū)別嗎?

v-model寫(xiě)起來(lái)更簡(jiǎn)潔,應(yīng)為父組件不需要知道表單組件中的屬性名(默認(rèn)使用value)。但是,如果表單組件中有多個(gè)input,那么v-model就無(wú)法表示綁定關(guān)系,因?yàn)?code>.sync要指定表單控件的屬性名,就可以解決1對(duì)多的綁定問(wèn)題。

使用v-model時(shí)還會(huì)碰到一個(gè)問(wèn)題,組件上的 v-model 默認(rèn)會(huì)利用名為 value 的 prop 和名為 input 的事件,但是像單選框、復(fù)選框等類型的輸入控件可能會(huì)將 value attribute 用于不同的目的。定義組件時(shí)可以用model 選項(xiàng)避免這樣的沖突。

{
  model: {  // 定義model
    prop: 'checked',  // 綁定prop傳遞的值
    event: 'change'  // 定義觸發(fā)事件名稱
  },
  props: {
    checked: Boolean    // 接受父組件傳遞的值
  }
}

參考:https://cn.vuejs.org/v2/guide/components-custom-events.html#自定義組件的-v-model

處理對(duì)象和數(shù)組

前面表單控件的屬性都是簡(jiǎn)單類型,如果直接傳遞對(duì)象或數(shù)組會(huì)有什么不一樣?先看看Vue官網(wǎng)文檔里的一句話,然后看例子。

注意在 JavaScript 中對(duì)象和數(shù)組是通過(guò)引用傳入的,所以對(duì)于一個(gè)數(shù)組或?qū)ο箢愋偷?prop 來(lái)說(shuō),在子組件中改變這個(gè)對(duì)象或數(shù)組本身將會(huì)影響到父組件的狀態(tài)。

傳遞引用

MyForm.vue實(shí)現(xiàn)一個(gè)簡(jiǎn)單的表單組件,輸入姓名和年齡。

<template>
  <div class="my-form">
    <div>
      <label>姓名:
        <input v-model="person.name" />
      </label>
    </div>
    <div>
      <label>年齡:
        <input v-model="person.age" />
      </label>
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyForm',
  //model: { prop: 'person' },
  props: { person: Object },
  watch: {
    person: {
      handler: function() {
        console.log('myform.person', this.person)
      },
      deep: true
    }
  }
}
</script>

HelloModel.vue調(diào)用表單組件,傳遞對(duì)象。

<template>
  <div class="hello">
    <div>
      <my-form :person="person" />
    </div>
    <div>
      <div>姓名:{{person.name}}</div>
      <div>年齡:{{person.age}}</div>
    </div>
  </div>
</template>

<script>
import MyForm from './MyForm'

export default {
  name: 'HelloModel',
  components: { MyForm },
  data() {
    return { person: { name: 'hello', age: 20 } }
  }
}
</script>

我們?cè)诟缸咏M件中都添加了watch,子組件中并沒(méi)有拋出修改數(shù)據(jù)事件,當(dāng)input中的數(shù)據(jù)發(fā)生變化時(shí),父子組件中都可以監(jiān)控到(父組件在前)。

這里有個(gè)問(wèn)題:是否可以用v-model傳遞對(duì)象呢?我認(rèn)為是可以的,但是其實(shí)沒(méi)有意義,因?yàn)闆](méi)有拋出修改事件數(shù)據(jù)就已經(jīng)被修改了,v-model要解決問(wèn)題已經(jīng)不存在了。

不直接修改父組件數(shù)據(jù)

如果不希望直接修改父組件的數(shù)據(jù)怎么辦?例如:需要對(duì)用戶輸入的數(shù)據(jù)進(jìn)行檢查,檢查通過(guò)后再更改父組件的數(shù)據(jù)。解決辦法是在組件中的控件不直接綁定傳入的屬性對(duì)象,而是創(chuàng)建一個(gè)組件內(nèi)的數(shù)據(jù)進(jìn)行綁定,檢查通過(guò)后再將修改合并到父組件的對(duì)象中。

子組件MyForm2.vue

<template>
  <div class="my-form">
    <div>
      <label>姓名:
        <input v-model="innerPerson.name" />
      </label>
    </div>
    <div>
      <label>年齡:
        <input v-model="innerPerson.age" />
      </label>
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyForm',
  props: { person: Object },
  data() {
    return {
      // 復(fù)制傳入的屬性
      innerPerson: JSON.parse(JSON.stringify(this.person))
    }
  },
  methods: {
    result() {
      return this.innerPerson
    }
  }
}
</script>

父組件HelloModel5.vue。

<template>
  <div class="hello">
    <div>
      <my-form ref="myForm" :person="person" />
    </div>
    <div>
      <div>姓名:{{person.name}}</div>
      <div>年齡:{{person.age}}</div>
    </div>
    <div>
      <button @click="merge">合并數(shù)據(jù)</button>
    </div>
  </div>
</template>

<script>
import MyForm from './MyForm2'

export default {
  name: 'HelloModel',
  components: { MyForm },
  data() {
    return { person: { name: 'hello', age: 20 } }
  },
  methods: {
    merge() {
      Object.assign(this.person, this.$refs.myForm.result())
    }
  }
}
</script>

渲染函數(shù)(render)

當(dāng)業(yè)務(wù)邏輯比較復(fù)雜時(shí),我們用渲染函數(shù)render編寫(xiě)代碼可能更有效,所以下面看看如何用render實(shí)現(xiàn)上面的這些功能。

簡(jiǎn)單類型

<script>
export default {
  name: 'MyInput',
  props: ['value'], // 需要指定名稱為value的屬性
  methods: {
    input: function(event) {
      let newVal = event.target.value
      // 同時(shí)支持v-model和sync
      if (this.$listeners.input) this.$emit('input', newVal)
      if (this.$listeners['update:value']) this.$emit('update:value', newVal)
    }
  },
  render(createElement) {
    const vnodeInput = createElement('input', {
      domProps: { value: this.value }, //這里是domProps,不是props
      on: { input: this.input }
    })
    return createElement('div', { class: 'my-input' }, [vnodeInput])
  }
}
</script>

父組件不需要有任何變化,就不貼在這里了。參照之前的代碼,就更容易理解render函數(shù),其實(shí)它就是將template變成了javascript代碼,Vue的build也是這么干的。這里稍微增加了一點(diǎn)邏輯,就是讓這個(gè)組件同時(shí)支持v-modelsync兩種方式。

參考:https://cn.vuejs.org/v2/api/#vm-listeners

對(duì)象和數(shù)組

<script>
export default {
  name: 'MyForm',
  props: { person: Object },
  data() {
    return {
      innerPerson: JSON.parse(JSON.stringify(this.person))
    }
  },
  methods: {
    result() {
      return this.innerPerson
    }
  },
  render(h) {
    const vnodes = [
      ['姓名:', 'name'],
      ['年齡:', 'age']
    ].map(([label, key]) => {
      return h('div', [
        h('label', [
          label,
          h('input', {
            domProps: { value: this.innerPerson[key] },
            on: { input: event => (this.innerPerson[key] = event.target.value) }
          })
        ])
      ])
    })

    return h('div', { class: 'my-form' }, vnodes)
  }
}
</script>

從渲染函數(shù)的角度看,處理對(duì)象屬性并沒(méi)有什么特殊的地方。這里雖然只有兩個(gè)屬性,但是特意寫(xiě)成了循環(huán)的方式,是要示例一下v-forv-ifrender中的寫(xiě)法。

參考:https://cn.vuejs.org/v2/guide/render-function.html#v-if-和-v-for

總結(jié)

通過(guò)上面的例子把Vue中和表單處理相關(guān)的知識(shí)點(diǎn)都整理了一遍,解決一般性的問(wèn)題應(yīng)該是夠用了。但是,表單處理是一個(gè)可以無(wú)限復(fù)雜的事情,會(huì)碰到各種各樣的情況,細(xì)節(jié)非常多,還需不斷嘗試與研究。

本系列其他文章:

Vue項(xiàng)目總結(jié)(1)-基本概念+Nodejs+VUE+VSCode

Vue項(xiàng)目總結(jié)(2)-前端獨(dú)立測(cè)試+VUE

Vue項(xiàng)目總結(jié)(3)-前后端分離導(dǎo)致的跨域問(wèn)題分析

Vue項(xiàng)目總結(jié)(4)-API+token處理流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 主要還是自己看的,所有內(nèi)容來(lái)自官方文檔。 介紹 Vue.js 是什么 Vue (讀音 /vju?/,類似于 vie...
    Leonzai閱讀 3,536評(píng)論 0 25
  • # 在本文中,筆者又提煉了以下幾個(gè)重點(diǎn) 補(bǔ)償雙向數(shù)據(jù)綁定 Vue.$set 數(shù)據(jù)偵聽(tīng) Vue.$watch 表單綁...
    果汁涼茶丶閱讀 1,529評(píng)論 1 15
  • 什么是組件? 組件 (Component) 是 Vue.js 最強(qiáng)大的功能之一。組件可以擴(kuò)展 HTML 元素,封裝...
    youins閱讀 9,702評(píng)論 0 13
  • 一、了解Vue.js 1.1.1 Vue.js是什么? 簡(jiǎn)單小巧、漸進(jìn)式、功能強(qiáng)大的技術(shù)棧 1.1.2 為什么學(xué)習(xí)...
    蔡華鵬閱讀 3,493評(píng)論 0 3
  • 1. 簡(jiǎn)介 在Spring Cloud 中使用Feign 非常簡(jiǎn)單 2. 導(dǎo)入依賴 啟用Feign 使用如下配置 ...
    劉升Hello閱讀 342評(píng)論 0 0

友情鏈接更多精彩內(nèi)容