@[TOC]

background

Recently, I studied the implementation process of a recursive component, and found that component communication is far more complicated than I have ever touched before. This article will summarize the problems in the process of component encapsulation.

Component name spelling

Careful spelling is required. I made a silly mistake. {elfrom-item}} {elfrom-item {elfrom-item}} {elfrom-item {elfrom-item}}

did you register the component correctly? 
For recursive components, make sure to provide the "name" option.
Copy the code

After 10 minutes of fidgeting, I found that the component was misspelled, and the correct spelling should have been el-form-item, but I wrote from-item instead.

El-select onchange event

Raising the change event requires additional arguments to accomplish a specific function. In this case, a lambda expression can be used to fire a specific function by passing the fixed argument val, as in the following example:

<el-form-item class="elFormItem">
  <el-select :value="dataType" 
    @change="((val)=>{add(val,index1,value)})" placeholder="Add condition">
    <el-option label="Value" value="Value"></el-option>
    <el-option label="And" value="and"></el-option>
    <el-option label="Or" value="or"></el-option>
    <el-option label="Not" value="not"></el-option>
  </el-select>
</el-form-item>
Copy the code

((val)=>{add(val,index1,value)}) {add(val,index1,value)}

If you need to bind elements, you should use v-model instead of value.

  1. V-model is bound to the value of the data, and should be used when there is an initial selected value.
  2. Value is an attribute of el-Select, which is passed in via v-Model. If you bind form elements with value, the dropdown option will not work.

Recursive component data structures

These two days, I studied a recursive component, whose basic function is to realize the splicing ES query conditions. The data structure of the component is as follows:

search: {
   and: [{
            fieldName: 'time',
            relation: '>',
            value: 'the 2020-02-02 07'
            },
            {
              or: [{
                 fieldName: 'status',
                 relation: '=',
                 value: '0'
                 },{
                 not: [{
                 fieldName: 'status',
                 relation: '=',
                 value: '2'
                 },{
                 fieldName: 'status',
                 relation: '=',
                 value: '5'
                 }]
              }]
            },{
                fieldName: 'time1',
                relation: '>',
                value: 'the 2020-02-02 07'}}]Copy the code

The AND property is an array, and each element of the array can be a basic search condition or a composite object of not/and/or, which is a typical recursive data structure.

Recursive component principle

According to the characteristics of the above data, draw the flow chart of the component:

Article on the Implementation of recursive Components for Ticket Information Expansion

The deletion logic is complicated. To ensure the normal deletion logic, the deletion operation can only start from the underlying leaf. That is, when the first element is deleted, the deletion operation is not allowed if there are child nodes. Because there is this situation:

{ and:[] }

Functions Bidirectional communication principle

When I first learned about the props communication, I had a cognitive defect that the props properties could not be modified in a child component because of this error:

[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.
Copy the code

Today, while researching parent-child component bidirectional communication, I realized that I knew too little about the use of props and that the above exception was only caused by modifications to primitive type properties.

If props is passing an object, it is possible to modify the props object information in a child component without error. When a child component modifies an object’s properties, there are two things to be aware of. The official documentation focuses on these to make the VUE respond immediately:

  1. This parameter is used when modifying the Array data of the props objectsliceCan take effect dynamically;
  2. This parameter is used when modifying an attribute of propsVue.set(this.propName, key, value)Ensure that object change monitoring is effective.

Component parameter analysis

The list of parameters required for this recursive component is:

parameter type role
search Object Mandatory, conditional data for the current component
parentSearch Object Non-mandatory, the parent element of the current component, used when the last node is deleted
parentKey String Non-mandatory, the Key of the parent element of the current component, used when the last node is deleted
index Number Non-mandatory. Data for the current component is subscript in the parent element list. Used when the last node is deleted

When a component refers to it, it only needs to provide the search attribute. The other several methods are called by the recursive component, which is mainly used to delete the conditional service.

<retrieve-panel :search="search"/>
Copy the code

Complete implementation of the code

Based on vue-admin-template project, using V-for traversal, complete the writing of recursive components. Define a file retrievepanel. vue in the/SRC /views/form directory:

<template> <div> <! Display add button without attribute --> <div v-if="Object.keys(search).length == 0">
     <el-button @click="addFirst('and')">And</el-button>
     <el-button @click="addFirst('or')">Or</el-button>
     <el-button @click="addFirst('not')">Not</el-button> </div> <! -- Otherwise, the advanced criteria panel is displayed: traverse search search criteria --> <div v-else> <div v-for="(value,key,index) in search " :key="index"> <! -- logic operation symbol --> <span class="itemIcon">{{ key }}</span> <! The traversal attribute, the presence of the fieldName attribute, is a true condition and prints --> <div class="item" v-for="(item, index1) in value" :key="index1">
            <div v-if="item['fieldName']! = undefined">
              <el-form ref="index1Form" :model="item">
                <el-row>
                 <el-col :span="18">
                    <el-form-item   class="elFormItem">
                      <el-input v-model="item.fieldName" placeholder="Input Field name"/>
                    </el-form-item>

                    <el-form-item   class="elFormItem">
                        <el-input v-model="item.relation" placeholder="Input condition symbol"/>
                    </el-form-item>

                    <el-form-item  class="elFormItem">
                        <el-input v-model="item.value" placeholder="Input field value"/>
                     </el-form-item>

                    <el-form-item class="elFormItem">
                      <el-select :value="dataType" @change="((val)=>{addOne(val,index1,value)})" placeholder="Add condition">
                        <el-option label="Value" value="Value"></el-option>
                        <el-option label="And" value="and"></el-option>
                        <el-option label="Or" value="or"></el-option>
                        <el-option label="Not" value="not"></el-option>
                      </el-select>
                    </el-form-item>
                    <el-form-item class="elFormItem">
                      <el-button @click="removeOne(index1,value,key,search)"> delete < / el - button > < / el - form - item > < / el - col > < / el - row > "< / el - form > < / div > <! -- Does not include fieldName indicates that it is a sub-component, recursively by itself --> <div v-else class="itemChildren">
               <retrieve-panel :search="item" :parentSearch="search" :parentKey="key" :index="index1"/>
            </div>
        </div>
    </div>
  </div>
</div>
</template>

<script>
import Vue from 'vue'
export default {
  name: 'RetrievePanel'.data() {return {
         dataType : ' '
     }
  },
  props: {
    'parentSearch': {type: Object,
        default: null
    },
    'parentKey': {type: String,
        default: ' '
    },
    'search': {type: Object
    },
    'index': {type: Number,
        default: -1
    }
  },
  methods:{
      addFirst(type){// Add an empty conditionlet temp = [{fieldName: ' ',
                      relation: ' ',
                      value: ' '
                      }]
         Vue.set(this.search, type, temp)
      },
      addOne: function(val,index,list){// Select add Valueif(val == 'Value'){
                list.splice(index+1, 0, {
                   fieldName: ' ',
                   relation: ' ',
                   value: ' '
                });
            }else{
                let temp = {};
                temp[val] = [{
                             fieldName: ' ',
                             relation: ' ',
                             value: ' '
                             }]
                list.splice(index+1, 0, temp);
            }
      },
	removeOne: function(index,list,key,search1){// The record to be deleted is the last entry in the list: if it contains child elements, it is not allowed to be deleted.if( index == 0 ){
	       letData = search1[key] // Contains child elements and cannot be deletedif(data.length >1 ){
	          this.$message('To ensure proper deletion logic, please delete from the bottom leaf node! ')}else{// No child element, last element, delete the last element of array list.splice(index, 1); // Remove the parent element of the last nodeif(this.parentSearch ! = null){ this.parentSearch[this.parentKey].splice(this.index, 1); }elseVue. Delete (this.search,key)}}else{
	       list.splice(index, 1);
	    }
	},
   }
}
</script>
<style scoped>
.elFormItem{
  width:150px;
  float:left;
  margin-left:10px;
  margin-top:3px;
}
.itemIcon{
    display: inline-block;
    background-size:  100% 100%;
    width:  66px;
    height:  20px;
    margin-right:  10px;
    margin-top:  5px;
    }
  .itemTitle{
    line-height:  80px;
    font-size:  20px;
    padding:  0 50px;
    }
  .itemChildren{
    padding:  0 60px;
    }
</style>
Copy the code

Modify the index.vue file in the/SRC /views/form directory and enter the following content to reference it:

<template>
  <div class="app-container">
    <retrieve-panel :search="search"/>
  </div>
</template>

<script>
import RetrievePanel from './RetrievePanel'
export default {
  components: {
    RetrievePanel
  },
  data() {
    return {
      search: {
            and: [{
            fieldName: 'time',
            relation: '>',
            value: 'the 2020-02-02 07'
            },
            {
            fieldName: 'time',
            relation: '<',
            value: 'the 2020-02-08 07'
            },
            {
              or: [{
                 fieldName: 'status',
                 relation: '=',
                 value: '0'
              },{
                 fieldName: 'status',
                 relation: '=',
                 value: '1'
              },{
                 not: [{
                 fieldName: 'status',
                 relation: '=',
                 value: '2'
                 },{
                 fieldName: 'status',
                 relation: '=',
                 value: '5'
                 }]
              }]
            },{
               fieldName: 'status',
               relation: '=',
               value: '0'
            },{
               fieldName: 'status',
               relation: '=',
               value: '1'
            }
           ]
      }
    }
  },
  methods: {
    onSubmit() {
      console.log(this.search)
      this.$message('submit! ')},onCancel() {
      this.$message({
        message: 'cancel! '.type: 'warning'
      })
    }
  }
}
</script>

<style scoped>
.line{
  text-align: center;
}
</style>
Copy the code

The results

The searh object set when the data is initialized has an AND property, and when started, click delete. After deleting all the conditions, there are three add buttons, and then reverse add data, the operation is quite a process, interested friends can test.

Apocalypse of Programming

Because the search property that the parent passes to the child is an object, modifying the search object by adding or deleting it in the child can be passed to the parent. Although VUE does not recommend this kind of data-flow logic, for complex applications like this, it is most convenient for a child component to operate directly on the parent component’s objects.

Finally, the technical points for implementing this component are summarized:

  1. Two-way communication between parent and child components is accomplished through the props object
  2. El-select requires a :value attribute, and since it doesn’t need to be stored separately, it doesn’t hurt to record it with a global variable
  3. The method by which the onchange event passes additional parameters
  4. El-input uses the Model attribute to complete the bidirectional binding of the attributes of the bound object
  5. When a recursive component is called, it passes a few extra arguments to ensure that the deletion logic is correct

One step closer to getting started with VUE components!