likes
comments
collection
share

源哥带你CodeReview03:提取table

作者站长头像
站长
· 阅读数 23

讲解在同名B/D上都有,主要介绍一些跟业务无关的代码技巧

注: 在CodeReview中,部分内容主观性较大,一家之言姑且听之

本文主要介绍对table的基础封装,介绍下组件/业务组件/模块的区别

概览

ui组合组件业务组件动态/模块/复用
目标ui复用业务封装业务流程复用
对接ui/pm自己pm/后端
是否处理接口不处理可以可以
减少data/methods可以可以
自定义事件很少随意模型相关
继承-继承某个ui组件

代码抽象

<template>
  <div id="app">
    <el-table v-loading="loading" :data="tableData" style="width: 100%">
      <el-table-column prop="name" label="Name" min-width="180" />
    </el-table>
    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-size="10"
      layout="total, prev, pager, next" :total="100">
    </el-pagination>
    <!-- <dialog> 等代码 -->
  </div>
</template>
    
<script>

const api = (params) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve({
        data: [{ name: Math.random() }, { name: Math.random() }],
        page: params.page,
        pageSize: 10,
        total: 100
      })
    }, 1000)
  })
}

export default {
  data() {
    return {
      tableData: [{ name: 1 }, { name: 2 }],
      pageSize: 10,
      total: 100,
      page: 1,
      loading: false,
      // 其他
    }
  },
  methods: {
    async getList() {
      try {
        this.loading = true
        const res = await api({
          page: this.page,
        })
        this.tableData = res.data
        this.total = res.total
        this.loading = false
      } catch (error) {
        this.loading = false
      }
    },
    handleSizeChange(row) {
      this.editRow = row;
      this.editVisible = true;
    },
    handleCurrentChange(page) {
      this.page = page
      this.getList()
    },
    // 其他
  }
}
</script>

封装table

上诉代码依然是一个文件当中,1000多行,我们可以将table+pagination 封装到一��组件当中

ui复用形

app

这是第一种封装方式,js部分全都不变,只对html进行封装

<template>
  <div id="app">
    <xxTable :loading="loading" :data="tableData" :total="total" :page="page" :pageSize="pageSize"
      @size-change="handleSizeChange" @current-change="handleCurrentChange">
      <el-table-column prop="name" label="Name" min-width="180" />
    </xxTable>
    <!-- <dialog> 等代码 -->
  </div>
</template>
<script>
import xxTable from './components/xxTable'

const api = (params) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve({
        data: [{ name: Math.random() }, { name: Math.random() }],
        page: params.page,
        pageSize: 10,
        total: 100
      })
    }, 1000)
  })
}

export default {
  components: {
    xxTable
  },
  data() {
    return {
      tableData: [{ name: 1 }, { name: 2 }],
      pageSize: 10,
      total: 100,
      page: 1,
      loading: false,
      // 其他
    }
  },
  methods: {
    async getList() {
      try {
        this.loading = true
        const res = await api({
          page: this.page,
        })
        this.tableData = res.data
        this.total = res.total
        this.loading = false
      } catch (error) {
        this.loading = false
      }
    },
    handleSizeChange(row) {
      this.editRow = row;
      this.editVisible = true;
    },
    handleCurrentChange(page) {
      this.page = page
      this.getList()
    },
    // 其他
  }
}
</script>

xxTable

这种封装,类似于functional,vue里的函数式/无状态组件,但本例中有$emit,无法使用函数式组件的方式

<template>
    <div>
        <el-table v-loading="loading" :data="data" style="width: 100%">
            <slot></slot>
        </el-table>
        <el-pagination 
        @size-change="$emit('size-change',$event)" 
        @current-change="$emit('current-change',$event)" 
        :page-size="10"
        layout="total, prev, pager, next" :total="100">
        </el-pagination>
    </div>
</template>
<script>
export default {
    props: ["loading","data","pageSize","total","page"]
}
</script>

总结

特点是js要写的内容一点都没少,还有一些变体

  • 不用slot

一种是slot的内容放到xxTable里面,类似如下操作

<template >
    <div>
        <el-table v-loading="loading" :data="data" style="width: 100%">
            <el-table-column prop="name" label="Name" min-width="180" />
        </el-table>
        <el-pagination 
        @size-change="$emit('size-change',$event)" 
        @current-change="$emit('current-change',$event)" 
        :page-size="pageSize"
        :total="total"
        layout="total, prev, pager, next" >
        </el-pagination>
    </div>
</template>
<script>
export default {
    props: ["loading","data","pageSize","total","page"]
}
</script>
  • 增加js内容

将html部分信息,转换为json/js,比如通过columns,重新设置column

template中的内容少了,但是js里的内容多了,js中的columns有他对应的场景[动态columns,此处暂略],就目前来讲,属于浪费

<template>
  <div id="app">
    <xxTable  :columns="columns" >
    </xxTable>
  </div>
</template>
<script>

export default {
  // ...
  computed: {
    columns() {
      return [{
        prop: 'name',
        label: 'name',
        minWidth: "180"
      }]
    }
  }
}
</script>

这种特点,属于ui组件

ui组件

注:此处开始扯概念了,主流前端没有统一,各有各的说法,一家之言,姑且听之

特点如下

  • 主要跟ui/pm沟通
  • 静态的,说什么状态,就有什么属性
  • 原子型的
  • 替代基础标签的
  • 复用性极强,难度不高
  • 没有业务价值
  • 存放在底层的components中或者二方库

比如element-ui就是这一类,我们这里使用的方式属于ui组件的嵌套,就是将两个ui组件合并成一个,将两个attrs和listeners放到同一个根节点下面,这种封装没有意义

业务复用型

不需要再slot中描述columns,在xxTable中会描述具体的columns,可能会涉及一些js的操作

app

会考虑减少调用组件时传递的参数,但是没有标准,同一个人,每次写的都不一定一样

<template>
  <div id="app">
    <xxTable :loading="loading" :data="tableData" :queryParams="queryParams" @getData="getData">
    </xxTable>
    <!-- <dialog> 等代码 -->
  </div>
</template>
<script>
import xxTable from './components/xxTable'

const api = (params) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve({
        data: [{ name: Math.random() }, { name: Math.random() }],
        page: params.page,
        pageSize: 10,
        total: 100
      })
    }, 1000)
  })
}

export default {
  components: {
    xxTable
  },
  data() {
    return {
      tableData: [{ name: 1 }, { name: 2 }],
      queryParams:{
        pageSize: 10,
        total: 100,
        page: 1,
      },
      loading: false,
      // 其他
    }
  },
  methods: {
    async getData() {
      try {
        this.loading = true
        const res = await api({
          page: this.queryParams.page,
        })
        this.tableData = res.data
        this.queryParams.total = res.total 
        this.loading = false
      } catch (error) {
        this.loading = false
      }
    },
    // 其他
  }
}
</script>

xxTable

会考虑合并data,减少事件,但会多出组合的属性或事件,比如queryParamsgetData

<template >
    <div>
        <el-table v-loading="loading" :data="data" style="width: 100%">
            <el-table-column prop="name" label="Name" min-width="180" />
        </el-table>
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
            :page-size="queryParams.pageSize" :total="queryParams.total" layout="total, prev, pager, next">
        </el-pagination>
    </div>
</template>
<script>
export default {
    props: ["loading", "data", "queryParams"],
    methods: {
        handleSizeChange(row) {

        },
        handleCurrentChange(page) {
            this.$emit("getData")
        }
    }
}
</script>

业务组件

具体如何实现与开发有关

  • 会考虑减少js中传递的属性和函数
  • 也可能会用到slot的性质
  • 包含自定义属性
  • 包含自定义事件
  • 存放在当前目录下的components中

千奇百怪,也是最不好建议的组件

  • 业务如此
  • 不需要考虑
  • 其他地方cv的

这种特点属于业务组件,涉及到业务内容,可能只有这个业务才会用到,封装梳理的意义大于复用的意义

流程复用型

比业务组件考虑的更多的业务组件,有一套的方法论

组件 + 模型 === 流程复用/动态组件/模块

app

模型相关的内容全在xxTable组件中,由他维护

但模型的定义需要在app中定义,这里将模型的定义简单的抽象为api

调用整个xxTable需要传递两部分,一部分是columns,一部分是如何如何获取数据,至此,所有的内容已传输完成,剩下的参数全在xxTable中维护

<template>
  <div id="app">
    <xxTable :api="api" height="500px">
      <el-table-column prop="name" label="Name" min-width="180" />
    </xxTable>
    <!-- <dialog> 等代码 -->
  </div>
</template>
<script>
import xxTable from './components/xxTable'

const api = (params) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve({
        data: [{ name: Math.random() }, { name: Math.random() }],
        page: params.page,
        pageSize: 10,
        total: 100
      })
    }, 1000)
  })
}

export default {
  components: {
    xxTable
  },
  methods: {
    api,
    // 其他
  }
}
</script>

xxTable

额外的参数全跟模型有关,前段模型比较重,这里将其简化为 三个参数

  • api

如何获取数据

  • queryParams

请求参数

  • autoLoad

是否自动初始化数据

<template >
    <div>
        <el-table v-loading="loading" :data="data" v-bind="$attrs" v-on="$listeners" >
            <el-table-column prop="name" label="Name" min-width="180" />
        </el-table>
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-size="pageSize"
            :total="total" layout="total, prev, pager, next">
        </el-pagination>
    </div>
</template>
<script>
export default {
    props: ["api", "queryParams", "autoLoad"],
    data() {
        return {
            data: [],
            // 本地存储的数据源
            queryParamsLocal: {
                pageSize: 10,
                total: 100,
                page: 1,
            },
            loading: false,
        }
    },
    beforeMount() {
        if (this.autoLoad !== false) {
            this.getData()
        }
    },
    computed: {
        // 用默认值或代理都可以
        // 针对传递的是json,但数据不全的情况下,需要特殊处理
        // 可抽取为一个utils
        pageSize: {
            get() {
                return this.queryParams?.pageSize || this.queryParamsLocal.pageSize
            },
            set(value) {
                Object.hasOwnProperty.call(this.queryParams || {}, "pageSize") ? this.queryParams.pageSize = value : this.queryParamsLocal.pageSize = value
            }
        },
        total: {
            get() {
                return this.queryParams?.total || this.queryParamsLocal.total
            },
            set(value) {
                Object.hasOwnProperty.call(this.queryParams || {}, "total") ? this.queryParams.total = value : this.queryParamsLocal.total = value
            }
        },
        page: {
            get() {
                return this.queryParams?.page || this.queryParamsLocal.page
            },
            set(value) {
                Object.hasOwnProperty.call(this.queryParams || {}, "page") ? this.queryParams.page = value : this.queryParamsLocal.page = value
            }
        }
    },
    methods: {
        handleSizeChange(row) {

        },
        handleCurrentChange(page) {
            this.page = page
            this.getData()
        },
        // 对外提供
        async getData() {
            try {
                this.loading = true
                const res = await this.api({
                    pageSize: this.pageSize,
                    total: this.total,
                    page: this.page
                })
                this.data = res.data

                this.loading = false
            } catch (error) {
                this.loading = false
            }

        }
    }
}
</script>

流程复用

  • 继承某个核心UI组件

比如table中,根节点的attrslisteners全部指向table,同理,也会涉及到方法对外暴露,上面的代码并没有处理

  • 前端模型 + ui组件
  • 动态状态
  • 实现后端/pm的需求
  • 只考虑布局,一般不考虑ui细节

注:代码只是其中一种实现,具体细节有很多都是不严谨的

转载自:https://juejin.cn/post/7372469848342904866
评论
请登录