Vue 之 More in js__前端__Vue.js
发布于 3 年前 作者 banyungong 1162 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

theme: fancy highlight: an-old-hope

More in js,纵享丝滑开发体验!

前言

在说 More in js 之前,想必大家对 ReactAll in js 都不陌生,各位看官莫被干扰,本文与 React 无关。More in js 为笔者开发过程中的一些实例尝试,可能大家都有过类似的开发经历,但少有人思考这么做的价值。

image.png

SFC 组件结构

讲到 Vue 的 SFC 组件结构,大家首先想到的一定是 template script style 三部曲。实际表现形式如下:

<template>
    ...
</template>

<script>
    ...
</script>

<style>
    ...
</style>

上面的代码就是 VueSFC 基础结构了,那这跟我们的 More in js 又有啥关系呢?

SFC 常见问题

我们通常在业务组件开发时,避免不了出现复杂布局情况,组件嵌套(一层又一层,层层不一样),此时再加上业务逻辑的处理,代码行数轻轻松松破 1000+

那么在维护或开发此类代码时,可能就是为了加一个属性导致我们在 templatescript 之间反复横跳,开发体验较差,也带来了额外的时间成本。(此时某位暴躁老哥已经开始砸键盘了!)

image.png

上图是笔者开发中的一个画布组件,其代码行数不算样式部分也突破了 2000+,对于正在修改业务逻辑的我突然需要给视图组件加个属性,直接促使肾上腺素飙升!

灵感来源

对于上述问题的解决方案是在业务组件封装时产生的灵感。

通常 UI 组件库的基础组件并不能满足我们复杂的业务需求时,会对齐进行二次封装,那么对于源组件的 propsevents 我们也是需要暴露给外界来降低上手成本,只针对该处的场景提供便利性,纵享丝滑体验。

例如:

<template>
  <Button
    class="async-button"
    ref="main"
    v-bind="mergeProps"
    :loading="loading"
    @click.prevent="onClick"
  >
    <slot></slot>
  </Button>
</template>

<script>
import { omit, merge } from 'lodash'

export default {
  name: 'AsyncButton',
  props: {
    /**
     * 使用原始 Button 事件
     * @default false
     */
    useRaw: { type: Boolean, default: false }
  },
  data() {
    return {
      loading: false
    }
  },
  computed: {
    mergeProps() {
      const defaultProps = { type: 'primary' }
      return omit(merge(defaultProps, this.$attrs), 'loading')
    }
  },
  methods: {
    /**
     * 处理点击事件。
     */
    onClick(e) {
      // useRaw: true 使用原始单击事件,false 使用异步单击事件
      if (this.useRaw) {
        this.$emit('click', e)
      } else {
        this.loading = true
        this.$emit('click', () => (this.loading = false), e)
      }
    }
  }
}
</script>

上面代码是对 Button 组件做的一层简单封装,让使用者能够在点击时自动启动加载状态,在需要时通过回调函数来关闭加载状态。

对于源 Button 组件的 props 通过 Vue 提供的 v-bind$attrs 实现属性穿透,那么反过来我们思考下是否可以把该方式应用到业务组件开发中?

答案是可以的。

More in js

提出的 More in js 概念就是为了让 Vue 开发者能够更好地专注于业务逻辑部分的处理,配合 IDE 的定义跳转功能,可以相对的优化上述问题。

<template>
  <SpecialTabs class="data-point-tabs" left-label="上传文件" right-label="已上传的文件列表">
    <!-- upload start -->
    <template slot="left">
      <SingleUpload ref="singleUpload" v-bind="singleUpload" />
    </template>
    <!-- upload end -->
    
    <!-- table start -->
    <template slot="right">
      <!-- table -->
      <Table class="file-table" v-bind="table" @hook:created="loadTableData" />

      <!-- modal start -->
      <Modal v-bind="modal" v-model="modal.show" transfer>
        <!-- modal footer -->
        <template slot="footer">
          <Button @click="toggleModal(false)">取消</Button>
          <Button v-bind="confirmBtn" @click="submitForm">确定</Button>
        </template>

        <!-- edit form -->
        <EditForm
          ref="editFormRef"
          :key="`edit-form_${updateFormFlag}`"
          :info="table.singleSelected"
          @on-validated="handleValidated"
          @on-submitted="handleSubmitted"
        ></PointForm>
      </Modal>
      <!-- modal end -->
    </template>
    <!-- table end -->
  </SpecialTabs>
</template>

<script>
import SpecialTabs from '@/components/SpecialTabs'
import SingleUpload from '@/components/SingleUpload'
import { dataPointColumns } from './entity/data-point'
import DeleteMixin from '@/mixins/delete'
import EditForm from './components/edit-form.vue'
import { showErrorMessage } from '@/util/error'

export default {
  name: 'DataPoint',
  mixins: [DeleteMixin],
  components: { SpecialTabs, SingleUpload, EditForm },
  data() {
    return {
      singleUpload: {
        autoUpload: true,
        loading: false,
        uploadRequest: this.uploadRequest,
        beforeUpload: this.handleBeforeUpload
      },
      table: {
        border: true,
        loading: false,
        disabledHover: true,
        columns: dataPointColumns.call(this),
        data: [],
        singleSelected: {},
        actionOptions: [...]
      },
      modal: {
        title: '配置表单',
        maskClosable: false,
        show: false
      },
      confirmBtn: {
        type: 'primary',
        loading: false
      },
      updateFormFlag: 0
    }
  },
  computed: {
    /**
     * DeleteMixin
     */
    deleteOption() {
      return {
        deleteUrl: '/db_table/delete',
        method: 'delete',
        loading: true,
        params: (selection) => ({ table_name: selection[0].table_name }),
        afterDelete: (flag) => flag && this.loadTableData()
      }
    }
  },
  methods: {
    ...
  }
}
</script>

上述代码是对 More in js 的一次尝试,组件的属性对象显示声明在 data 中,通过 v-bind 来动态绑定到模板上,能够提升代码的阅读性,让开发者更加专注业务逻辑层,对于视图层只关注在布局即可。

image.png

配合 IDE 的定义跳转功能,能够轻松实现对视图层组件的属性控制及添加,减少代码横跳次数,提升开发效率。

Vue3 更优体验

Vue2 中由于 options api 的结构,我们的组件开发通常在单文件中,不会对内部业务逻辑过于聚合,所以 More in js 方式只会解决单文件内部的代码横跳问题。

但是在 Vue3hooks api 中,我们可以通过抽离业务逻辑到 @use/xxx 后,让视图层与逻辑层的代码高度聚合,使用 More in js 可以更好的维护组件状态,不需要频繁的切换文件来更新属性信息。

// dp.vue
<template>
  <SpecialTabs class="data-point-tabs" left-label="上传文件" right-label="已上传的文件列表">
    <!-- upload start -->
    <template slot="left">
      <SingleUpload ref="singleUploadRef" v-bind="singleUploadAttrs" />
    </template>
    <!-- upload end -->
    
    <!-- table start -->
    <template slot="right">
      <!-- table -->
      <Table class="file-table" v-bind="tableAttrs" @hook:created="loadTableData" />

      <!-- modal start -->
      <Modal v-bind="modal" v-model="modal.show" transfer>
        <!-- modal footer -->
        <template slot="footer">
          <Button @click="toggleModal(false)">取消</Button>
          <Button v-bind="confirmBtn" @click="submitForm">确定</Button>
        </template>

        <!-- edit form -->
        <EditForm
          ref="editFormRef"
          :key="`edit-form_${updateFormFlag}`"
          :info="tableAttrs.singleSelected"
          @on-validated="handleValidated"
          @on-submitted="handleSubmitted"
        ></PointForm>
      </Modal>
      <!-- modal end -->
    </template>
    <!-- table end -->
  </SpecialTabs>
</template>

<script setup>
...

const { singleUploadAttrs, ... } = useUpload()
const { tableAttrs, ... } = useTable()
</script>


// @use/upload.js
...

export default function useUpload() {
    const singleUploadAttrs = reactive({
        autoUpload: true,
        loading: false,
        uploadRequest: uploadRequest,
        beforeUpload: handleBeforeUpload
    })
    
    function uploadRequest() {...}
    
    function handleBeforeUpload() {...}
    
    ...
    
    return { singleUploadAttrs, ... }
}


// @use/table.js
...

export default function useTable() {
    const tableAttrs = reactive({
        border: true,
        loading: false,
        disabledHover: true,
        columns: dataPointColumns(),
        data: [],
        singleSelected: {},
        actionsOptions: [...]
    })
    
    function loadTableData() {
        ...
        
        tableAttrs.data = [...]
    }
    
    ...
    
    return { tableAttrs, ... }
}

Volar 体验提升

随着 Vue3 的稳定,IDE 方面的插件也有了新的支持,官方维护的 Volar 脱颖而出,这里只重点说明 Volar 的编辑器拆分功能来让体验更加丝滑。

image.png

通过拆分编辑器,配合 More in js 方式,只需要关注左侧 hooks 代码即可让开发体验上升一个台阶。

总结

本次灵感主要解决开发时繁琐的代码横跳,从而找出更优的开发体验,但非最优体验,因人而异,请各位看官勿喷。有何问题,可评论区留言,谢谢!

相逢即是缘,挥一挥手指,留下一个赞吧!(^▽^)

image.png

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: iel 原文链接:https://juejin.im/post/6974283138910388261

回到顶部