Vue 规范
需要熟悉每一条,规范中绝大部分采用 eslint 和 prettier 配置对应规则强制统一。
简介
- 文中标记
vue 2
的是vue 2
需要注意的 - 文中标记
vue 3
的是vue 3
需要注意的 - 没有标记的是
vue 2
和vue 3
通用的
文件命名
参考上一页 文件命名规范
Vue 组件顶级标签顺序
顺序保持一致,且标签之间留有空行。
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style>
.app {
}
</style>
data 数据 vue 2
组件的 data
必须是一个函数,并且建议在此不使用箭头函数
props vue 2
小驼峰命名。内容尽量详细,至少有类型和默认值,顺序是type
default
对象和数组默认值不要使用 []
{}
应该使用函数 () => []
() => ({})
export default {
props: {
// 不推荐
'greeting-text': Object,
// 推荐
greetingText: { type: Object, default: () => ({}) }
}
}
props vue 3
setup
语法中建议的写法
const props = defineProps({
list: {
type: Array,
default: () => []
},
readonly: {
type: Boolean,
default: false
}
})
setup
ts
语法中建议的写法
常用
tsinterface Props { content?: string } const props = withDefaults(defineProps<Props>(), { content: '' })
或者为
tsconst props = defineProps({ modelValue: { type: Array as PropType<string[]>, default: () => [] }, valueFormat: { type: String, default: '' } })
为 v-for 设置键值 key
在组件上总是必须用 key
配合 v-for
避免 v-if 和 v-for 用在一起
永远不要把 v-if
和 v-for
同时用在同一个元素上。
避免 v-if 中使用不严格等号
v-if
中判断永远使用严格相等, 用===
代替==
, 用!==
代替!=
。
<!-- 不推荐 -->
<div v-if='isData == "data"'></div>
<!-- 推荐 -->
<div v-if='isData === "data"'></div>
模板中简单的表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
// 不推荐
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
// 推荐
// <!-- 在模板中 -->
{{ normalizedFullName }}
// 复杂表达式已经移入一个计算属性
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
简单的计算属性
应该把复杂计算属性分割为尽可能多的更简单的 property。
export default {
// 不推荐
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return basePrice - basePrice * (this.discountPercent || 0)
}
},
// 推荐
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
}
不可更改的计算属性
不要试图修改计算属性的值,除非有设置 setter
。
在 v-if/v-else-if/v-else 中使用 key
如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个 <div>
元素)。
<!-- 不推荐 -->
<div v-if="error">错误:{{ error }}</div>
<div v-else>{{ results }}</div>
<!-- 推荐 -->
<div v-if="error" key="search-status">错误:{{ error }}</div>
<div v-else key="search-results">{{ results }}</div>
为组件样式设置作用域
对于应用来说,顶级 App
组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。
<!-- 不推荐 -->
<template>
<button class="btn btn-close">X</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
<!-- 推荐 -->
<template>
<button class="button button-close">X</button>
</template>
<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
组件文件
只要有能够拼接文件的构建系统,就把每个组件单独分成文件,不要写成字符串模板的形式。
组件名为多个单词
组件名应该始终是多个单词的,根组件 App
以及 <transition>
、<component>
之类的 Vue
内置组件除外。
例如:TreeSelect
TableColumn
紧密耦合的组件命名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名
# 不推荐
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
# 推荐
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
自闭合组件
在单文件组件、字符串模板和 JSX
中没有内容的组件应该是自闭合的——但在 DOM 模板里永远不要这样做。
<!-- 不推荐 -->
<custom-input></custom-input>
<!-- 推荐 -->
<CustomInput />
组件名称
为每一个组件起一个独有的名称,名称采用PascalCase
命名。
export default {
name: 'AppTable'
}
指令缩写
- 用
:
表示v-bind
- 用
@
表示v-on
- 用
#
表示v-slot
组件属性顺序和分行规则
这是我们推荐的组件选项默认顺序。它们被划分为几大类,所以你也能知道从插件里添加的新 property
应该放到哪里。
分行规则:放在一行,重要内容较多时,可放置 2 ~ 3 行。
- 定义 (提供组件的选项)
is
- 列表渲染 (创建多个变化的相同元素)
v-for
- 条件渲染 (元素是否渲染/显示)
v-if
v-else-if
v-else
v-show
v-cloak
- 渲染方式 (改变元素的渲染方式)
v-pre
v-once
- 全局感知 (需要超越组件的知识)
id
- 唯一的
attribute
(需要唯一值的attribute
)
ref
key
- 唯一的
- 双向绑定 (把绑定和事件结合起来)
v-model
- 其它
attribute
(所有普通的绑定或未绑定的attribute
)
- 其它
- 事件 (组件事件监听器)
v-on
- 内容 (覆写元素的内容)
v-html
v-text
<!-- 不推荐 -->
<AppForm
@submitSuccess="handleSubmitSuccess"
footer-text-align="center"
:label-width="formLabelWidth"
:submit-loading="submitLoading"
:model="ruleForm"
@cancel="handleBack"
:rules="rules"
ref="appFormInstance"
:confirm-text="$t('app.submit')"
:has-footer="isCreate"
v-model="data"
/>
<!-- 推荐 -->
<AppForm
ref="appFormInstance"
v-model="data"
footer-text-align="center"
:label-width="formLabelWidth"
:submit-loading="submitLoading"
:model="ruleForm"
:rules="rules"
:confirm-text="$t('app.submit')"
:has-footer="isCreate"
@submitSuccess="handleSubmitSuccess"
@cancel="handleBack"
/>
组件/实例的选项的顺序 vue 2
export default {
// 1. 副作用 (触发组件外的影响)
el: '#app',
// 2.全局感知 (要求组件以外的知识)
name: 'App',
parent: '', // 指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。
// 3. 组件类型 (更改组件的类型)
functional: false,
// 4.模板修改器 (改变模板的编译方式)
delimiters: ['{{', '}}'], // 改变纯文本插入分隔符。
comments: false, // 当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
// 5.模板依赖 (模板内使用的资源)
components: {},
directives: {},
filters: {},
// 6.组合 (向选项里合并 property)
extends: {},
mixins: [],
// 7.接口 (组件的接口)
inheritAttrs: true,
model: {},
provide: {}, // inject
props: {}, // 或者 propsData: {}
// 8.本地状态 (本地的响应式 property)
data() {},
computed() {},
// 9.事件 (通过响应式事件触发的回调)
watch: {},
// 生命周期钩子 (按照它们被调用的顺序)
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
activated() {},
deactivated() {},
beforeDestroy() {},
destroyed() {},
// 10. 非响应式的 property (不依赖响应系统的实例 property)
methods: {},
// 11.渲染 (组件输出的声明式描述)
template: '', // 或者 render(h, err) {}
renderError(h, err) {}
}
组件/实例的顺序 vue 3
setup
ts
语法中建议的写法 (非 ts 项目忽略掉 1)
// 1. ts 类型
interface Props {
content: string
}
interface Emits {
(event: 'select', data: string): string
}
interface State {
msg: string
}
// 2. 遗留的选项 (vue3.3已经官方支持 不再需要第三方插件unplugin-vue-define-options)
defineOptions({
// 使用第三方插件unplugin-vue-define-options (vue3.2.x)
name: 'AppRule',
inheritAttrs: true
})
// 3. inject
const mode = inject(ProvideKeys.mode) as Ref<string>
// 4. props
const props = withDefaults(defineProps<Props>(), {
content: ''
})
// 5. emit
const emit = defineEmits<Emits>()
// 6. ref reactive
const appTable = ref()
const state = reactive<State>({
msg: ''
})
// 7. hooks
const router = useRouter()
// 8. computed
const hasMsg = computed(() => !!state.msg)
// 9. watch
watchEffect()
watchPostEffect()
watchSyncEffect()
watch()
// 10. 生命周期
onServerPrefetch()
onBeforeMount()
onMounted()
onBeforeUpdate()
onUpdated()
onActivated()
onDeactivated()
onBeforeUnmount()
onUnmounted()
onRenderTracked()
onRenderTriggered()
onErrorCaptured()
// 11. 方法
const getList = async () => {}
import 引入顺序
同等类型的放一起,优先级如下
- 优先放第三方的模块 如
vue
lodash
- 优先放第三方的模块 如
- 然后放
@/
下的components
,hooks
,utils
等模块。
- 然后放
- 最后放
./
或者../
下的模块。
- 最后放
// 不推荐
import Search, { DefaultQuery } from './search.vue'
import { computed, reactive, ref, nextTick, watch } from 'vue'
import { useTable, useSetTableHeight, useBase } from '@/hooks'
import AppTable, { defaultPageInfo } from '@/components/Table'
import type { SearchData } from './search.vue'
import type { TableConfigRaw, AppTableInstance } from '@/components/Table'
import { ElMessage } from 'element-plus'
// 推荐
import { computed, reactive, ref, nextTick, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { useTable, useSetTableHeight, useBase } from '@/hooks'
import type { TableConfigRaw, AppTableInstance } from '@/components/Table'
import AppTable, { defaultPageInfo } from '@/components/Table'
import type { SearchData } from './search.vue'
import Search, { DefaultQuery } from './search.vue'
全局方法和属性 vue 2
尽量少的在 Vue 原型上添加全局方法和属性
// 不推荐
Vue.prototype.$http = axios
Vue.prototype.$utils = utils
Vue.prototype.$filter = filter
全局方法和属性 vue 3
尽量少的在 Vue 原型上添加全局方法和属性
// 不推荐
const app = createApp(App)
app.config.globalProperties.$http = axios
app.config.globalProperties.$utils = utils
app.config.globalProperties.$filter = filter
推荐 Vue-Router 写法
路由全部采用懒加载的方式导入(除了登录布局等需要网站一打开就需要加载的页面), 保证代码分割和高效加载。
// 不推荐
import RoleIndex from '@/views/user-permission/role/index.vue'
import CreateRole from '@/views/user-permission/role/create.vue'
const asyncRouter = [
{
path: '/user/permission/role',
name: 'RoleIndex',
component: RoleIndex
},
{
path: '/user/permission/role/create',
name: 'CreateRole',
component: CreateRole
}
]
// 推荐
const asyncRouter = [
{
path: '/user/permission/role',
name: 'RoleIndex',
component: () => import('@/views/user-permission/role/index.vue')
},
{
path: '/user/permission/role/create',
name: 'CreateRole',
component: () => import('@/views/user-permission/role/create.vue')
}
]