组件开发规范

2,353 阅读3分钟

分支规则

主分支: master

开发分支: develop

功能开发以 `feature/功能名` 命名

组件开发以 `components/组件名` 命名

修复 bug 以 `bugfix/bug(bugId或简短名)` 命名

紧急修复以 `hotfix/bug` 命名

文档开发以 `docs/功能名` 命名

命名规则

  • 统一命名规则

    • 动态组件 统一使用 `tag` 属性来指定生成的标签类型

    • 状态对应属性应该为直接的状态名

      如: `loading` 状态对应属性名应该为 `loading`、`disabled` 状态对应属性名应该为 `disabled`

    • 分子以上组件,内部出现多个组件有相同状态则以 [组件名][状态名] 小驼峰形式对外暴露

    • 内部组件对外暴露 `class` 属性以 `[组件名][Class]` 小驼峰形式命名

  • `script`、`js` 中驼峰命名,文档、文件夹、模板中以 - 连接

  • 数组类型以复数形式命名

    例如: `options` 对应为 `array` 类型, `option` 对应为 `object` 类型

  • 内部组件命名也应该符合命名规范

  • 内部组件属性透传命名建议以 `[组件名]Props` 名称命名

    例如: 导航组件使用 `logoProps` 属性对 `logo` 标签进行属性传递

  • 内部响应事件函数应该以 `handle[EventName]` 命名, `update` 事件以 `update[PropName]` 命名,如果内部有多个相同的事件名,则以 `handle[Element/ComponentName][EventnName]` 命名

  • 子组件有更改属性需求时,对外发送 `update:PropName` 事件,并且将新值当做第一个参数

    代码块
    Vue.js Component
    <template>
      <div></div>
    </template>
    <script>
    export default {
      props: {
        visible: Boolean,
      },
      methods: {
        toggle () {
          this.$emit('update:visible', !this.visible);
        }
      }
    }
    </script>

  • slot 命名应该明确表明该 slot 对应元素的意义

    例如: <slot name='loading' /> 表明此插槽用来显示自定义的 loading 元素

代码块
Vue.js Component
// only an demo 
<script>
export default {
  name: 'MtdButton',
  props: {
    // logo 地址
    logo: String, 
    // [组件/模块名]Props
    logoProps: Object, 
    // [组件/模块名]Class
    logoClass: String, 
    // 数组类型命名复数
    options: Array, 
    option: Object, 
  },
  methods: {
    // handle[ComponentName][EventName]
    handleInputClick () {
    }
  }
}
</script>

Props

  • 样式相关枚举类型属性不做强校验、功能相关强校验

    用户可能自己定义了其他样式并将改属性传入,所以对样式方面的枚举类型不做强校验,但是功能方面需要内部的支持,传入一个不支持的对于用户来说没有任何意义。

    代码块
    Vue.js Component
    // tooltip component 
    <script>
    export default {
      name: 'MtdTooltip',
      props: {
        // 此属性用于生成对应的 class,内部样式只支持 dark、 light,但是不能排除用户提供了其他样式的可能,所以此处不校验
        theme: String, 
        // 此属性用于 tooltip 的显示触发方式,由于内部只实现了 hover,click 所以对于用户来说传递一个其他方式并没有意义
        trigger: { 
          type: String,
          validator: function (v) { 
            return ['hover', 'click'].includes(v);
          }
        }
        // ...other
      }
      // ...
    }
    </script>
  • 表单需要支持 `v-model`, 使用 .sync 修饰符表达更新 props 意图

    代码块
    Vue.js Component
    // switch component 
    <script>
    export default {
      name: 'MtdSwitch',
      model: {
        prop: 'actived',
      },
      props: {
        actived: Boolean,
      },
      methods: {
        handleClick () {
          this.$emit('input', this.actived);
        }
      }
    }
    </script>
  • 上层组件需要支持内部组件属性透传,特别是要提供内部组件 `class` 传递

    有些时候需要对内部进行样式的覆盖,如果不提供 `class` 支持,外部只能通过选择器的优先级完成.

    代码块
    Vue.js Component
    // 分页组件分为 Pagination, Pager
    // Pagination component 
    <template>
      <ss-pager v-bind="$attrs" :class="pagerClass"></ss-pager>
      // or
      <ss-pager v-bind="pagerProps"></ss-pager>
      // ...
    </template>
    <script>
    export default {
      name: 'MtdPagination',
      props: {
        pagerClass: string,
      }
    }
    </script>
  • 组件作用对应某一个原生标签时,需要使用 `v-bind="$attrs"` ,对应的也可以使用 `v-on="$lisenters"`

    这样做可以不用显示的定义全原生标签属性,而且当原生属性扩展时不需要更变

    代码块
    Vue.js Component
    // input component
    <template>
      // ...
      <input v-bind="$attrs"  />
    </template>
  • 组件避免出现不同属性控制相同功能、样式

    代码块
    Vue.js Component
    // pager component
    // 不推荐
    <script>
    export default {
      props: {
        total: Number,
        pageCount: Number,
        pageSize: Number,
      }
    }
    </script>
    // 推荐
    <script>
    export default {
      props: {
        total: Number,
        pageSize: Number,
      }
      computed: {
        pageCount () {
          return Math.ceil(total/pageSize) || 0;
        }
      }
    }
    </script>

Data / State

  • 组件无状态,控制权交与使用者

    代码块
    Vue.js Component
    // switch component
    <script>
    export default {
      props: {
        actived: Boolean,
      },
      methods: {
        handleClick () {
          this.$emit('input', !this.actived); // 此时如果外部没有改变 actived 属性的值,显示依然会是原来的状态
        }
      }
    }
    </script>
    • 优先使用计算方式得出当前所需内部属性 (vue 中的 computed)

      例如: tabs 组件中,tab 组件会有 active 的状态,active 的状态应该由计算属性得出来,而不是通过 watch 来改变内部变量

      代码块
      JavaScript
      // better 
      get active () {     
        return tabsValue === this.value 
      } 
      // not 
      watch {     
        tabsValue (n) {         
          this.active = n === this.value     
        } 
      } 
    • 避免出现内部属性的使用 (vue 的 data 函数, react 的 state)

Event

  • 在父子组件通信、属性方法定义时,当不需要方法的返回值时,原则上都应该使用事件的方式 ( Vue )

    代码块
    JavaScript
    // 不推荐
    export default {
      name: 'MtdInput',
      props: {
        onChange: Function,
      },
      methods: {
        handleInput (v) {
          this.onChange(v);
        }
      }
    }
    // 推荐
    export default {
      name: 'MtdInput',
      methods: {
        handleInput (v) {
          this.$emit('input', v);
        }
      }
    }
  • 组件应该支持常用的原生事件,原生事件第一个参数应该是 event 对象

    代码块
    Vue.js Component
    <template>
    <div>
      <input v-bind="$attrs" v-on="$lisenters" :value="value" @input="handleInput" />  
    </div>
    </template>
    <script>
    export default {
      name: 'MtdInput',
      props: {
        value: [String, Number]
      },
      // ...other
    }
    </script>
  • 原生类型事件,其行为应该同原生事件 例如: compositionstart、compositionend 事件,应该表现同原生,不应该对外发送 change 类型事件

  • change、update 类型事件第一个参数是 新值,第二个参数是 旧值

    代码块
    Vue.js Component
    <template>
    <div></div>
    </template>
    <script>
    export default {
      props: {
        visible: Boolean,
      },
      methods: {
        handleClose () {
          this.$emit('update:visible', false, this.visible);
        }
      }
    }
    </script>

  • 事件、方法参数应该避免超过3个,且越常用的参数应该越靠前

  • 方法中最后一个参数不应该是 bool 类型,应该将所有 bool 类型参数改为 object

    代码块
    JavaScript
    // 不推荐: 
    function doSomthing (param, replace /* bool */) 
    // 推荐: 
    function doSomthing (param, { replace /* bool */ }) 

组件通信

  • 统一使用 事件 方式向父级通信,父级通过更改 prop 做出回应

  • 避免使用 refs

  • 避免使用 $parent

    Vue.js 支持组件嵌套,并且子组件可访问父组件的上下文。访问组件之外的上下文违反了基于模块开发第一原则。因此你应该尽量避免使用 this.$parent

其他

  • 组件内部不要出现魔数,如果确实有需求需要使用,必须添加注释,描述清楚数值的作用及来源,如果可能更改(该值可能出现自定义的需求),则将其作为属性,默认值为当前值

  • 样式相关需求优先考虑 `css` 或 `scss` 变量方式,如果 `css`、`scss` 实现不了,则需要在 `js` 实现时添加注释说明原因

    将样式与 `js` 分离,方便用户做样式覆盖,如果写在了 `js` 中则很大概率会使用 `style` 方式来生成样式,对于用于自定义样式来说非常困难,而且,各个页面、组件之间样式很可能有一定的关联性,一部分在 `css` 中,一部分在 `js` 中不利于管理