如何手寫(xiě)El-Form表單組件
本文轉(zhuǎn)載自微信公眾號(hào)「前端有道」,作者星野。轉(zhuǎn)載本文請(qǐng)聯(lián)系前端有道公眾號(hào)。
前言
在剛?cè)胄袝r(shí)候,只會(huì)知道如何使用表單組件,在后面一兩年工作中也沒(méi)有什么技術(shù)積累成為一個(gè)工具人,操作最多的就是ctrl+c和ctrl+v,在去年進(jìn)了一家新公司,這家公司以前舊項(xiàng)目代碼經(jīng)過(guò)太多人的手,代碼已經(jīng)快不成人樣了,難以維護(hù),技術(shù)人員已經(jīng)跑的差不多了,我進(jìn)去好在讓我們負(fù)責(zé)新的項(xiàng)目開(kāi)發(fā),要不然可能第二天就看不到我了,哈哈。項(xiàng)目主要面向于小程序和H5端,網(wǎng)上的UI庫(kù)很難滿足產(chǎn)品后續(xù)規(guī)劃需求開(kāi)發(fā),只好開(kāi)始研究組件原理及封裝組件。
最近又個(gè)項(xiàng)目讓我有開(kāi)始接觸element-ui,想到當(dāng)初對(duì)el-form表單有一些困惑,查看一下源碼和一些技術(shù)文章,對(duì)el-form有一些新的認(rèn)識(shí)。
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: '請(qǐng)輸入名字', trigger: 'blur' }],
- age: [{ required: true, message: '請(qǐng)輸入年齡', 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兩個(gè)prop
- model表示表單綁定的數(shù)據(jù)對(duì)象
- rule表示驗(yàn)證規(guī)則策略,表單驗(yàn)證
2.el-form-item接收的prop屬性,對(duì)應(yīng)form組件的model數(shù)據(jù)中某個(gè)key值,如果rule剛好有key,給定的條件下去如失去焦點(diǎn)驗(yàn)證規(guī)則匹不匹配。
最終得到類(lèi)似這樣代碼結(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>
手寫(xiě)表單組件
組件中嵌套組件,主要是通過(guò)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>
接下來(lái)就要考慮到組件中的通訊。由于組件中有可能嵌套很多的組件,如果單純通過(guò)$parent和$children查找出來(lái)的父級(jí)組件,不一定是el-form組件。
兩個(gè)問(wèn)題:
- el-form-item組件如何得到el-form的數(shù)據(jù)
- el-form組件如何和el-form-item進(jìn)行交互
解決第一問(wèn)題,可以通過(guò)provide與inject實(shí)現(xiàn)。解決第二問(wèn)題,就要講到dispatch派發(fā)和broadcast廣播
provide與inject
通過(guò)provide將當(dāng)前表單實(shí)例傳遞到所有后代組件中,后代通過(guò)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是一種有歷史的組件通信方式,因?yàn)樗麄兪荲ue1.0提供的一種方式,在Vue2.0中廢棄了。
$dispatch: $dispatch會(huì)向上觸發(fā)一個(gè)事件,同時(shí)傳遞要觸發(fā)的祖先組件的名稱與參數(shù),當(dāng)事件向上傳遞到對(duì)應(yīng)的組件上時(shí)會(huì)觸發(fā)組件上的事件偵聽(tīng)器,同時(shí)傳播會(huì)停止。
$broadcast: $broadcast會(huì)向所有的后代組件傳播一個(gè)事件,同時(shí)傳遞要觸發(fā)的后代組件的名稱與參數(shù),當(dāng)事件傳遞到對(duì)應(yīng)的后代組件時(shí),會(huì)觸發(fā)組件上的事件偵聽(tīng)器,同時(shí)傳播會(huì)停止(因?yàn)橄蛳聜鬟f是樹(shù)形的,所以只會(huì)停止其中一個(gè)葉子分支的傳遞)
$dispatch
- /**
- * 派發(fā) (向上查找) (一個(gè))
- * @param componentName // 需要找的組件的名稱
- * @param eventName // 事件名稱
- * @param params // 需要傳遞的參數(shù)
- */
- dispatch(componentName, eventName, params) {
- let parent = this.$parent || this.$root;//$parent 找到最近的父節(jié)點(diǎn) $root 根節(jié)點(diǎn)
- let name = parent.$options.name; // 獲取當(dāng)前組件實(shí)例的name
- // 如果當(dāng)前有節(jié)點(diǎn) && 當(dāng)前沒(méi)名稱 且 當(dāng)前名稱等于需要傳進(jìn)來(lái)的名稱的時(shí)候就去查找當(dāng)前的節(jié)點(diǎn)
- // 循環(huán)出當(dāng)前名稱的一樣的組件實(shí)例
- while (parent && (!name||name!==componentName)) {
- parent = parent.$parent;
- if (parent) {
- name = parent.$options.name;
- }
- }
- // 有節(jié)點(diǎn)表示當(dāng)前找到了name一樣的實(shí)例
- if (parent) {
- parent.$emit.apply(parent,[eventName].concat(params))
- }
- },
$broadcast
- /**
- * 派發(fā) (向上查找) (一個(gè))
- * @param componentName // 需要找的組件的名稱
- * @param eventName // 事件名稱
- * @param params // 需要傳遞的參數(shù)
- */
- broadcast(componentName, eventName, params) {
- // 循環(huán)子節(jié)點(diǎn)找到名稱一樣的子節(jié)點(diǎn) 否則 遞歸 當(dāng)前子節(jié)點(diǎn)
- this.$children.map(child=>{
- if (componentName===child.$options.name) {
- child.$emit.apply(child,[eventName].concat(params))
- }else {
- broadcast.apply(child,[componentName,eventName].concat(params))
- }
- })
驗(yàn)證表單
async-validator是一個(gè)表單的異步驗(yàn)證的第三方庫(kù),也是element-ui 中的form組件所使用的驗(yàn)證方式。
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>