LMX
BlogSelectedWorkSponsorsAbout
Command Palette

Search for a command to run...

Blog

Worked hard on Vue3 projects for a year and a half, summarizing 16 coding standards

LMXFeb 20, 202515 min read#Vue3#Development Standards

This article summarizes 16 key coding standards distilled from Vue3 project development, covering various aspects from the use of arrow functions to performance optimization, aiming to improve code quality and development efficiency.

Comprehensive Analysis of Vue3 Development Experience and Specification Points Derived from Practical Work, Wishing You and Me to Progress Together!

1. Vue3 Specifications

1.1 Arrow Functions

It is recommended to use arrow functions (to keep the this reference unchanged and avoid the complexity of locating issues later).

Example:

//【建议】业务开发中提倡的做法, 箭头函数配合const函数一起使用
const getTableListData = () => { // TODO }

//【反例】尽量不要出现混用,如下:
function getDomeData () {}
const getDome1Data = () => {}

// 混用会导致可读性变差,而开发首要元素的可读性。

1.2 Variable Hoisting

In projects or during development, try to use let or const to define variables, which can effectively avoid issues with variable hoisting. This will not be elaborated further. Note that const is generally used to declare constants or variables whose values cannot be changed.

1.3 Data Requests

For data request types and asynchronous operations, use try…catch to catch exceptions. Try to avoid the emergence of callback hell.

Example:

// 推荐写法

/**
 * @description 获取列表数据
 * @return void
 */
const getTableListData = async () => {
  // 自己的业务处理TODO
  try {
    const res = await getTableListDataApi();
    const res1 = await getTableListDataApi1();
    // TODO
  } catch (error) {
    // 异常处理相关
  } finally {
    // 最终处理
  }
};

//【提倡】推荐接口定义带着Api结尾,比如我的方法是getTableListData,
//【提倡】内部逻辑调用的后端接口,那我的接口便可以定位为getTableListDataApi。

Of course, you can also use the following method:

Example:

/**
 * @description 获取列表数据
 * @return void
 */
const getTableListData = () => {
  getTableListDataApi({....}).then(() => {
      // TODO
  }).catch(() => {
      // TODO
  }).finally(() => {
      // TODO
  })
}

// 注意使用这种方式避免嵌套层级太深,如下反例:
const getTableListData1 = () => {
  getTableListDataApi({....}).then(() => {
     getTableListDataApi1({....}).then(() => {
          getTableListDataApi2({....}).then(() => {
             // TODO 这种就是典型的回调地狱,禁止出现这种
          })
       })
  })
}

Reasonable Use of Data Concurrent Requests:

Example:

// 场景描述:表头和表格数据都需要请求接口获取,可以使用并发请求。
/**
* 查询列表数据
*/
const getTableList = async () => {
  // TODO
  try {
    // 并行获取表格列数据和列表数据
    const [resColumns, resData] = await Promise.all([
      getTableColumnsApi({....}),
      getTableListApi({...}),
    ]);
    // TODO
  } catch (error) {
   // TODO
  } finally {
    // TODO
  }
};

//  Promise.all的一些执行细节不在赘述,但是注意区分和Promise.allSettled用法
//
//  Promise.all()方法会在任何一个输入的 Promise 被拒绝时立即拒绝。
// 相比之下,Promise.allSettled() 方法返回的 Promise 会等待所有
// 输入的 Promise 完成,不管其中是否有 Promise 被拒绝。如果你需
// 要获取输入可迭代对象中每个 Promise 的最终结果,则应使用allSettled()方法。

Reasonable Use of Data Racing Requests:

Example:

// 场景描述:某些业务需要请求多个接口,但是只要一个接口先返回便处理逻辑

let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("数据请求1");
  }, 1000);
});

let promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("数据请求2");
  }, 500);
});

Promise.race([promise1, promise2]).then((result) => {
  console.log(result); // 输出 "数据请求2"
});

Note:

When requesting data, be sure to properly catch and handle exceptions. Catching and handling exceptions can increase the robustness of the program and improve the user experience.

The following counterexamples are prohibited:

/**
 * 获取表格数据
 */
function  getTableListData () {
    getTableListData({
        pageNum: 1,
        pageSize: 10, // pageSize: 100000
    }).then((res) => {
        tableList.value = res.rows;
        tableTotal.value = res.total;
        //【提倡】
        tableList.value = res?.code === 200 ? res.rows : [];
    })
}

// 上面写法,界面可能没报错,功能也实现了,但是....

1.4 Reactive Variables

Use reactive variables reasonably. For objects or arrays with a large amount of data, and whose properties are nested objects, if your business scenario only requires the first-level properties to be reactive, it is recommended to use shallowRef and shallowReactive to define reactive variables. In this case, it is no longer recommended to use ref and reactive.

1.5 Single Responsibility Principle

When writing components or methods, you must follow the Single Responsibility Principle (the concept will not be elaborated here; please understand it yourself).

1.6 File Naming

The entry file for a functional menu must include a name, and it is also recommended that other business components include a name. The naming rule for name is PascalCase, and it should be globally unique as much as possible (to avoid increasing complexity when locating issues later).

For file naming, Vue does not have mandatory rules. Here, we borrow React's rules: PascalCase.

React component names must start with a capital letter, like StatusBar and SaveButton. React components also need to return something that React knows how to display, like a piece of JSX.

Example:

<script setup  name='CustomName'> </script>

// 或者

export default defineComponent({
    name: 'CustomName',
    .......
})

1.7 Using Listeners

When using listeners watchEffect and watch in Vue3, you need to pay attention to how they are used. First, let's look at watchEffect:

Example:

<script setup>
import { ref, watchEffect } from "vue"

const a = ref(true)
const b = ref(false)

watchEffect(() => {
  if (a.value || b.value) {
    console.log('执行了更新操作');
  }
})

const test = () => b.value = !b.value;
</script>

<template>
  <button @click="test">改变b的值</button>
  <h2>当前b的值:{{ b }}</h2>
</template>

Answer: When the value of b is changed in the template, watchEffect cannot detect 'the update operation was performed'.

See the example below:

<script setup>
import { ref, watchEffect } from "vue"

const getInfo = async () => {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(111)
    }, 2000)
  })
}

watchEffect(async () => {
  // 请求信息
  await getInfo()
  if (b.value) console.log('执行了更新操作');
})

const test = () =>  b.value = !b.value;
</script>

<template>
  <button @click="test">改变b的值</button>
  <h2>当前b的值:{{ b }}</h2>
</template>

Answer: When the value of b is changed in the template, watchEffect cannot detect 'the update operation was performed'.

Let's continue with the following example:

<script setup>
import { ref, watchEffect } from "vue"

const a = ref(true)
const b = ref(true)

setTimeout(() => {
  watchEffect(() => {
    if (a.value) {
      console.log('执行了更新操作');
    }
  })
}, 2000)

const test = () => b.value = !b.value;
</script>

<template>
  <button @click="test">改变b的值</button>
  <h2>当前b的值:{{ b }}</h2>
</template>

Answer: When the value of b changes in the template, watchEffect cannot detect 'update operations performed'. When using watchEffect, you need to pay attention to two points:

  1. Make sure watchEffect can capture reactive variables immediately;
  2. Asynchronous operations that trigger microtasks can affect watchEffect's immediate capture of reactive variables.

If you are not very familiar with using watchEffect, it is recommended to use watch as much as possible.

Points to note about watch: when your component uses a lot of watch internally or you want to manually reduce the complexity of watch.

Recommendations are as follows:

<script setup>
   ....
    const currentScope = effectScope();

    currentScope.run(() => {
      watch(
        () => props.currentRow,
        (newVal, oldVal) => {
          // TODO
        },
        { deep: true }
      );
      watchEffect(() => {
        if (queryObj.visitId) {
          // TODO
        }
      });
    });

    onBeforeUnmount(() => {
      currentScope.stop();
    });
</script>

It should be noted that in Vue 3.5+, a new 'deep' property has been added, which can directly take a number to tell the watch how many levels deep to listen to reactive data.

1.8 Hooks Usage

In Vue 3 projects, it is strongly recommended to use hooks for splitting and reusing functionality. This is the recommended way by the official Vue team. Here is an example: for instance, if I want to implement a popup feature, the common way of writing it is as follows, with the first approach being more backend-oriented:

const editModel = reactive({
  isShow: false,
  form: {
    name: "ANDROID",
    // ......
  },
  showFunc: () => {
    // 显示逻辑
  },
  cancelFunc: () => {
    // 取消逻辑
  },
  submitFunc: () => {
    // 提交逻辑
  },
});

Or other similar writing styles, not elaborated here. Actually, they can all be replaced with the hooks writing style:

Example:

const useEditModel = () => {
  const isShow = ref(false);

  /**
   * 显示弹框
   */
  const showModal = () => {};

  /**
   * 关闭弹框
   */
  const cancelModal = () => {};

  /**
   * 提交操作
   */
  const submitModal = () => {};

  onBeforeMount(() => {
    // TODO
  });

  return {
    isShow,
    showModal,
    cancelModal,
    submitModal,
  };
};

// 其他地方使用
const { isShow, showModal, cancelModal, submitModal } = useEditModel();

A simple summary of the idea behind writing hooks:

Within the function scope, define and use reactive/non-reactive states, variables, or states, variables, and methods obtained from multiple functions in combination to handle complex problems.

1.9 Exposing Methods

When we want to expose all properties of a third-party component, how can we expose them quickly?

Using expose requires writing them one by one, which is obviously too cumbersome. We can use the following method:

expose(
  new Proxy(
    {},
    {
      get(target, key) {
        // CustomDomRef是定义的模板中的ref dom节点
        return CustomDomRef.value?.[key];
      },
      has(target, key) {
        return key in CustomDomRef.value;
      },
    },
  ),
);

1.10. Selecting Attributes

In certain business scenarios, we need to select some attributes to pass to an interface. How to elegantly select attributes can be referenced as follows:

const obj = {
  name: "张三",
  age: 20,
  sex: "男",
  name1: "张三1",
};

// 当不需要name1传递时,怎么做呢?

// 方式1
delete obj.name1;

// 方式2
const newObj = {
  name: obj.name,
  age: obj.age,
  sex: obj.sex,
};

// 方式3
const newObj = {
  ...obj,
  name1: undefined,
};

// 其实可以使用一种更优雅的方式
const { name1, ...newObj } = obj;

// 或者使用lodash的omit或者pick方法

1.11 Compositional API

The Compositional API itself is designed for flexibility, but in projects it has been used in various inconsistent ways. Some put expose at the very beginning and put component imports at the bottom. When you are unsure about the usage order under the setup syntax sugar, you can refer to the following order:

Example:

<script setup>
  // import语句 // Props(defineProps) // Emits(defineEmits) // 响应性变量定义 //
  Computed // Watchers // 函数 // 生命周期 // Expose(defineExpose)
</script>

1.12 Logical Branches

When we write business code, we often encounter the following kind of writing. The writing is not right or wrong, there are just better ways to optimize it:

Example:

// 场景一
if (type === 1) {
  // TODO
} else if (type === 2) {
  // TODO
} else if (type === 3) {
  // TODO
} else if (type === 4) {
  // TODO
} else if (type === 5) {
  // TODO
} else {
  // TODO
}

// 场景二
if (type === 1) {
  if (type1 === 1) {
    if (type2 === 1) {
      if (type3 === 1) {
        // TODO
      }
    }
  }
}

Scenario 1: Violates the Open-Closed Principle (open for extension, closed for modification) and the Single Responsibility Principle. Scenario 1 can be optimized as follows:

// 优化方式一:字典映射方式
const typeHandlers = {
  1: handleType1,
  2: handleType2,
  3: handleType3,
  4: handleType4,
  5: handleType5,
  default: handleDefault,
};
const handler = typeHandlers[type] || typeHandlers.default;
handler();

// 优化方式二:高阶函数方式
const handleType1 = () => {
  /* TODO for type 1 */
};
const handleType2 = () => {
  /* TODO for type 2 */
};
// 其他处理函数...
const handlers = [handleType1, handleType2 /*...*/];
const processType = (type) => {
  if (handlers[type - 1]) handlers[type - 1]();
};
processType(type);

Scenario 2: Violated the Cyclomatic Complexity Principle and the Single Responsibility Principle. Scenario 2 can be optimized as follows:

// 优化方式一
const isValidType = () => {
  return type === 1 && type1 === 1 && type2 === 1 && type3 === 1;
};
if (isValidType()) {
}

// 优化方式二:使用"早返回原则"或者叫"错误前置原则"进行优化
if (type !== 1) return;
if (type1 !== 1) return;
if (type2 !== 1) return;
if (type3 !== 1) return;
// TODO

The above is just a brief outline of optimization ideas; there are many plans, as long as they are reasonable.

1.13 Delete Redundancies

In the process of business development, we often comment out code. Some files may have many commented sections, and these comments may be uncommented later. However, the official recommended approach is to delete these commented-out codes as much as possible, and only restore the codes that are really needed.

Another common issue is: console logs and debuggers. Although these can be removed during packaging through plugin configuration, the official recommendation is to immediately delete them in the source code once debugging is completed.

Also, a single file should not exceed 600 lines of code; of course, this can be moderately adjusted based on actual situations. Generally, when this line count is exceeded, code splitting is required, including components, methods, styles, configuration items, etc. However, excessive splitting can lead to fragmentation issues, so it needs to be reasonably managed.

1.14 Asynchronous Components

Vue3 provides the definition of asynchronous components (defineAsyncComponent). The advantages of asynchronous components are:

  1. They are lazy-loaded at runtime, which allows the browser to render other functions better.
  2. They facilitate code splitting during Vite packaging.

Example:

// 简单示例
<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)
</script>

<template>
  <AdminPage />
</template>

// 复杂示例

// 异步组件的定义
import { defineAsyncComponent } from "vue";
export const PreferenceItemComs: any = {
  Residence: defineAsyncComponent(() => import("./Residence.vue")),
  PastHistory: defineAsyncComponent(() => import("./PastHistory.vue")),
  AllergyHistory: defineAsyncComponent(() => import("./AllergyHistory.vue")),
  Diagnose: defineAsyncComponent(() => import("./Diagnose.vue")),
};
// 异步组件的使用
<keep-alive>
  <component
    :is="getCurrentComponents()"
  ></component>
</keep-alive>

/**
 * 获取当前需要渲染的组件
 */
const getCurrentComponents = () => {
  const projectType = activeName.value;
  if (projectType && PreferenceItemComs[projectType]) {
    return PreferenceItemComs[projectType];
  }
  return null;
};

For splitting complex functions, consider using asynchronous components.

1.15 Route Lazy Loading

In existing frameworks, we generally do not need to handle this part ourselves, because menus and routes are already well-encapsulated. However, we still need to understand the concept of route lazy loading:

Example:

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [
    { path: '/users/:id', component: UserDetails }
    // 或在路由定义里直接使用它
    { path: '/users/:id', component: () => import('./views/UserDetails.vue') },
  ],
})

Lazy loading of routes is beneficial for Vite to perform code splitting for different menu functionalities, reducing the size of the bundled code, thereby increasing access speed. It should be noted: do not use asynchronous components in routes. Asynchronous components can still be used within route components, but the route components themselves are dynamically imported.

1.16 Operators

There are several new operators in ES that you need to understand because they can simplify your coding.

?? (Nullish coalescing operator)

?. (Optional chaining operator)

??= (Nullish coalescing assignment operator)

?= (Safe assignment operator)

Example

// ?? ( 空值合并运算符):这个运算符主要是左侧为null和undefined,直接返回右侧值
// 请在开发过程中合理使用||和??
let result = value ?? '默认值';
console.log('result', result);



// ?.(可选链运算符): 用于对可能为 null 或 undefined 的对象进行安全访问。
// 建议这个属性要用起来,防止数据不规范时控制台直接报错
const obj = null;
let prop = obj?.property;
console.log('prop', prop);



// ??= (空值合并赋值操作符): 用于在变量已有非空值,避免重复赋值。
let x = null;
x ??= 5; // 如果 x 为 null 或 undefined,则赋值为 5



// ?= (安全复制运算符):旨在简化错误处理。改运算符与 Promise、async 函数以及任何实现了 Symbol.result 方法的对象兼容,简化了常见的错误处理流程。
// 注意:任何实现了 Symbol.result 方法的对象都可以与 ?= 运算符一起使用,Symbol.result 方法返回一个数组,第一个元素为错误,第二个元素为结果。
const [error, response] ?= await fetch("https://blog.conardli.top");

2. Code Comments

The readability and iterability of code are the primary considerations when writing code.

2.1. File Comments

Individual file comment standards: Each separate VUE file can have a file comment at the beginning, indicating the file's description, author, creation time, etc.

Example:

<!--
 * @FileDescription: 该文件的描述信息
 * @Author: 作者信息
 * @Date: 文件创建时间
 * @LastEditors: 最后更新作者
 * @LastEditTime: 最后更新时间
 -->

2.2 Method Comments

Methods developed during functionality implementation should have method comments and explanations. The comments must follow the JSDOC specification.

Method comment format:

/**
 * @description: 方法描述 (可以不带@description)
 * @param {参数类型} 参数名称
 * @param {参数类型} 参数名称
 * @return 没有返回信息写 void / 有返回信息 {返回类型} 描述信息
 */

Example:

/**
 * @description 获取解析统计相关数据
 * @param {Object} userInfo
 * @param {Array} lists
 * @return void
 */

或者;

/**
 * 获取解析统计相关数据
 * @param {Object} userInfo 用户信息
 * @param {Array} lists 用户列表
 * @return void
 */

2.3 Variable Comments

Key variables should be commented on, and variable comments generally include two types:

Example:

// 提倡(vscode可以给出提示的写法)
/* 描述信息 */
activeName: "first";

activeName: "first"; // 默认激活的Tab页
或者;

// 默认激活的Tab页
activeName: "first";

2.4 Inline Comments

Key business code must have inline comments. It is recommended that inline comments follow the format below:

Example:

// 根据指定的属性对数据进行分类

或者;

// 根据指定的属性对数据进行分类,
// 分类之后按住时间进行降序排序
// ......

或者;
/**
 * 根据指定的属性对数据进行分类,
 * 分类之后按住时间进行降序排序
 * ......
 */

2.5. Collapsed Code Block Comments

For variables or methods with very high coupling, it is recommended to use collapsed code block comments.

Example:

// #region 升序、降序处理逻辑

/**
 * 升序、降序处理逻辑说明:
 *
 * 根据指定的属性对数据进行分类,
 * 分类之后按住时间进行降序排序
 * ......
 */

const asceOrderLists = []; // 升序数组
const descOrderLists = []; // 降序数组

/**
 * @description 升序操作
 * @param {Array} lists
 * @return {Array} arrs
 */
const handleAsceOrder = (lists) => {
    // .........
    return arrs
}



/**
 * @description 降序操作
 * @param {Array} lists
 * @return {Array} arrs
 */
const handleDescOrder = (lists) => {
    // .........
    return arrs
}

......
// #endregion

2.6 Other

In daily development, common issue fixes and feature development suggestions are annotated in the following manner:

- 新功能点开发
// FEAT-001: 进行了XXXXX功能开发(LMX-2024-09-24)
- 问题修复
// BUGFIX-001: 进行了XXXXX功能修复(LMX-2024-09-24)

....

Explanation:

格式说明:
[${a1}-${a2}]: 相关描述信息(${a3}-${a4})

- a1:类型描述,建议遵循git提交规范,但是使用全驼峰大写。(feat、fix、bugfix、docs、style、refactor、perf、chore)
- a2: 编号,可以使用bug单号、功能特性单号或者自增序号,建议使用bug单号、功能特性单号。
- a3: git账户或者能标识自己的账号即可。
- a4: 新增或者修改时间,建议精确到天。

3. Directory Structure

For project feature development, how should one divide the directory structure of a feature? What kind of directory structure can improve code readability?

Below is a relatively complete business feature file directory for reference:

custom_module                   # Business module
│   ├── api                     # Private API of the business module
│   ├── components/modules      # Business components (involving business processing)
│   ├── composable              # Business components (not involving specific business)
│   ├── functional              # Business functional components
│   ├── methods/hooks           # Business hooks
│   ├── config                  # Business configuration items
│   ├── styles                  # Business styles
│   └── utils                   # Private utilities for the business module
|── index.vue                   # Business entry file
|── .pubrc.js                   # Business late module federation entry
└── README.md                   # Business documentation

Specific business function divisions can be determined based on your own particular business needs, as long as they are reasonable. If it is a commonly used component, it may not be necessary to divide it according to the above directory structure.

4. Performance Optimization

Reduce Code Bundle Size

  • Reduce duplicate source code, reuse functions by extracting common components or methods
  • Optimize frontend dependencies to prevent new dependencies from increasing bundle size; for instance, lodash-es is preferred over lodash
  • Code splitting (ESM dynamic import, route lazy loading)
  • Properly configure items in vite.config.ts, such as rollupOptions configuration for output.manualChunks, sourceMap, etc.

Optimize Resource Loading Speed

  • Some static resources or dependencies can consider using a CDN to increase access speed
  • Enable browser gzip compression to reduce bandwidth requests
  • Consider preloading certain critical resources
  • Consider lazy loading some images and videos

Business Code-level Optimization

  • Reduce the number of API requests and optimize time-consuming interfaces
  • Handle scenarios with large data volumes (pagination, virtual scrolling)
  • Minimize unnecessary updates (updates between parent and child components, avoid using index as key)
  • Reduce reactivity overhead for large quantities
  • Minimize manual memory leaks and overflow operations
  • Optimize tasks in JS that take a long time to execute (for example, consider asynchronous execution, requestAnimationFrame, requestIdleCallback)

Make Reasonable Use of Caching

  • Browser's conditional caching
  • Browser's strong caching
  • Use local storage in the browser (consider using localStorage, sessionStorage, indexedDB)

Discussion

Support the author
Previous post

Easy-to-Miss Nginx Details

Next post

Improving Website Performance with Resource Hints

LMX BlogWeb · TypeScript · Vue · Node.js
Crafted by
@LMXZQQ
Build
unavailable
Date
2026-10-30
Articles
29
Deployed on
Nginx
Source code
GitHub
License
MIT License
Typeface
Geist
Stack
  • next@16.4.3
  • react@19.3.1
  • @base-ui/react@1.8.0
  • shadcn@4.12.0
  • tailwindcss@4.3.3
Analytics
  • OpenPanel
  • Google Analytics
For agents
  • llms.txt
  • index.md
Inspired by
  1. Tailwind CSS
  2. shadcn/ui
  3. Vercel
  4. Apple
  5. Evil Charts
  6. Devouring Details
  7. Skiper UI
  8. Making Software
  9. shadcncraft
Approved by
静水流深 / LMX
1602
Command Palette

Search for a command to run...