来个大佬,如何优化设计这个组件?

基于 el-select 封装了一个 my-select

接受一个外部传入的api,其中 request 是封装好了的 axios 请求函数

const getUserList = (data)=>{
    return request({
        data
    })
}
<my-select :api="getUserList">

现在遇到一个场景, 要改变 其中的参数,改完后就重新渲染 select 我现在想了两个方案

  1. 手动更新外部 getUserList

    const params = {name:xxx}
    this.getUserList = ()=>{
     return request({
         data:params
     })
    }

    2.传入 request params 监听 params 的改变 内部更新 getUserList

    <my-select :request="request" :params="params">
    watch:{
     params:{
         handler(val){
             this.getUserList = ()=>{
                 return request({
                     data:val
                 })
             }
         },
         deep:true
     }
    }

总结下,就是传入一个业务 api, 如何更加优雅的 去更新 组件内部 api 的参数值~

底层组件,最好让使用者 更加简单 易上手~

阅读 1.9k
4 个回答

有点疑惑,select这种直接展示结果的组件,既然都是底层组件了,针对异步获取数据,直接业务使用方拿到结果后传给组件不就行了么,为什么还要大费周折把异步方法传入到组件内部去处理?
个人认为你这里应该封装的是getUserList方法,然后参数变化的时候每次重新调用这个请求,而不是封装select


// getUserList允许传入参数
const params = reactive({});
const data = ref([]);
const getUserList = (val)=>{
 return request({
     data:val
 })
}

// 这里如何保证每次params变化,this.data也变化
data.value = await this.getUserList(params);

// 比如使用computed
data.value = computed(async () => await getUserList(params));

// data如何变化全部交由业务方决定,组件只负责拿到最新的data渲染即可
<select :options="data" />

个人感觉这个组件可能没有封装的意义,看到的描述,你封装的组件有两种方向:

  1. 传递一个 API 给组件内部调用,获取数据
  2. API 固定,组件内部监听传递的参数变化,调用 API 获取数据

不管哪种方式,可以确定是只有参数会发生变化,接口是固定的;

这个时候我思考的问题是:

  1. 接口返回的数据量大吗?
  2. 接口调用频繁吗?

我思考的方向是优化,而不是封装组件;

优化的方向不管是接口返回的数据量大还是调用频繁,我都使用数据缓存来做:

方案1

<el-select>
    <el-option
        v-for="item in list"
        :key="item.id"
        :label="item.label"
        :value="item.id"
    />
</el-select>

export default {
    data() {
        return {
            selectData: [],
            params: {
                condition1: '',
                condition2: ''
            }
        }
    },
    computed: {
        list() {
            return this.selectData.filter(item => {
                const entries = Object.entries(this.params);
                return entries.every(([key, value]) => {
                    if (value === '') {
                        return true;
                    }

                    switch (typeof item[key]) {
                        case 'string':
                            return item[key].includes(value);
                        case 'number':
                            return item[key] === value;

                        // ...

                        default:
                            return false;
                    }
                })
            });
        }
    },
    created() {
        // 数据量少直接获取全部数据
        request().then(res => {
            this.selectData = res.data;
        });
    }
}

这种方式在条件多变,请求频繁,数据量并不是很大的情况下比较合适。

方案2

export default {
    data() {
        return {
            cache: {},
            params: {
                condition1: '',
                condition2: ''
            }
        }
    },
    computed: {
        list() {
            const key = JSON.stringify(this.params);
            if (!this.cache[key]) {
                request(this.params);
            }
            
            return this.cache[key] || [];
        }
    },
    methods: {
        getData() {
            request(this.params).then(res => {
                this.cache[JSON.stringify(this.params)] = res;
            })
        }
    }
}

这种在条件变化没那么频繁,且条件相对固定的情况下比较合适,适合数据量大,接口请求比较耗时的情况。


然后就是继续优化,第二种方案就没必要优化了,没几行代码,第一种情况如果条件复杂就可以封装组件,将业务逻辑封装起来,简化代码。

我的想法一直都是使用 mixins 混入。主要就是查询函数和一些 QueryParams 都固定好,只不过URL按照具体页面去赋值,或者使用 import { remoteAPI as loadData } from '@api/xxx.js 形式去引入。
具体思路可以借鉴这个 mxins 文件 JeecgListMixin.js at master · jeecgboot/ant-design-vue-jeecg

  1. request在父组件写好,传给my-select,给选择按钮添加一个点击事件,点击时调用这个从父组件传下来的request,做到点击时更新下拉菜单,缺点是每次点击select都会有一个loading
  2. requset在父组件写好,由父组件更新optionsmy-select中仅获取父组件传下来的options
  3. 在父组件受到参数影响的位置绑定一个与改参数有关的key,让参数更新时重新渲染所有内容,其中就能包括这个select和里面的值
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题