1. 程式人生 > >基於Vue構造器建立Form元件的通用解決方案

基於Vue構造器建立Form元件的通用解決方案

在前端平常的業務中,無論是官網、展示頁還是後臺運營系統都離不開表單,它承載了大部分的資料採集工作。所以如何更好地實現它,是平常工作中的一個重要問題。

在應用Vue框架去開發業務時,會將頁面上每個獨立的可視/可互動區域拆分為一個元件,再通過多個元件的自由組合來組成新的頁面。例如

<template>
  <header></header>
  ...
  <content></content>
  ...
  <footer></footer>
</template>
複製程式碼

當用戶的某個行為頁面觸發表單時(例如註冊、建立內容等),我們會在頁面中彈出一個From元件。通常的做法是在template

中填入一個<register-form>元件用於開發登錄檔單,並通過控制data中的UI.isOpen來對其display進行控制。

<template>
  <header></header>
  ...
  <content></content>
  ...
  <footer></footer>
  ...
  <register-form v-if="UI.isOpen">
    <form-item></form-item>
    ...
    <submit-button></submit-button>
  </register-form>
</template>
複製程式碼

這樣開發有一點優勢,Form元件與原元件可以通過prop以及event方便通訊以及資料傳遞。但是也會有以下幾個缺陷:

  • 當前元件的data必須要有UI.isOpen來控制表單,如果存在多個表單時,就會有大段的程式碼來維護表單的狀態;
  • 如果多個button觸發同一個表單時,可能需要對錶單的data進行重置;
  • 與元件化思想相違背,表單不屬於當前頁面,它只是由於使用者行為觸發的結果。

為了解決以上缺陷,並且還能具備方便通訊的優勢,本文選擇用Vue.extend將原有<register-form></register-form>元件轉化為函式,並維護在當前元件的method

中,當用戶觸發時,在頁面中掛載,關閉時自動登出。

例項:

程式碼地址:[[github.com/FatGe/examp…]]

  • APP元件
<template>
  <div id="app">
    <el-button 
        type="primary" icon="el-icon-edit-outline"
        @click="handleClick"
    >註冊</el-button>
  </div>
</template>

<script>
import register from './components/register'
import { transform } from './transform'

export default {
  name: 'App',
  methods: {
    register: transform(register),

    handleClick () {
      this.register({
        propsData: {
          name: '皮鞋'
        },
        done: name => alert(`${name}牛B`)
      })
    }
  }
}
</script>
複製程式碼

當APP元件中<el-button>的點選事件觸發時,呼叫register方法,將表單元件掛載在頁面中。

  • Form元件
<template>
  <div class="mock" v-if="isVisible">

    <div class="form-wrapper">
       <i class="el-icon-close close-btn" @click.stop="close"></i>

       <div class="header">
            ...
       </div>

        <div class="content">
            ...
        </div>

        <div class="footer">
            <el-button 
                type="primary"
                @click="handleClick"
            >確定</el-button>

            <el-button 
                type="primary"
                @click="handleClick"
            >取消</el-button>
        </div>

    </div>
     
  </div>
</template>

<script>
export default {
    
  props: {
    name: { type: String, default: '' }
  },

  data () {
    return {
      isVisible: true
    }
  },
    
  watch: {
    isVisible (newValue) {
      if (!newValue) {
        this.destroyElement()
      }
    }
  },
    
  methods: {
    handleClick ({ type }) {
      const handler = {
        close: () => this.close()
      }
    },
    destroyElement () {
      this.$destroy()
    },
    close () {
      this.isVisible = false
    }
  },
  
  created () {
    // init data
  }
  
  mounted () {
    document.body.appendChild(this.$el)
  },
    
  destroyed () {
    this.$el.parentNode.removeChild(this.$el)
  }
}
</script>
複製程式碼

兩者的通訊通過registeroptions還完成,並且Form元件中維護了自身的isVisible ,當用戶點選close或者取消時,表單會自動關閉並從頁面中移除。

原理:

上述程式碼中,最為關鍵的一步就是transform函式,它將原有的Form元件從原文的single-file components轉化為了method,其原理如下

const transform = (component) => {
  const _constructor = Vue.extend(component)
  return function (options = {}) {
    const {
      propsData
    } = options
    let instance = new _constructor({
      propsData
    }).$mount(document.createElement('div'))

    return instance
  }
}
複製程式碼

首先利用Vue的API-Vue.extend(options)[cn.vuejs.org/v2/api/#Vue…]建立一個Form元件的子類

const _constructor = Vue.extend(component)
複製程式碼

然後return一個function,它的功能是,在method呼叫時,將元件例項化

const {
  propsData
} = options
let instance = new _constructor({
  propsData
}).$mount(document.createElement('div'))
複製程式碼

為了能夠控制例項化後的元件,選擇instance返回。

當元件例項化時,它只是掛載到document.createElement('div')上,但是並沒有掛載到頁面上,所以需要將其appendChild到頁面中。為了更好的語義化,選擇在元件的生命週期中完成它在頁面中的掛載。例項化時,會觸發元件mounted生命週期,所以當其觸發時可以掛載在document.body中,具體如下

mounted () {
  document.body.appendChild(this.$el)
}
複製程式碼

有了掛載,就必須要有登出。對應的生命週期應該是destroyed,所以

method: {
  destroyElement () {
    this.$destroy()
  }   
},
destroyed () {
  this.$el.parentNode.removeChild(this.$el)
}
複製程式碼

元件登出的時間與它在頁面中顯示息息相關,所以

watch: {
  isVisible (newValue) {
    if (!newValue) {
      this.destroyElement()
      // 如果需要新增過渡動畫
      // this.$el.addEventListener('transitionend', this.destroyElement)
    }
  }
}
複製程式碼

一般Form元件有兩個功能:

  • done:代表使用者確認;
  • cancel:代表使用者取消;

當done或cancel觸發時,APP元件內可能會有相應的變化,所以在元件例項化之後,利用$on去監聽對應的done事件以及cancel事件。

done && inlineListen({
  method: 'done',
  options,
  instance
})
cancel && inlineListen({
  method: 'cancel',
  options,
  instance
})
複製程式碼

其中inlineListen函式可以方便後續新增其他的event,其程式碼為

const inlineListen = ({
  method,
  options,
  instance
}) => {
  let listener = `on${method}`
  instance[listener] = options[method]
  instance.$on(method, function (data) {
    this[listener](data)
  })
}
複製程式碼

以上是解決Form元件的原理以及方案。

使用:

可以將上述屬於Form元件公有的data以及method獨立出來,再通過mixins引入到每個表單內,例如

export default {
  data() {
    return {
      visible: true
    }
  },
  watch: {
    visible(newValue) {
      if (!newValue) {
        this.destroyElement()
      }
    }
  },
  mounted() {
    document.body.appendChild(this.$el)
  },
  destroyed() {
    this.$el.parentNode.removeChild(this.$el)
  },
  methods: {
    destroyElement() {
      this.$destroy()
    },
    close() {
      this.visible = false
    }
  }
}
複製程式碼

再通過mixins混入。

<script>
import popupWin from '../mixins/popup-win'

export default {
  mixins: [popupWin],

  data () {
    return {
      input: '',
      gender: 1
    }
  },
  methods: {
    handleClick ({ type }) {
      const handler = {
        close: () => this.close()
      }
    }
  }
}
</script>
複製程式碼
總結:

通過上述的transform函式,將原有的注入式元件轉化為了命令式,簡化了頁面狀態的維護,在通過mixins混入公有data以及method,簡化了表單元件開發。上述方法也可用於開發toast、alert、confirm等元件,只需要將Vue.prototype = transform(Toast-Component)

原創宣告: 該文章為原創文章,轉載請註明出處。