专栏名称: 奇舞精选
《奇舞精选》是由奇舞团维护的前端技术公众号。除周五外,每天向大家推荐一篇前端相关技术文章,每周五向大家推送汇总周刊内容。
目录
相关文章推荐
生信宝典  ·  2025 年2月 | ... ·  昨天  
生物学霸  ·  打破校史,「双一流」首篇 Nature ·  昨天  
生物学霸  ·  蒲慕明院士:物理学出身的神经科学家 ·  2 天前  
BioArt  ·  Nat Immunol | ... ·  3 天前  
BioArt  ·  Nature | ... ·  4 天前  
51好读  ›  专栏  ›  奇舞精选

浅读Vue3代码10万行,总结出30个代码规范

奇舞精选  · 公众号  ·  · 2024-10-11 18:00

正文

不管是Vue.js源码,还是UI组件库Element-plus,只要有多人协同开发,代码规范上多多少少都会有一些"百花齐放"。即使像Vue.js源码,不同开发者在function、enum、variable命名上也会附带个人风格。

Vue.js部分枚举定义, 三个大牛,三种不同的风格。

// Type结尾enum TargetType {  INVALID = 0,  COMMON = 1,  COLLECTION = 2,}// Types结尾enum OptionTypes {  PROPS = 'Props',  DATA = 'Data',  COMPUTED = 'Computed',  METHODS = 'Methods',  INJECT = 'Inject',}
// 枚举项使用pascalCaseenum BooleanFlags { shouldCast, shouldCastTrue,}

通过对Vue、Element-plus源码浅读分析,罗列出30个代码规范建议,前半部分和Vue相关,后半部分和Javascript相关。如果表述有误,欢迎指正。需要说明的是: 代码规范没有标准答案,只有适合、不适合。其目的是让项目代码保持风格统一, 提升易读性, 降低上手成本。

代码规范往期介绍:

  • 浅读Vue3代码10万行,总结出30个代码规范
  • 200+收藏的Vue3规范,如何配置eslint、prettier、editorconfig
  • Vue3黑神话:悟空版 eslint: eslint-plugin-wukong
  • 这样的Git规范,Leader看了都说好!

目录规范

模块命名

采用kebab-case命名方式,多个noun单词用短横线"-"分割,一般不超过2个单词,命名采用 模块-子模块 方式,这样module会按模块级别归类排序。

如vue的编译器compiler分core、dom、ssr、sfc模块,因此命名为 compiler-core ,将根模块放在第一个单词。

//BADcompilerCorecompilerDomcompilerSSR
// GOODvuevue-compat
compiler-corecompiler-domcompiler-ssrcompiler-sfc
runtime-coreruntime-domruntime-test

文件夹命名

文件夹命名需要特别注意,windows系统对大小写不敏感,如components和Components在windows系统下会认为是同一个,因此把名字由Components改为components,git是识别不到差异。由于文件夹大小写重命名导致在linux环境编译报错的问题屡见不鲜。

和module类似,采用kebab-case规则,以noun单词命名。如果为同类功能的文件夹,则一般以复数形式结尾,常用的有 components utils 等等。

layoutscomponentsdirectiveshooksutilspatchesscriptstransformsservices

组件文件夹命名

一个复杂组件通常会拆分为多个文件夹,每个文件夹以kebab-case方式命名。

dropdowndropdown-itemdropdown-menu
checkboxcheckbox-buttoncheckbox-group

assets目录

assets存放静态资源,images, styles, icons、svgs等静态目录以复数形式结尾,静态资源文件以kebab-case形式命名。

- assets  - images  - icons  - styles  - svgs    - ant-design-vue.svg

组件规范

组件结构化

组件编写大部分事件聚焦逻辑,因此将script放在顶层,文件结构可按sript、template、style顺序布局。

.........

script可以按props、emits、ref、 computed、watch、methods、events顺序排列代码。为你而是每个文件顺序统一,可以使用vscode定义代码片段,按以上顺序添加注释,开发组件直接在对应注释下添加对应代码。






    
/** imports  */import { computed, ref, useSlots } from 'vue'import { ElIcon } from '@element-plus/components/icon'import { TypeComponents, TypeComponentsMap } from '@element-plus/utils'import { useNamespace } from '@element-plus/hooks'import { alertEmits, alertProps } from './alert'
/** props */const props = defineProps(alertProps)
/** emits */const emit = defineEmits(alertEmits)
/** refs */const visible = ref(true)
/** computed */const iconComponent = computed(() => TypeComponentsMap[props.type])const iconClass = computed(() => [ ns.e('icon'), { [ns.is('big')]: !!props.description || !!slots.default },])const withDescription = computed(() => { return { 'with-description': props.description || slots.default }})
/** methods */const close = (evt: MouseEvent) => { visible.value = false emit('close', evt)}

组件中单引号、双引号

html中、vue的template中标签属性使用 双引号

      :is="tag"    ref="_ref"    v-bind="_props"    :class="buttonKls"    :style="buttonStyle"    @click="handleClick"  >

所有js中的字符串使用 单引号

  ns.is('disabled', _disabled.value),  ns.is('loading', props.loading),  ns.is('plain', props.plain),  ns.is('round', props.round),

所有js中的代码行换行,要么统一使用分号";",要么统一不使用分号,不能混着用。

// BADimport { buttonProps } from './button';import type { ExtractPropTypes } from 'vue'
// GOODimport { buttonProps } from './button'import type { ExtractPropTypes } from 'vue'

组件名命名规范

采用kebab-case,一个项目必须保持统一,组件名一般不超过2 到 3 个单词。

checkbox-button.vuecheckbox-group.vue

组件以高优单词开头

组件命名以高优先单词开头,以描述性单词结尾,重要单词放前面可实现有序排列。例如查询组件,使用Search前缀,输入组件命名为SearchInputXXX,而按钮组件使用SearchButtonXXX。

components/|- search-button.vue|- search-button-clear.vue|- search-input.vue|- search-input-query.vue|- settings-checkbox.vue|- settings-checkbox-terms.vue

父、子组件命名

和父组件紧密相关的子组件应该以父组件名作为前缀命名,例如:

components|- todo-list.vue|- todo-list-item.vue|- todo-list-item-button.vue

组件名应使用完整单词

组件命名不能使用缩写,而应该使用完整单词组成的名称。因为时间一长,或者换个人,可能完全看不出SdSettings组件为何意。由于文件引入一般都有智能提示,因此也不容易出现拼写类错误。

BADcomponents/|- SdSettings.vue|- UProfOpts.vue
GOODcomponents/|- student-dashboard-settings.vue|- user-profile-options.vue

组件Props命名

组件props定义使用lowerCamelCase命名,在template中使用组件时,使用kebab-case规则。例如定义greetingText属性,模板使用方式为 greeting-text=""

// BAD// componentconst props = defineProps({  'greeting-text': String})// for in-DOM templates
// GOODcomponentconst props = defineProps({ greetingText: String})

组件事件命名规则

事件命名需要注意定义和template使用。

事件定义:通常使用一个verb定义的事件名居多,例如open、close、click、change、focus、blur、select。对于状态类事件定义一般采用noun + "-" + verb形式,例如state-change、active-change。生命周期类通常采用prep + verb形式表示事件,例如before-enter、after-enter。或者是标识动作的事件,采用verb + "-" + noun。

// verbdefineEmits(['open'])defineEmits(['close'])




    
defineEmits(['focus'])
// noun + "-" + verbdefineEmits(['state-change'])
// verb + "-" + noundefineEmits(['open-menu'])
// 生命周期'before-enter''before-leave''after-enter''after-leave'

事件定义推荐使用对象的形式,而不是简写。对象形式能够直观看到每一个事件入参和返回值类型。

export const cascaderEmits = {  focus: (evt: FocusEvent) => evt instanceof FocusEvent,  blur: (evt: FocusEvent) => evt instanceof FocusEvent,  clear: () => true,  visibleChange: (val: boolean) => isBoolean(val),  expandChange: (val: CascaderValue) => !!val,  removeTag: (val: CascaderNode['valueByOption']) => !!val,}
const emit = defineEmits(cascaderEmits)

// template中使用组件时,注册事件使用kebab-case形式。

  @size-change="handleSizeChange"  @current-change="handleCurrentChange"/>

template中组件属性设置单独占用一行

在使用属性时,每个属性独占一行可提升代码的可读性。

// BAD
// GOOD foo="a" bar="b" baz="c"/>

组件模板应仅包含简单表达式,复杂的应提取到computed中

在template中避免使用复杂的表达式,包含计算逻辑的值应提取到computed。

// BAD 
height: '30rem', width: '100%', transition: '.3s ease-out all', transform: `rotateX(${parallax.roll}deg) rotateY(${parallax.tilt}deg)`, }">

// GOOD
const cardStyle = computed(() => ({ height: '30rem', width: '100%', transition: '.3s ease-out all', transform: `rotateX(${parallax.roll}deg) rotateY(${parallax.tilt}deg)`,}))

复杂的computed应提取为多个简单计算

包含多个值计算的computed应当通过拆分,简化逻辑。

// BADconst price = computed(() => {  const basePrice = manufactureCost.value / (1 - profitMargin.value)  return basePrice - basePrice * (discountPercent.value || 0)})
// GOODconst basePrice = computed( () => manufactureCost.value / (1 - profitMargin.value))
const discount = computed( () => basePrice.value * (discountPercent.value || 0))
const finalPrice = computed(() => basePrice.value - discount.value)

属性赋值规则

template中属性值使用引号(equoted)包裹。如果值为匿名对象则需要增加空格,保证值可读性。

// BAD
// GOOD

directive指令统一使用简写,不能简写、全称混用。

directive统一使用简写:: for v-bind:@ for v-on: and # for v-slot.

// BAD// v-bind全称和":"混用  v-bind:value="newTodoText"  :placeholder="newTodoInstructions">// v-on和@混用
// v-slot和#混用
// GOOD

Here might be a page title

Here's some contact info

组件属性定义添加校验

在定义组件属性时,如果属性值是必填、类型明确,则可添加type、required、validator限定值的有效性。

// BADconst props = defineProps({ status: String })
// GOODconst props = defineProps({ status: { type: String, required: true, validator: (value) => { return ['created','loading', 'loaded'].includes( value ) } }})

Javascript 规范

boolean类型变量命名

boolean变量、属性命名可添加is、has前缀,可以使用

  • is + Noun、
  • is + Adjective
  • is + Adjective + Noun
  • is + Noun + Adjective
  • has + X

按统一前缀规范,代码中只要看到is、has前缀的,则可初步判断为boolean类型。

isString // is + NounisStatic // is + NounisSlot // is + NounisMemberExpressionBrowser // is + Noun, 为表达清楚变量意义,可多个名词链接isSimpleIdentifier // is + Adjective + NounisCompatEnabled // is + AdjectiveisVBindisVOnisFromSetupisUsedInTemplate
hasFallback // has + NounhasVnodeHook // has + NounhasStyleBinding // has + NounhasDynamicKeys // has + NounhasText // has + NounhashPrefix // has + NounhasCommas // has + NounhasName // has + NounhasAttrsChanged // has + Noun + AdjectivehasCloned // has + Adjective

boolean类型参数命名

函数、方法、构造函数的参数,如果为boolean类型,命名:

  • is + Noun
  • is + Adjective
  • adjective
  • adv
  • verb + Noun
  • allow + Verb

不管使用那种方式,前提是直观上能推断是boolean类型。

isLocal // is + NounisReference is + Nouninline // advinheritAttrs // verb + Noun.optimized // adjectivechecked // adjectiveallowRecurse // allow + Verbforce?: boolean // verbsync?: boolean // noun
function genModulePreamble( genScopeId: boolean, inline?: boolean,) {}
export function findProp( dynamicOnly: boolean = false, allowEmpty: boolean = false,) {}
export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate // adjective deep?: boolean // adjective once?: boolean // adv}

boolean类型方法命名

boolean类型方法命名,一般以is、has、should、include、exclude、check等为前缀, 以Exists、Enabled、Equal等为后缀。

// is前缀isInDestructureAssignment(...)isInNewExpression(...)isFragmentTemplate(...)isTagStartChar(...)isSameKey(...)// has前缀hasScopeRef(...)hasForwardedSlots(...)hasPropsChanged(...) // 表示状态变化的hasMultipleChildren(...)hasExplicitCallback(...)hasCSSTransform(...)shouldSkipAttr(...)shouldReloadHmr(...)includeBooleanAttr(...)checkCompatEnabled(...)// Exists、Equal后缀fileExists(...)looseEqual(...)

Function:业务方法命名

使用lowerCamelCase小驼峰,以verb作为前缀,少数情况以描述性noun作为前缀。常用动词前缀:

  • create、init、gen、walk、to、process、extract、unwrap、patch。
createObjectMatcher(obj: Record) {}createElementWithCodegen(...)genFlagText(...)parseWithForTransform(...)createConditionalExpression(...)convertToBlock(...)walkFunctionParams(...) // work + NounwalkBlockDeclarations(...) // 遍历Block定义extractIdentifiers(...)unwrapTSNode(...)patchEvent(...)patchDOMProp(...)patchStyle(...)// 描述性noun作为前缀ssrRender(_ctx, _push, _parent, _attrs)

Function:事件方法命名

注册事件的回调方法,经常纠结前缀要不要加"on",后缀要不要加"ed",错误示范:onChanged。

如果是交互类事件的函数定义,通常采用on + Verb + Noun?形式,如onClick、onConfirm、onClose、onChange等等。

Delete Item// on + Verb + Noun

如果是逻辑处理类事件,通常采用handle+Verb,handle + Noun + Verb,例如:

     
v-model="value" :options="options" :props="props" @change="handleChange"/>

如果是处理单一业务逻辑,则可直接调用function,不需要专门定义事件方法,例如:


如果是更改单一状态,则可直接在template赋值,例如:







请到「今天看啥」查看全文