1. 首页
  2. >
  3. 前端开发
  4. >
  5. Vue

如何使用Select组件封装成级联组件

前言

因为工作内容变更的原因,在20年的时候,我的前端技术栈由Angular转移到了Vue。 它们都是现阶段比较火的前端框架,在一开始的时候,需要快速地转换一下组件设计的思想。


到现在也有半年多的时间了,我现在的前端技术栈主要如下:

前端框架:Vue 2

UI框架: Element UI

开发语言: TypeScript


使用这些技术,不断地开发、封装一些项目中的组件,乐此不疲。

需求

在项目开发过程中,有一个表单组件联动的场景:两个Select组件组成,第二个Select的选项内容根据第一个Select选择的结果动态加载。

如何使用Select组件封装成级联组件


设计

要设计联动组件,首先想到的是将两个Select组合起来,将两个Select通过事件机制绑定在一起,从而实现联动效果。

联动组件最终会在Form表单中使用,在设计其功能的时候就需要将表单组件基本功能考虑进去,如:disabled,clearable,placeholder,以及v-model双向绑定等等。


如何使用Select组件封装成级联组件


实现

export interface Option {   value: string | number;   label: string; }
export interface SelectProvider {   getFirstOptions(): Promise<Option[]>;   getOptionsByName(name: string, type?: string): Promise<Option[]>;   getOptionsById(id: number | string, type?: string): Promise<Option[]>;   getDefaultOptions(type?: string): Promise<Option[]>; }
export class DefaultSelectProvider {   emptyOptions: Option[] = [];      getFirstOptions(): Promise<Option[]> {     return Promise.resolve(this.emptyOptions);   }      getOptionsByName(name: string, type?: string): Promise<Option[]> {     return Promise.resolve(this.emptyOptions);   }      getOptionsById(id: number | string, type?: string): Promise<Option[]>  {     return Promise.resolve(this.emptyOptions);    }      getDefaultOptions(type?: string): Promise<Option[]> {       return Promise.resolve(this.emptyOptions);   } }
<template>   <div>     <el-select v-model="firstValue"                 :disabled="disabled"                 :clearable="clearable" placeholder="请选择" @change="handleFirstChange">       <el-option v-for="item in firstOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>     </el-select>     <el-select       v-model="secondValue"       :disabled="disabled"       :clearable="clearable"       remote       filterable       placeholder="请选择"       style="margin-left: 8px"       @change="handleSecondChange"       :remote-method="handleSecondRemoteMethod"     >       <el-option v-for="item in secondOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>     </el-select>   </div> </template>  <script lang="ts"> // import ...  @Component export default class TwoSelect extends Vue {   @Prop() value: any;   @Prop() first!: string;   @Prop() second!: string;   @Prop() clearable!: boolean;   @Prop() disabled!: boolean;   @Prop({ type: Object, default: new DefaultSelectProvider() }) selectProvider!: SelectProvider;     firstOptions: Option[] = [];   secondOptions: Option[] = [];    @Watch('value', { immediate: true, deep: true })   onValueChanged(val) {     if (val) {       this.firstValue = val[this.firstField];       this.secondValue = val[this.secondField];     } else {       this.firstValue = '';       this.secondValue = '';     }   }    firstField = this.first || 'first';   secondField = this.second || 'second';    firstValue = '';   secondValue = '';    created() {     this.selectProvider.getFirstOptions().then(res =>  this.firstOptions = res);     this.initSecondOptions();   }    initSecondOptions() {     if (!this.secondOptions || this.secondOptions.length === 0) {       this.selectProvider.getOptionsByValue(this.secondValue, this.firstValue).then(res => (this.secondOptions = res));     }   }    handleFirstChange(val) {     this.secondValue = '';     this.$emit('firstChange', val);      const ms: any = null;      this.selectProvider.getDefaultOptions(val).then(res => {       this.secondOptions = res;     });   }    emitChange() {     const value = {};     value[this.firstField] = this.firstValue;     value[this.secondField] = this.secondValue;     this.$emit('change', value);   }    handleSecondChange(val) {     this.emitChange();   }    clearValue() {     this.firstValue = '';     this.secondValue = '';   }    handleSecondRemoteMethod(query) {     // TODO: optimize with throttle-debounce     if (query !== '') {       this.selectProvider.getOptionsByLabel(query, this.firstValue).then(res => this.secondOptions = res);     }   } } </script>