# vue 单文件组件

## 基本原则

### 总体原则

1. 使用`setup`语法，`组合式 API`，而非`选项式 API`
2. 不要使用`mixins`，vue3中已不推荐使用，参考：[mixins](https://cn.vuejs.org/api/options-composition.html#mixins)，[app.mixin()](https://cn.vuejs.org/api/application.html#app-mixin)
3. 不要在 `updated` 钩子中更改组件的状态，这可能会导致无限的更新循环！
4. 慎用`provide`和`inject`。
5. 慎用`watch`，能在`数据变化`源头进行操作的，就不要用`watch`。
6. 适时进行`组件拆分`，组件中代码长度不宜超过`300`行（概数，`少于`或者`略超`都可接受），宽度不宜超过`3/4 屏幕宽度`，代码应适时`空格`、`换行`、`空行`。
7. 每次提交代码之前，应移除`console.log`等无用代码。

### 组件拆分原则

1. 通常`v-for`遍历的`每一项`，应拆分为`独立组件`。除非`场景极其简单`，或者`确定后续不再更改`。
2. 通常`v-if`对应的`每一项`，应拆分为`独立组件`。除非`场景极其简单`，或者`确定后续不再更改`。
3. 通常`modal`组件中的内容，应拆分为`独立组件`。
4. 通常`drawer`组件中的内容，应拆分为`独立组件`。
5. 通常`popover`组件中的内容，应拆分为`独立组件`。
6. 通常`表单`应拆分为`独立组件`。
7. `过于复杂`的组件应设法拆分为`多个独立组件`

### 模板-template

1. `v-if`和`v-for`不要用在同一个节点上。
2. `v-for`必须加独立的唯一的`key`，如果API不返回，客户端应设法构造一个`唯一的可逆的key`
3. 任意节点的`key`都不能用`随机数`
4. 不要在模板里写`函数`，例如：`<button @click="() => {status = 1}">按钮</button>`，这是糟糕的做法。
5. 不要在模板里写过多`js表达式`，请使用`计算属性`或者其他定义好的值。例如：绑定的样式对象直接在模板里书写，这是糟糕的做法。
6. 模板中的字符串使用`双引号`包裹，例如:`<div title="标题"></div>`
7. 不要使用`v-else`，当在模板中存在条件判断时，应写完整的`v-if`，例如：`<div v-if="open">开</div><div v-if="!open">关</div>`。使用`v-else`不利于后续扩展
8. 请使用缩写指令，`@`代替`v-on`，`:`代替`v-bind`，`#`代替`v-slot`
9. 当节点上有`多个属性`时，应对每一个属性进行`换行`。
10. 在模板中使用`自定义组件`时，应写成`大写驼峰`（参考下方组件命名），而非`小写中横线`。

### 脚本-script

1. `js`中的字符串使用`单引号`包裹，例如：`const user_name = ref('zhangsan')`
2. 代码不宜写成一坨，要进行合理的`空行`。通常做`相似事情的代码`可连续书写，当代码进行到下一类事情时，需`空行`。例如声明多个变量，可连续书写，当将要声明一个函数时，需`空行`后再进行函数声明。
3. `关键词`与`操作符`之间要进行合理的`空格`
4. `注释`要清晰，必要时添加`文档`进行说明。请参考[jsdoc](https://jsdoc.nodejs.cn/)进行注释。

### 样式-style

1. 在样式中不要直接使用`元素选择器`。
2. 在样式中不要使用 `deep`

## 命名规范

为了`统一风格`，特对于项目中的`命名`进行一些规定。下方给出了一个项目的`src`目录中关键文件的`命名示例`。

```
src/
  |- index.js
  |- App.vue
  |- assets/
    |- images/
      |- site-logo.png
      |- man.svg
      |- woman.svg
    |- audio/
      |- response-media.mp3
    |- video/
      |- movie.mp4
  |- store/
    |- index.js
    |- use-counter.js
  |- routes/
    |- index.js
  |- components/
    |- account/
      |- Login.vue
      |- Register.vue
    |- product/
      |- ProductCreateForm.vue
      |- ProductDetail.vue
    |- shop-online/
      |- ShopOnlineConfirmForm.vue
      |- ShopOnlineStatus.vue
  |- views/
    |- Index.vue
    |- About.vue
    |- Login.vue
    |- ProductDetail.vue
```

### 文件夹名称

`文件夹名称`一律`小写`，用`中横线`分隔。例如：`shop-online`

### 文件名称

除了`组件名`之外，其他类型的文件一律使用`小写英文字母`，使用`中横线`分隔。例如：`response-media.mp3`、`site-logo.png`

### 第三方组件名在模板中的使用

对于`第三方组件`，使用`小写`、`中横线`的方式进行书写。

```vue
<template>
  <a-space>
    <a-button type="primary">Primary</a-button>
    <a-button>Secondary</a-button>
    <a-button type="dashed">Dashed</a-button>
    <a-button type="outline">Outline</a-button>
    <a-button type="text">Text</a-button>
  </a-space>
</template>
```

### 自定义组件名称

`组件名`使用`驼峰`形式命名，例如：`Index.vue`、`ProductDetail.vue`。

### 项目内自定义组件

对于`项目内`的`自定义组件`，使用`大写`、`驼峰`方式进行书写。

```vue
<template>
  <ViewContainer>
    <ProductDetail :id="product_id"/>

    <PriceHistory />
  </ViewContainer>
</template>
```

### props命名

`props`命名使用`小写`、`下划线`形式命名。例如下方的`props.user_age`、`props.user_name`。

```vue
<script>
const props = defineProps({
  foo: String,
  user_age: Number,
  user_name: String
});
</script>
```

### emit命名

`emit`命名使用`小写`、`驼峰`形式，例如：`priceChange`,`messageFinished`。

> 无需加`on`前缀，例如：`onPriceChange`，这是错误做法。

```vue
<script>
const emit = defineEmits(['change', 'delete'])
</script>
```

### 变量命名

同`props`命名，使用`小写`、`下划线`形式。

```js
const my_name = ref('张三');
const user_age = 18;
const theme_color = 'dark';
```

### 常量命名

`常量`使用`大写`字母加`下划线`的形式命名。

```js
const PROJECT_TITLE = '小数智能'; // 项目名称

const EXPIRE_VALUE = 3600; // 过期时间
```

### 函数命名

`函数`名称同`emit`命名

```js
function handleClick(){

}

/**
 * 获取产品的ID
 * @params {string} id - 产品ID
 **/
function getProductInfo(id){

}
```

### 类命名

`类`使用`大写`、`驼峰`形式命名。

```js
class Human{

}

class Animals{

}

class ProductOrder{

}
```

## 关键顺序约定

### 单文件组件顺序

`单文件组件`中区块顺序按照`template`,`script`,`style`顺序进行书写。

```vue
<template>
  <div class="container">组件模板</div>
</template>

<script>
import { ref } from "vue";

const status = ref(1);
</script>

<style scoped>
.container {
  font-weight: bold;
}
</style>

<style>

</style>
```

### 模板中元素属性的顺序

在模板中为元素绑定属性，需要遵循一定的顺序，大致顺序如下：

1. `v-`开头的指令，`v-if`、`v-show`、`v-for`
2. `key`、`class`、`style`、`id`、`title`等
3. 自定义属性，例如： `:age="18"`、`:price="300"`
4. 事件 `@click`、`@input`、`@change` 等

```vue
<template>
  <div 
    v-if="is_show" 
    class="news-title"
    :class="{'disabled': disabled}"
    title="标题"
    :age="18"
    @click="handleTitleClick"
  >这是新闻的标题</div>
</template>
```

### `script`标签内的顺序

在`单文件组件`中，各项顺序大致如下：

1. `import`
2. `defineProps`
3. `defineEmits`
4. `ref`、`reactive`、`let`、`const`、`var`等
5. `computed`
6. `watch`
7. `事件处理`方法
8. `生命周期钩子`:`onCreated`、`onMounted`、`onUpdated`、`onBeforeUnmount`
9. `defineExpose`

### css属性规则的顺序

样式的定义应遵循一定的顺序。

建议遵循以下顺序：

1. 布局定位属性：display / position / float / clear / visibility / overflow
2. 自身属性：width / height / margin / padding / border / background
3. 文本属性：color / font / text-decoration / text-align / vertical-align / white- space / break-word
4. 其他属性（CSS3）：content / cursor / border-radius / box-shadow / text-shadow / background:linear-gradient …

> 参考：https://guide.aotu.io/docs/css/code.html#%E5%B1%9E%E6%80%A7%E4%B9%A6%E5%86%99%E9%A1%BA%E5%BA%8F

```css
.title{
  display: block;
  position: relative;
  float: left;
  width: 100px;
  height: 100px;
  margin: 0 10px;
  padding: 20px 0;
  font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
  color: #333;
  background: rgba(0,0,0,.5);
  -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
  -o-border-radius: 10px;
  -ms-border-radius: 10px;
  border-radius: 10px;
}
```
