Vue 3 风格指南解析

本文详细阐述了Vue3的风格指南,包括组件命名应使用多个单词,prop定义应指定类型以优化提示和维护,v-for指令需添加唯一key以提升性能,组件样式推荐使用scoped或CSSModules,以及组件拆分和命名规范等。强调了编码一致性、可读性和团队协作的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue 3 风格指南解析

其实好多人即便使用vue三五年了, 也没有看过vue风格指南

根据官方描述, 目前vue3的风格指南后续将继续完善, 以适用Composition API风格代码

规则概述

规则分为 A / B / C / D 四个优先级

  • A: 必要规则

    除非你精通Vue和JS, 否则请遵守

  • B: 强烈推荐

    不这样做容易造人嫌弃(手动狗头), 如果没有充分的理由不这样做, 请遵守

  • C: 推荐

    编码风格保持一致, 会让你更容易融入社区和团队开发中来

  • D: 谨慎使用

    基本上代码能跑, 但不太好(主要是性能)

A 级: 必要规则

  • 组件名使用多个单词

    可以避免和html元素冲突

    正例:

    <!-- 预编译模板 -->
    <TodoItem />
    <!-- dom模板 -->
    <todo-item></todo-item>
    

    反例:

    <!-- 预编译模板 -->
    <Item />
    <!-- dom模板 -->
    <item></item>
    

    注:

    1. 预编译模板和dom模板的区别: 可简单理解为一个在vue文件中, 一个在html文件中

    2. 个人推荐统一使用kebab-case写法, 所有地方适用, 且有些字母大小写不易区分

      如小写L, 大写的i, 和数字1, 费劲

  • 详细的prop定义

    至少应当指定类型, 可以更好的利用IDE的代码提示, vue的开发环境也会有警告提示. 而且有利于代码维护

    正例:

    props: {
      status: String
    }
    
    // 最好这样
    props: {
      status: {
        type: String,
        required: true,
    
        validator: value => {
          return [
            'syncing',
            'synced',
            'version-conflict',
            'error'
          ].includes(value)
        }
      }
    }
    
    // 见注1
    const props = defineProps({
      // 文本对齐方式
      align: String as PropType<'left' | 'center' | 'right'>
    })
    

    反例:

    // 能用, 但不太好, 容易让人头大
    props: ['status']
    

    注:

    1. 这条是我自己补充的, 我一般写validator, 让IDE来完成提示即可
    2. vue3支持类型声明定义props, 对此不做评价, 由于默认值用起来比较麻烦, 所以我目前很少使用
  • v-for添加key

    记得唯一, 如果没有手动生成一个, 不要使用下标作为key

    利于性能, 如果不提供的话, <transition-group>动画列表排序可能产生bug

    正例:

    <ul>
      <li v-for="user in activeUsers"
          :key="user.id" >
        {{ user.name }}
      </li>
    </ul>
    
    <ul>
      <template v-for="user in users" :key="user.id">
        <li v-if="user.isActive">
          {{ user.name }}
        </li>
      </template>
    </ul>
    

    反例:

    <ul>
      <li v-for="user in users" v-if="user.isActive" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
    

    还要注意一点: 反例中v-if与v-for混用, 更好的写法见 正例 中第二种用法

  • 组件样式作用域

    style标签尽量设置作用域, 推荐scoped, 当然也可以使用CSS Modules或其他约束(如BEM)

    正例:

    <template>
      <button class="button button-close">×</button>
    </template>
    
    <!-- 使用scoped属性 -->
    <style scoped>
    .button {
      border: none;
      border-radius: 2px;
    }
    
    .button-close {
      background-color: red;
    }
    </style>
    
    <template>
      <button :class="[$style.button, $style.buttonClose]">×</button>
    </template>
    
    <!-- 使用 CSS Modules -->
    <style module>
    .button {
      border: none;
      border-radius: 2px;
    }
    
    .buttonClose {
      background-color: red;
    }
    </style>
    
    <template>
      <button class="c-Button c-Button--close">×</button>
    </template>
    
    <!-- 使用 BEM 约定 -->
    <style>
    .c-Button {
      border: none;
      border-radius: 2px;
    }
    
    .c-Button--close {
      background-color: red;
    }
    </style>
    

B 级: 强烈推荐

  • 把组件拆分成文件

    每个组件都应该在自己的文件中, 有利于IDE提示以及维护

    正例:

    components/
    |- TodoList.js
    |- TodoItem.js
    
    components/
    |- TodoList.vue
    |- TodoItem.vue
    

    反例:

    app.component('TodoList', {
      // ...
    })
    
    app.component('TodoItem', {
      // ...
    })
    

    注: 我比较推荐文件名全部使用中划线(kebab-case)命名, 原因上面说过一次了, 大小写和数字在UI字体中更容易混淆

  • 单文件组件文件名大小写

    推荐PascalCasekebab-case

    PascalCase在IDE加持下没什么优势, 而且文档中补充, 有些文件系统是不区分大小写的(我没遇到过), 会产生问题

    就不贴示例了, 篇幅太长了

  • 基础组件名

    应当以特定前缀开头, 如Base / App / V

    基础组件定义: 仅包含HTML元素 / 其他基本组件 / 第三方UI组件, 且不包含Vuex或Pinia的状态管理, 由于基础组件使用频繁, 建议全局注册

    正例:

    components/
    |- BaseButton.vue
    |- BaseTable.vue
    |- BaseIcon.vue
    
    components/
    |- AppButton.vue
    |- AppTable.vue
    |- AppIcon.vue
    
    components/
    |- VButton.vue
    |- VTable.vue
    |- VIcon.vue
    

    反例:

    components/
    |- MyButton.vue
    |- VueTable.vue
    |- Icon.vue
    

    注:

    官方文档中举了一个组件批量注册的例子, 但vite不适用, 而且会造成WebStorm不能正确提示, 因此我使用此方案

    另外推荐一个前缀ly, 意为领域

  • 单例组件名称

    只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。

    这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。

    注: 并不苟同, 不抄文档

  • 紧密耦合的组件名称

    和父组件紧密耦合的子组件应该以父组件名作为前缀命名。

    如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。

    正例:

    components/
    |- TodoList.vue
    |- TodoListItem.vue
    |- TodoListItemButton.vue
    
    components/
    |- SearchSidebar.vue
    |- SearchSidebarNavigation.vue
    

    反例:

    components/
    |- TodoList.vue
    |- TodoItem.vue
    |- TodoButton.vue
    
    components/
    |- SearchSidebar.vue
    |- NavigationForSearchSidebar.vue
    
  • 组件名中的单词排序

    组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。

    有利于代码提示, btn前缀的组件如btn-search / btn-reset / btn-submit等按钮, 在众多文件中可以起到分类的作用, 而且在编码过程中通过btn前缀来过滤IDE的提示结果会很方便, 即便是忘掉search/reset/submit等后缀也能更快想起来, 避免思路被打断

    正例:

    components/
    |- SearchButtonClear.vue
    |- SearchButtonRun.vue
    |- SearchInputQuery.vue
    |- SearchInputExcludeGlob.vue
    |- SettingsCheckboxTerms.vue
    |- SettingsCheckboxLaunchOnStartup.vue
    

    反例:

    components/
    |- ClearSearchButton.vue
    |- ExcludeFromSearchInput.vue
    |- LaunchOnStartupCheckbox.vue
    |- RunSearchButton.vue
    |- SearchInput.vue
    |- TermsCheckbox.vue
    
  • 自闭合标签

    dom模板中不要使用自闭合标签, 而单文件模板, 预编译模板, jsx中推荐自闭合写法

    自闭合写法目的是简洁, 而不是强制

    正例:

    <!-- 单文件组件, 字符串模板, JSX -->
    <MyComponent/>
    
    <!-- dom模板 -->
    <my-component></my-component>
    
  • 模板中组件名大小写

    概要:

    单文件组件, 字符串模板, jsx中使用大驼峰(PascalCase), dom模板中使用中划线(kebab-case)

    但如果已经习惯了中划线了, 或者有项目约定, 则统一中划线命名即可

  • JS / JSX中组件名大小写

    JS/ JSX中的组件名称应该始终是 PascalCase

    但通过app.component注册组件时, 应当使用中划线

    我们不用jsx, 不好评价, 举例见文档

  • 组件名称避免缩写

    这个好说, 因为缩写并不能减少什么工作量, 反而会引起歧义

    但是对于上方举例中的btn这种通用的缩写我觉得没必要使用全拼

  • Prop命名大小写

    入乡随俗, 在js中使用驼峰

    在模板中使用中划线(当然如果有项目约定, 则可以统一使用小驼峰)

    正例:

    props: {
      greetingText: String
    }
    
    // SFC中, 两种均可, 但应当统一
    <WelcomeMessage greeting-text="hi"/>
    <WelcomeMessage greetingText="hi"/>
    
    // dom模板
    <welcome-message greeting-text="hi"></welcome-message>
    

    个人推荐SFC中组件名和prop和attribute全都小写

  • 多个 attribute 的元素

    每行一个, 易于阅读, 模板和jsx中均推荐

    正例:

    <MyComponent
      foo="a"
      bar="b"
      baz="c"
    />
    
    <img
      src="https://vuejs.org/images/logo.png"
      alt="Vue Logo"
    >
    

    反例:

    <img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
    <MyComponent foo="a" bar="b" baz="c"/>
    

    我意见是闭合标签不另起一行, 而且对齐方式不同(WebStorm默认对齐), 感觉这样更有层次感:

    <el-input v-model="model"
              clearable
              disabled/>
    
  • 不在模板中使用复杂表达式

    组件模板应该只包含简单的表达式,将更复杂的表达式重构为计算属性或方法。

    模板中的复杂表达式会降低它们的声明性。我们应该努力描述应该出现什么,而不是我们*如何计算该值。*计算属性和方法还允许重用代码。

    正例:

    <!-- 模板中使用 -->
    {{ normalizedFullName }}
    
    // 使用计算属性完成复杂表达式
    computed: {
      normalizedFullName() {
        return this.fullName.split(' ')
          .map(word => word[0].toUpperCase() + word.slice(1))
          .join(' ')
      }
    }
    

    反例:

    {{
      fullName.split(' ').map((word) => {
        return word[0].toUpperCase() + word.slice(1)
      }).join(' ')
    }}
    
  • 计算属性应当尽可能拆分

    好处:

    • 更容易测试
    • 更容易阅读
    • 更容易应对需求变更
    computed: {
      price() {
        const basePrice = this.manufactureCost / (1 - this.profitMargin)
        return (
          basePrice -
          basePrice * (this.discountPercent || 0)
        )
      }
    }
    
    computed: {
      basePrice() {
        return this.manufactureCost / (1 - this.profitMargin)
      },
    
      discount() {
        return this.basePrice * (this.discountPercent || 0)
      },
    
      finalPrice() {
        return this.basePrice - this.discount
      }
    }
    

    注: 应该是想表达避免多个computed使用相同的代码片段, 到时候需求变了都不知道要改哪些地方, 而且可能会扩大测试范围1

  • attribute值应当带引号

    非空 HTML attribute 值应该始终带引号 (单引号或双引号,以 JS 中未使用的为准)。

    在 HTML 中不带空格的 attribute 值是可以没有引号的,但这鼓励了大家在特征值里不写空格,导致可读性变差。

    正例:

    <input type="text">
    <AppSidebar :style="{ width: sidebarWidth + 'px' }">
    

    反例:

    <input type=text>
    <AppSidebar :style={width:sidebarWidth+'px'}>
    
  • 指令缩写

    要么都用, 要么都不用

    缩写如下:

    • v-bind: 缩写 :
    • v-on: 缩写 @
    • v-slot:缩写 #

    个人建议都用

C 级: 推荐

  • 组件 / 实例的选项的顺序

    组件/实例的选项应该有统一的顺序, 推荐顺序如下:

    太长了, 见文档吧, vue2文档也可作为参照, 变化不大

  • 元素attribute的顺序

    太长了, 见文档吧, vue2文档也可作为参照, 变化不大

  • 组件/实例选项中的空行

    太长了, 见文档吧, vue2文档也可作为参照, 变化不大

    个人并推荐不加空行, 仅<script setup>时推荐

  • 单文件组件顶层元素顺序

    <script><template><style>三者之间的顺序

    官方推荐<style>标签放最后, 剩下两个随意

    我习惯了vue2时写法template在前, 但官方demo一般script在前, 尝试适应中

D 级: 谨慎使用

  • scoped 中的元素选择器

    为了给样式设置作用域,Vue 会为元素添加一个独一无二的 attribute,例如 data-v-f3f3eg9。然后修改选择器,使得在匹配选择器的元素中,只有带这个 attribute 才会真正生效 (比如 button[data-v-f3f3eg9])。

    问题在于大量的元素和 attribute 组合的选择器 (比如 button[data-v-f3f3eg9]) 会比类和 attribute 组合的选择器 (比如 .btn-close[data-v-f3f3eg9]) 慢,所以应该尽可能选用类选择器。

    正例:

    <template>
      <button class="btn btn-close">×</button>
    </template>
    
    <style scoped>
    .btn-close {
      background-color: red;
    }
    </style>
    

    反例:

    <template>
      <button>×</button>
    </template>
    
    <style scoped>
    button {
      background-color: red;
    }
    </style>
    
  • 优先通过 prop 和 emits 进行父子组件之间的通信

    不要使用**this.$parent** 或修改props的值来达到通讯目的

    一个理想的 Vue 应用是 prop 向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下 prop 的变更或 this.$parent 能够简化两个深度耦合的组件。

    问题在于,这种做法在很多简单的场景下可能会更方便。但请当心,不要为了一时方便 (少写代码) 而牺牲数据流向的简洁性 (易于理解)

    正例:

    app.component('TodoItem', {
      props: {
        todo: {
          type: Object,
          required: true
        }
      },
    
      emits: ['input'],
    
      template: `
        <input
          :value="todo.text"
          @input="$emit('input', $event.target.value)"
        >
      `
    })
    
    app.component('TodoItem', {
      props: {
        todo: {
          type: Object,
          required: true
        }
      },
    
      emits: ['delete'],
    
      template: `
        <span>
          {{ todo.text }}
          <button @click="$emit('delete')">
            ×
          </button>
        </span>
      `
    })
    

    反例:

    app.component('TodoItem', {
      props: {
        todo: {
          type: Object,
          required: true
        }
      },
    
      template: '<input v-model="todo.text">'
    })
    
    app.component('TodoItem', {
      props: {
        todo: {
          type: Object,
          required: true
        }
      },
    
      methods: {
        removeTodo() {
          this.$parent.todos = this.$parent.todos.filter(
            (todo) => todo.id !== vm.todo.id
          )
        }
      },
    
      template: `
        <span>
          {{ todo.text }}
          <button @click="removeTodo">
            ×
          </button>
        </span>
      `
    })
    

再聊点

  1. 关于命名方式

    我推荐在(模板 / 文件名)中优先使用中划线命名, 因为除了jsx, 都能用, 而我不用jsx

    我认为统一的小写可读性更高一些, 就像后端写SQL, 关键字大写, 字段小写, 在我看来就显得有些杂乱. 包括我看大部分人的代码也都是中划线居多, 很少见到大驼峰的, 至于尤大为何优先推荐这种写法, 我也不清楚原因, 如有朋友知道的话欢迎补充

    当然好多朋友觉得vue组件是一个类, 文件名/组件名应当大写, 这个嘛, 看个人习惯好了, 我js中的类名是大驼峰, 但是文件名依然是中划线

    对于上面提到的容易混淆的字母数字, 其实一般影响也不大(Il1-iL1, 0oO, q9g-Q9G), 主要影响在协同时字体不同或字体功能时, 会稍有影响

  2. 下班了, 有空再补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值