Let’s look at the effect first πŸ‘‡

Two common uses of the SELECT component are shown above

  • 1. Filtering of tables (filtering data according to items)
  • 2. Use of forms (creating receipts based on projects)

Components use cases

Give a use case an overview of what functionality the component provides

  • Supports formatting option data
  • Custom event callbacklisteners
  • Insert the scope slot provided by the component
<template> <DynamicSelect type="select" v-model="value" v-bind="config"> </DynamicSelect> <div> {{value}}</div> </template> <script> import '@/components/FormSelect' export default { data() { return { value: ", config: {clearable: true, filterable: true, options: [{name: 'FFF1', id: '1', number: 'FFF1'}, {name: 'option 2, id:' 2 ', number: 'SSS2'}, {name: 'option 3, id:' 3 ', number: 'TTT3, disabled: true}, {name: four' 'option, id: 4, number: 'LLL4' } ], props: { label: 'name', value: 'id', formatter: (val, op) => { return `${val} ${op.number}` } }, multiple: true, listeners: { change: (val) => {console.log('change ===> ', val)}, dataChange: (val) => {console.log('dataChange ===> ', val)}, }, slots: { prefix: (h) => (<i class='el-icon-edit el-input__icon' />) } } } }, } </script>Copy the code

List filtering use

For lists that filter by item are used in many places, we can choose to define a mixin file to simplify the configuration of each page

DynamicSelectOptions.js

import { requestUrl } from '@/api/constant'
const DEFAULT_PAGE = 1
const DataTableMixin = {
  data() {
    return {
      _url: ' '._params: {},
      _query: {},
      page: DEFAULT_PAGE,
      offset: 20.loading: false.// Whether the data is being loaded
      list: [].// Data list
      total: 0._dataField: null.projectSelectOption: {
        url: requestUrl.projectSettingsList,
        props: {label: 'proName'.value: 'id'},
        params: {status: 1.pageSize: 1000.pageIndex: 1},
        searchKey: "projectName".clearable: true.filterable: true}}},methods: {
     /** * Pull data *@returns {Promise<any>}* /
      async dataTableInit() {
        // Pull data according to the URL}}},export default DataTableMixin
Copy the code

A page

<template class="A app-container"> <FormSelect ref="FormSelect" value.sync="projectId" v-bind="projectSelectOption" :clearable="false" // If the clearance option is not allowed, you can override the configuration item that is bound to v-bind with the following command. false}) > </FormSelect> </template> <script type="text/ecmascript-6"> import '@/components/FormSelect' import DynamicSelectOptions form './DynamicSelectOptions' export default { data() { reutrn { projectId: '', } } } </script>Copy the code

Configuration items

Based on the currently provided configuration items to implement the basic functions of the component

parameter instructions type The default value
value data [String.Boolean.Number.Array]
formatter Formatting data Function
disabled Whether to disable Boolean false
readonly Whether the read-only Boolean false
options Alternatives to the Array
props Alternative mapping Object {label: 'label',value: 'value',children: 'children'}
className The name of the class [String.Array]
customStyle Custom styles Object
url Get the data URL dynamically String
method Dynamic data acquisition request mode String GET
params Get data request parameters dynamically Object
parseData Parse the data returned by the interface Function
searchable Whether remote search is available Boolean
searchKey Search keyword field name String label
remoteMethod Remote search method Function
listeners The event Object {}

Basic implementation

DynamicSelect/select.js

export default {
  name: 'DynamicSelect'.props: {/* ζš‚ζ—Άε°±δΈŠι’ι‚£δΊ› */},
  inheritAttrs: false.inject: {
    jlFormSubject: { default: null } // Accept the injected form instance
  },
  data() {
    return {
      // Default event
      defaultOn: {
        input: this.handleChange
      },
      optionsData: this.options,
      loading: false.oldOptionsData: null,}},computed: {
     // Because Vue cannot change the props value, Vue passes it with the new props
     newValue: {
      get({ value }) {
        return value
      },
      set(val) {
        // Bidirectional binding
        this.$emit('update:value', val)
        return val
      }
    },
    // Support an incoming callback to the Listener object
    onEvents() {
      return Object.assign({}, this.defaultOn, this.listeners)
    },
    // props supports extensions, such as passing the formatter function to format
    optionsProps() {
      return Object.assign({}, defaultProps, this.props)
    },
    bindAttrs() {
      const obj = {
        // Placeholder is not displayed when disabled
       	placeholder: this.disabled ? ' ' : this.$attrs.placeholder || 'Please select'
      }
      // Add necessary props when remote search is enabled
      if (this.searchable) {
        obj.filterable = true
        obj.remote = true
      }
      return Object.assign(
        {},
        this.$attrs,
        obj
      )
    },
    // Combine remote search params: increase fault tolerance as much as possible
    requestOption() { 
      let paramsKey = this.method.toUpperCase() === 'GET' ? 'params' : 'data'
      return {
        baseUrl: this.$attrs.baseUrl,
        url: this.url,
        method: this.method
        [paramsKey]: this.params || this.$attrs.data 
      }
    }
  },
  watch: {
    options: {
      handler(value) {
        this.optionsData = value
      },
      deep: true.immediate: true
    },
    // Allow dynamic modification of URL, params, method
    requestOption: {
      handler() {
        if (this.url) {
          this.getOptionsData()
        }
      },
      deep: true}}},Copy the code

Which of the above are our dynamic data sources, from which we can generally get some tips for encapsulating components:

  • The attribute is computedgetter/setterOr $emitTo implement thev-model
  • For example, for example, we don’t want to define the props that are critical for the user to pass, such as defining the props for requestOption for the user to pass the configuration items of the object.
  • Instead of using props for all attributes that are too marginal, $attrs and props are logically integrated into a single attribute object with computed

Dynamic request data and response data changes

// This is the same file
created() {
 // Uncascaded select
 if (!Reflect.has(this.bindAttrs, 'cascade') && this.url) {
   	// getOptionsData: Return the promise after obtaining the data. You can do other operations after obtaining the data
    this.getOptionsData().then(data= > {
     // Select the first one automatically...})}},methods: {
  // Pull the option data dynamically
  async getOptionsData() {
    return new Promise((resolve, reject) = > {
      let {
        requestOption,
        parseData
      } = this
			this.loading = true
       // Do the bottom check again
       if(! requestOption.url)returnrequest(... requestOption).then((res = {}) = > {
        this.loading = false
        // The response data can be processed
        {code: 200, pageData: {data: []}, pageIndex: 1, pageSize: 100,} => res.pageData? .data | | []} 2. For example: all we need to add a option: the return {data: res. PageData? .data ? [{label: 'all', id: 'all'},... res pageData. Data] : []} * /
        if (parseData && typeof parseData === 'function') {
          res = parseData(res)
        }
        if (Array.isArray(res.data)) {
          this.optionsData = res.data
        } else {
          this.optionsData = []
        }
        resolve(this.optionsData)
      }).catch((e) = > {
        this.optionsData = []
        this.loading = false
        reject(e)
      })
    }) 	
  }
	
}
Copy the code

From the above, we can get a rough idea of how to encapsulate components:

  • As much as possiblespread(…). Operator, avoid writing dead keys, etc
  • Less controlled blocks give sovereignty to the user as much as possible, usually through callbacks

Render function

Let’s render the select using the render function, which is not written in JSX. If you do not know the suggestion to write to the document’s render, deep into the data object block. Portal πŸ‘‰ delve into data objects

render(h) {
  const self = this   
  / / rendering options
  const optionsVnode = self.optionsData.map((op, index) = > {
    return [h('el-option', {
      attrs: {
        value: op[value],
        label: op[label],
        disabled: op.disabled
      },
      key: op.id || op.value // Bind the unique key}]})return h('el-select', {
    // Support custom className
    class: self.className, 
    // Static className, which can be used to write styles
    staticClass: 'jl-full-line'.Inline styles with high write permissions are also supported
    style: self.customStyle,
    // Pass all props and the native ATTRs support takes effect
    attrs: {
      ...self.bindAttrs
    },
    props: {
      value: self.newValue,
      loading: self.loading, ... self.bindAttrs },on: {
      ...self.onEvents, // Select all listeners to listen to
      input: self.handleInputEvent,
      change: self.handleChangeEvent,
      'visible-change': self.handleVisibleChange
    },
  }, optionsVnode)
}
Copy the code

From the above, we can get a rough idea of how to encapsulate components:

  • spread(…). Operator to make your component properties massively extensible

At this point, one might ask, why not use $listener instead of having to implement your own custom event via callback?

If you’ve written a lot of basic components you probably have

When a component is used as a child of another component, the other component will also pass down custom events that it listens for via v-on=$listener. If a component is nested too many times, it is possible that events inside the component will be affected by external components. Events such as input and change are heard by your component itself, but are also heard by the outer layer through passthrough

We won’t parse the input and change events that we listened for, but let’s talk a little bit about handleValueChanged

Data: [{id: '1', name: 'A'}, {id: '2', name: 'A'} [{id: '1', name: 'A'}, {id: '2', name: 'B'}] 'A', projectEntrys: {id: '1', name: 'A'}, ... }] */
Copy the code

We can’t listen to events to change the value. Formatter is the best way to do this. Write logic according to the current business

The following content will be updated in the next chapter

  • Customize slots to render component content
  • Provides the component’s outer render function to flexibly render the scope slots provided by the component

Rendering πŸ’—

Write in the last

If there is a piece of writing in the article is not very good or there are questions welcome to point out, I will also keep modifying in the following article. I hope I can grow with you as I progress. Those who like my article can also pay attention to it

I’ll be grateful to the first people to pay attention. At this time, you and I, young, packed lightly; And then, rich you and I, full of it.

The articles

The idea of modularity is to build the middle and background projects

The first chapter is to develop the background project with modularity

【 Front-end system 】 Talk about understanding EventLoop from an interview question (updated analysis of four advanced questions)

[Front end system] Build a great oaks from the foundation

[Front-end System] The application scenario of re in development is not just rule verification

“Functional programming practical scenario | nuggets technical essay – double festival special article”

【 suggested favorites 】 CSS obscure points are all here