如何手寫El-Form表單組件
本文轉(zhuǎn)載自微信公眾號「前端有道」,作者星野。轉(zhuǎn)載本文請聯(lián)系前端有道公眾號。
前言
在剛?cè)胄袝r候,只會知道如何使用表單組件,在后面一兩年工作中也沒有什么技術積累成為一個工具人,操作最多的就是ctrl+c和ctrl+v,在去年進了一家新公司,這家公司以前舊項目代碼經(jīng)過太多人的手,代碼已經(jīng)快不成人樣了,難以維護,技術人員已經(jīng)跑的差不多了,我進去好在讓我們負責新的項目開發(fā),要不然可能第二天就看不到我了,哈哈。項目主要面向于小程序和H5端,網(wǎng)上的UI庫很難滿足產(chǎn)品后續(xù)規(guī)劃需求開發(fā),只好開始研究組件原理及封裝組件。
最近又個項目讓我有開始接觸element-ui,想到當初對el-form表單有一些困惑,查看一下源碼和一些技術文章,對el-form有一些新的認識。
Form 表單
下面是一份el-form示例代碼
- <template>
- <el-form :model="ruleForm" :rules="rules" ref="ruleForm">
- <el-form-item label="名字" prop="pass">
- <el-input type="password" v-model="ruleForm.pass"></el-input>
- </el-form-item>
- <el-form-item label="年齡" prop="age">
- <el-input v-model.number="ruleForm.age"></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
- <el-button @click="resetForm('ruleForm')">重置</el-button>
- </el-form-item>
- </el-form>
- </template>
- <script>
- export default {
- data() {
- return {
- ruleForm: {
- pass: '',
- checkPass: '',
- age: '',
- },
- rules: {
- pass: [{ required: true, message: '請輸入名字', trigger: 'blur' }],
- age: [{ required: true, message: '請輸入年齡', trigger: 'blur' }],
- },
- }
- },
- methods: {
- submitForm(formName) {
- this.$refs[formName].validate(valid => {
- if (valid) {
- alert('submit!')
- } else {
- console.log('error submit!!')
- return false
- }
- })
- },
- resetForm(formName) {
- this.$refs[formName].resetFields()
- },
- },
- }
- </script>
首先要清楚一下組件的使用方式
1.el-form接收model和rule兩個prop
- model表示表單綁定的數(shù)據(jù)對象
- rule表示驗證規(guī)則策略,表單驗證
2.el-form-item接收的prop屬性,對應form組件的model數(shù)據(jù)中某個key值,如果rule剛好有key,給定的條件下去如失去焦點驗證規(guī)則匹不匹配。
最終得到類似這樣代碼結(jié)構(gòu)
- <template>
- <div>
- <form @submit.prevent>
- <div>
- 姓名<input v-model="form.name" />
- </div>
- <div>
- 年齡<input v-model="form.age" />
- </div>
- <div>
- <button @click="submit">提交</button>
- </div>
- </form>
- </div>
- </template>
手寫表單組件
組件中嵌套組件,主要是通過slot插槽,可以將組件拼接成上面代碼結(jié)構(gòu)。代碼如下
el-form
- <template>
- <form>
- <slot></slot>
- </form>
- </template>
- <script>
- export default {
- name:'elForm'
- }
- </script>
el-form-item
- <template>
- <div>
- <slot></slot>
- </div>
- </template>
- <script>
- export default {
- name:'elFormItem'
- }
- </script>
el-input
- <template>
- <input type="text">
- </template>
- <script>
- export default {
- name:'elInput'
- }
- </script>
接下來就要考慮到組件中的通訊。由于組件中有可能嵌套很多的組件,如果單純通過$parent和$children查找出來的父級組件,不一定是el-form組件。
兩個問題:
- el-form-item組件如何得到el-form的數(shù)據(jù)
- el-form組件如何和el-form-item進行交互
解決第一問題,可以通過provide與inject實現(xiàn)。解決第二問題,就要講到dispatch派發(fā)和broadcast廣播
provide與inject
通過provide將當前表單實例傳遞到所有后代組件中,后代通過inject接受傳遞的值。
el-form
- <template>
- <form><slot></slot></form>
- </template>
- <script>
- export default {
- name:'elForm',
- provide(){
- return {
- elForm: this
- }
- },
- props:{
- model:{
- type:Object,
- default:()=>({})
- },
- rules:Object
- }
- }
- </script>
el-form-item
- <template>
- <div><slot></slot></div>
- </template>
- <script>
- export default {
- name:'elFormItem',
- inject:['elForm'],
- props:{
- label:{
- type:String,
- default:''
- },
- prop:String
- },
- mounted(){
- console.log(this.elForm)
- }
- }
- </script>
provide中this指el-form組件,this.elForm就能得到el-form組件中的數(shù)據(jù)和方法。
dispatch和broadcast廣播
$dispatch與$broadcast是一種有歷史的組件通信方式,因為他們是Vue1.0提供的一種方式,在Vue2.0中廢棄了。
$dispatch: $dispatch會向上觸發(fā)一個事件,同時傳遞要觸發(fā)的祖先組件的名稱與參數(shù),當事件向上傳遞到對應的組件上時會觸發(fā)組件上的事件偵聽器,同時傳播會停止。
$broadcast: $broadcast會向所有的后代組件傳播一個事件,同時傳遞要觸發(fā)的后代組件的名稱與參數(shù),當事件傳遞到對應的后代組件時,會觸發(fā)組件上的事件偵聽器,同時傳播會停止(因為向下傳遞是樹形的,所以只會停止其中一個葉子分支的傳遞)
$dispatch
- /**
- * 派發(fā) (向上查找) (一個)
- * @param componentName // 需要找的組件的名稱
- * @param eventName // 事件名稱
- * @param params // 需要傳遞的參數(shù)
- */
- dispatch(componentName, eventName, params) {
- let parent = this.$parent || this.$root;//$parent 找到最近的父節(jié)點 $root 根節(jié)點
- let name = parent.$options.name; // 獲取當前組件實例的name
- // 如果當前有節(jié)點 && 當前沒名稱 且 當前名稱等于需要傳進來的名稱的時候就去查找當前的節(jié)點
- // 循環(huán)出當前名稱的一樣的組件實例
- while (parent && (!name||name!==componentName)) {
- parent = parent.$parent;
- if (parent) {
- name = parent.$options.name;
- }
- }
- // 有節(jié)點表示當前找到了name一樣的實例
- if (parent) {
- parent.$emit.apply(parent,[eventName].concat(params))
- }
- },
$broadcast
- /**
- * 派發(fā) (向上查找) (一個)
- * @param componentName // 需要找的組件的名稱
- * @param eventName // 事件名稱
- * @param params // 需要傳遞的參數(shù)
- */
- broadcast(componentName, eventName, params) {
- // 循環(huán)子節(jié)點找到名稱一樣的子節(jié)點 否則 遞歸 當前子節(jié)點
- this.$children.map(child=>{
- if (componentName===child.$options.name) {
- child.$emit.apply(child,[eventName].concat(params))
- }else {
- broadcast.apply(child,[componentName,eventName].concat(params))
- }
- })
驗證表單
async-validator是一個表單的異步驗證的第三方庫,也是element-ui 中的form組件所使用的驗證方式。
el-form-item
- <template>
- <div>
- <label v-if="label">{{label}}</label>
- <slot></slot>
- {{errorMessage}}
- </div>
- </template>
- <script>
- import Schema from "async-validator";
- export default {
- name: "elFormItem",
- inject: ["elForm"],
- props: {
- label: {
- type: String,
- default: ""
- },
- prop: String
- },
- data(){
- return {errorMessage:''}
- },
- mounted() {
- this.$on("validate", () => {
- if (this.prop) {
- let rule = this.elForm.rules[this.prop];
- let newValue = this.elForm.model[this.prop];
- let descriptor = {
- [this.prop]: rule
- };
- let schema = new Schema(descriptor);
- return schema.validate({[this.prop]:newValue},(err,res)=>{
- if(err){
- this.errorMessage = err[0].message;
- }else{
- this.errorMessage = ''
- }
- })
- }
- });
- }
- };
- </script>