MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue 2与Vue 3 最佳实践与代码规范建议

2023-05-015.5k 阅读

Vue 2 最佳实践

组件设计与架构

  1. 单一职责原则
    • 在 Vue 2 中,组件应遵循单一职责原则。每个组件应该只负责一件事情,这样可以提高组件的可维护性和复用性。例如,假设有一个用户信息展示组件 UserInfo.vue,它应该只专注于展示用户的基本信息,如姓名、年龄等,而不应该包含用户登录、注册等与展示无关的逻辑。
    • 代码示例
<template>
  <div>
    <p>姓名: {{ user.name }}</p>
    <p>年龄: {{ user.age }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: '张三',
        age: 25
      }
    };
  }
};
</script>
  1. 组件层次结构
    • 合理规划组件的层次结构至关重要。对于大型应用,通常会采用父子组件的嵌套方式来构建页面。例如,在一个电商应用中,可能有一个 ProductList 组件作为父组件,它包含多个 ProductItem 子组件来展示商品列表。父组件负责管理商品数据的获取和传递,子组件负责单个商品的展示和交互。
    • 代码示例
    • ProductList.vue
<template>
  <div>
    <ProductItem v - for="product in products" :key="product.id" :product="product" />
  </div>
</template>

<script>
import ProductItem from './ProductItem.vue';
export default {
  components: {
    ProductItem
  },
  data() {
    return {
      products: [
        { id: 1, name: '商品1', price: 100 },
        { id: 2, name: '商品2', price: 200 }
      ]
    };
  }
};
</script>
  • ProductItem.vue
<template>
  <div>
    <p>{{ product.name }}</p>
    <p>价格: {{ product.price }}</p>
  </div>
</template>

<script>
export default {
  props: ['product']
};
</script>

数据管理

  1. 使用 data 选项
    • 在 Vue 2 组件中,data 选项是存储组件状态的地方。需要注意的是,data 必须是一个函数,这样每个组件实例才能拥有独立的数据副本。例如,在一个计数器组件中:
    • 代码示例
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>
  1. 计算属性与方法的选择
    • 计算属性是基于它们的依赖进行缓存的,只有当依赖发生变化时才会重新计算。而方法在每次调用时都会重新执行。例如,在一个购物车组件中,如果需要计算商品的总价,使用计算属性会更合适。
    • 代码示例
<template>
  <div>
    <ul>
      <li v - for="item in cartItems" :key="item.id">
        {{ item.name }} - {{ item.price }}
      </li>
    </ul>
    <p>总价: {{ totalPrice }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      cartItems: [
        { id: 1, name: '商品1', price: 100 },
        { id: 2, name: '商品2', price: 200 }
      ]
    };
  },
  computed: {
    totalPrice() {
      return this.cartItems.reduce((acc, item) => acc + item.price, 0);
    }
  }
};
</script>

事件处理

  1. 绑定原生 DOM 事件
    • 在 Vue 2 中,可以使用 v - on 指令(缩写为 @)来绑定原生 DOM 事件。例如,在一个按钮点击事件中:
    • 代码示例
<template>
  <div>
    <button @click="handleClick">点击我</button>
  </div>
</template>

<script>
export default {
  methods: {
    handleClick() {
      console.log('按钮被点击了');
    }
  }
};
</script>
  1. 组件间事件通信
    • 父子组件通信:父组件可以通过 props 向子组件传递数据,子组件通过 $emit 触发自定义事件向父组件传递数据。例如,在一个 Child 组件中,点击按钮向父组件传递一个消息。
    • 代码示例
    • Parent.vue
<template>
  <div>
    <Child @child - event="handleChildEvent" />
  </div>
</template>

<script>
import Child from './Child.vue';
export default {
  components: {
    Child
  },
  methods: {
    handleChildEvent(message) {
      console.log('子组件传来的消息:', message);
    }
  }
};
</script>
  • Child.vue
<template>
  <div>
    <button @click="sendMessage">发送消息</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      this.$emit('child - event', '这是子组件的消息');
    }
  }
};
</script>
  • 非父子组件通信:对于非父子组件通信,可以使用一个事件总线(event bus)。创建一个空的 Vue 实例作为事件总线,然后在各个组件中通过这个实例来触发和监听事件。
  • 代码示例
  • eventBus.js
import Vue from 'vue';
export const eventBus = new Vue();
  • ComponentA.vue
<template>
  <div>
    <button @click="sendMessage">向 ComponentB 发送消息</button>
  </div>
</template>

<script>
import { eventBus } from './eventBus.js';
export default {
  methods: {
    sendMessage() {
      eventBus.$emit('message - from - a', '这是来自 ComponentA 的消息');
    }
  }
};
</script>
  • ComponentB.vue
<template>
  <div>等待接收消息</div>
</template>

<script>
import { eventBus } from './eventBus.js';
export default {
  created() {
    eventBus.$on('message - from - a', (message) => {
      console.log('收到来自 ComponentA 的消息:', message);
    });
  }
};
</script>

指令使用

  1. v - if 与 v - show
    • v - if 是真正的条件渲染,它会根据条件动态地添加或移除 DOM 元素。v - show 则是通过 CSS 的 display 属性来控制元素的显示与隐藏。一般来说,如果元素在运行时很少改变显示状态,使用 v - if 更合适;如果元素需要频繁切换显示状态,使用 v - show 性能更好。
    • 代码示例
    • 使用 v - if
<template>
  <div>
    <button @click="toggle">切换</button>
    <p v - if="isVisible">这是使用 v - if 控制的内容</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: true
    };
  },
  methods: {
    toggle() {
      this.isVisible =!this.isVisible;
    }
  }
};
</script>
  • 使用 v - show
<template>
  <div>
    <button @click="toggle">切换</button>
    <p v - show="isVisible">这是使用 v - show 控制的内容</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: true
    };
  },
  methods: {
    toggle() {
      this.isVisible =!this.isVisible;
    }
  }
};
</script>
  1. v - for 与 key
    • 在使用 v - for 指令进行列表渲染时,必须为每个列表项提供一个唯一的 key 值。key 主要用于 Vue 进行虚拟 DOM 对比时,更高效地更新和复用 DOM 元素。例如,在渲染一个用户列表时:
    • 代码示例
<template>
  <div>
    <ul>
      <li v - for="user in users" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' }
      ]
    };
  }
};
</script>

Vue 3 最佳实践

组合式 API 应用

  1. setup 函数基础
    • 在 Vue 3 中,setup 函数是组合式 API 的入口点。它在组件创建之前被调用,并且在 datacomputedmethods 等选项之前执行。setup 函数接收两个参数:propscontextprops 是传递给组件的属性,context 包含 attrsslotsemit 等上下文信息。
    • 代码示例
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  setup() {
    const count = ref(0);
    const increment = () => {
      count.value++;
    };
    return {
      count,
      increment
    };
  }
};
</script>
  1. 使用 reactive 定义响应式数据
    • reactive 函数用于创建一个响应式对象。与 ref 不同,ref 主要用于创建单个响应式值,而 reactive 适用于创建复杂的响应式对象。例如,在一个用户信息组件中:
    • 代码示例
<template>
  <div>
    <p>姓名: {{ user.name }}</p>
    <p>年龄: {{ user.age }}</p>
    <button @click="updateUser">更新用户信息</button>
  </div>
</template>

<script>
import { reactive } from 'vue';
export default {
  setup() {
    const user = reactive({
      name: '张三',
      age: 25
    });
    const updateUser = () => {
      user.name = '李四';
      user.age = 26;
    };
    return {
      user,
      updateUser
    };
  }
};
</script>
  1. 计算属性与方法在组合式 API 中的实现
    • 计算属性:在组合式 API 中,可以使用 computed 函数来定义计算属性。例如,计算购物车商品总价:
    • 代码示例
<template>
  <div>
    <ul>
      <li v - for="item in cartItems" :key="item.id">
        {{ item.name }} - {{ item.price }}
      </li>
    </ul>
    <p>总价: {{ totalPrice }}</p>
  </div>
</template>

<script>
import { ref, computed } from 'vue';
export default {
  setup() {
    const cartItems = ref([
      { id: 1, name: '商品1', price: 100 },
      { id: 2, name: '商品2', price: 200 }
    ]);
    const totalPrice = computed(() => {
      return cartItems.value.reduce((acc, item) => acc + item.price, 0);
    });
    return {
      cartItems,
      totalPrice
    };
  }
};
</script>
  • 方法:在 setup 函数中定义的函数就是组件的方法。例如,在一个表单提交组件中:
  • 代码示例
<template>
  <div>
    <input v - model="formData.username" placeholder="用户名" />
    <input v - model="formData.password" placeholder="密码" type="password" />
    <button @click="submitForm">提交</button>
  </div>
</template>

<script>
import { reactive } from 'vue';
export default {
  setup() {
    const formData = reactive({
      username: '',
      password: ''
    });
    const submitForm = () => {
      console.log('提交的数据:', formData);
    };
    return {
      formData,
      submitForm
    };
  }
};
</script>

生命周期钩子函数

  1. 在组合式 API 中的使用
    • 在 Vue 3 组合式 API 中,生命周期钩子函数以 on + 钩子函数名 的形式使用。例如,onMounted 用于在组件挂载后执行某些操作,onUnmounted 用于在组件卸载后执行操作。
    • 代码示例
<template>
  <div>组件示例</div>
</template>

<script>
import { onMounted, onUnmounted } from 'vue';
export default {
  setup() {
    onMounted(() => {
      console.log('组件已挂载');
    });
    onUnmounted(() => {
      console.log('组件已卸载');
    });
  }
};
</script>
  1. 与 Vue 2 生命周期钩子的对应关系
    • Vue 2 的 created 对应 Vue 3 组合式 API 中的 setup 函数内部逻辑执行开始;Vue 2 的 mounted 对应 onMounted;Vue 2 的 beforeUpdate 对应 onBeforeUpdate;Vue 2 的 updated 对应 onUpdated;Vue 2 的 beforeDestroy 对应 onBeforeUnmount;Vue 2 的 destroyed 对应 onUnmounted

响应式原理与优化

  1. 理解 Vue 3 响应式原理
    • Vue 3 使用了 Proxy 来实现响应式系统,相比 Vue 2 的 Object.definePropertyProxy 具有更强大的功能和性能优势。Proxy 可以直接代理整个对象,而不需要对对象的每个属性进行遍历和劫持。例如,在创建一个响应式对象时:
    • 代码示例
import { reactive } from 'vue';
const user = reactive({
  name: '张三',
  age: 25
});
// 当访问或修改 user 的属性时,Vue 3 会通过 Proxy 捕获并进行响应式处理
  1. 性能优化
    • 使用 shallowRefshallowReactive:如果数据结构非常复杂,并且不需要对所有深层次的数据进行响应式处理,可以使用 shallowRefshallowReactiveshallowRef 只对自身的值变化做出响应,不会对其内部对象的属性变化做出响应;shallowReactive 只对对象的直接属性进行响应式处理,不会递归处理深层次属性。
    • 代码示例
    • 使用 shallowRef
<template>
  <div>
    <p>{{ complexData.value.name }}</p>
    <button @click="updateComplexData">更新数据</button>
  </div>
</template>

<script>
import { shallowRef } from 'vue';
export default {
  setup() {
    const complexData = shallowRef({
      name: '初始值',
      subData: {
        detail: '一些细节'
      }
    });
    const updateComplexData = () => {
      // 这不会触发视图更新,因为 shallowRef 只对自身值变化响应
      complexData.value.subData.detail = '新细节';
      // 这会触发视图更新
      complexData.value = {
        name: '新值',
        subData: {
          detail: '一些细节'
        }
      };
    };
    return {
      complexData,
      updateComplexData
    };
  }
};
</script>
  • 使用 shallowReactive
<template>
  <div>
    <p>{{ user.name }}</p>
    <p>{{ user.subUser.name }}</p>
    <button @click="updateUser">更新用户</button>
  </div>
</template>

<script>
import { shallowReactive } from 'vue';
export default {
  setup() {
    const user = shallowReactive({
      name: '张三',
      subUser: {
        name: '子用户'
      }
    });
    const updateUser = () => {
      // 这不会触发视图更新,因为 shallowReactive 不对深层次属性响应
      user.subUser.name = '新子用户';
      // 这会触发视图更新
      user.name = '李四';
    };
    return {
      user,
      updateUser
    };
  }
};
</script>

Vue 2 代码规范建议

组件命名规范

  1. 文件命名
    • 组件文件命名应采用 PascalCase(大驼峰命名法)。例如,UserInfo.vueProductList.vue 等。这样命名可以清晰地表明这是一个组件,并且在导入和使用时也更容易识别。
  2. 组件内 name 选项
    • 组件内部的 name 选项也应采用 PascalCase。这有助于在调试工具中更好地识别组件,同时在递归组件中也需要使用 name 选项来进行正确的递归调用。
    • 代码示例
<template>
  <div>组件内容</div>
</template>

<script>
export default {
  name: 'MyComponent',
  data() {
    return {};
  }
};
</script>

样式规范

  1. 局部样式
    • 对于组件内部的样式,应使用 <style scoped>。这样可以确保样式只作用于当前组件,避免样式污染全局。例如:
    • 代码示例
<template>
  <div class="my - component">
    <p>组件内容</p>
  </div>
</template>

<style scoped>
.my - component {
  background - color: lightblue;
}
</style>
  1. 使用 BEM 命名规范
    • 在编写 CSS 类名时,推荐使用 BEM(Block - Element - Modifier)命名规范。例如,在一个按钮组件中,按钮整体是一个块(block),按钮的不同状态(如禁用状态)可以作为修饰符(modifier)。
    • 代码示例
<template>
  <button class="button button--disabled">按钮</button>
</template>

<style scoped>
.button {
  background - color: blue;
  color: white;
}
.button--disabled {
  background - color: gray;
  cursor: not - allowed;
}
</style>

代码结构规范

  1. 选项顺序
    • 在 Vue 2 组件中,推荐按照以下顺序排列选项:namecomponentsdirectivespropsdatacomputedwatchcreatedmountedupdatedbeforeDestroydestroyedmethods。这样的顺序可以使组件代码结构更清晰,易于阅读和维护。
    • 代码示例
<template>
  <div>组件示例</div>
</template>

<script>
export default {
  name: 'MyComponent',
  components: {},
  directives: {},
  props: {},
  data() {
    return {};
  },
  computed: {},
  watch: {},
  created() {},
  mounted() {},
  updated() {},
  beforeDestroy() {},
  destroyed() {},
  methods: {}
};
</script>
  1. 注释规范
    • 对复杂的逻辑代码段、组件的功能、props 的含义等都应添加注释。单行注释使用 //,多行注释使用 /*... */。例如:
    • 代码示例
<template>
  <div>
    <!-- 这里是一个用于展示用户信息的组件 -->
    <p>{{ user.name }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 用户信息对象
      user: {
        name: '张三'
      }
    };
  }
};
</script>

Vue 3 代码规范建议

组合式 API 代码规范

  1. 变量与函数命名
    • setup 函数中定义的变量和函数应采用 camelCase(小驼峰命名法)。例如,countincrementCount 等。这样的命名方式与 JavaScript 的命名习惯保持一致,易于理解和维护。
  2. 逻辑拆分与组织
    • 对于复杂的组件逻辑,可以将相关的逻辑拆分成多个函数或模块。例如,在一个包含用户登录、注册和密码找回功能的组件中,可以将登录逻辑放在 useLogin.js 模块中,注册逻辑放在 useRegister.js 模块中,然后在 setup 函数中引入并使用。
    • 代码示例
    • useLogin.js
import { ref } from 'vue';
export const useLogin = () => {
  const username = ref('');
  const password = ref('');
  const login = () => {
    // 登录逻辑
    console.log('登录中,用户名:', username.value, '密码:', password.value);
  };
  return {
    username,
    password,
    login
  };
};
  • 组件 LoginComponent.vue
<template>
  <div>
    <input v - model="username" placeholder="用户名" />
    <input v - model="password" placeholder="密码" type="password" />
    <button @click="login">登录</button>
  </div>
</template>

<script>
import { useLogin } from './useLogin.js';
export default {
  setup() {
    const { username, password, login } = useLogin();
    return {
      username,
      password,
      login
    };
  }
};
</script>

模板语法规范

  1. v - if 和 v - for 的使用顺序
    • 在 Vue 3 模板中,如果同时使用 v - ifv - for,应该将 v - if 放在外层元素上。这是因为 v - for 的优先级高于 v - if,如果将 v - if 放在 v - for 内部,会在每次循环时都进行条件判断,影响性能。
    • 代码示例
<template>
  <div>
    <div v - if="shouldShowList">
      <ul>
        <li v - for="item in items" :key="item.id">
          {{ item.name }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  setup() {
    const shouldShowList = ref(true);
    const items = ref([
      { id: 1, name: '项目1' },
      { id: 2, name: '项目2' }
    ]);
    return {
      shouldShowList,
      items
    };
  }
};
</script>
  1. 指令缩写使用
    • 推荐使用指令的缩写形式,如 @ 代替 v - on: 代替 v - bind。这样可以使模板代码更简洁易读。例如:
    • 代码示例
<template>
  <div>
    <a :href="link" @click="handleClick">点击链接</a>
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  setup() {
    const link = ref('https://example.com');
    const handleClick = () => {
      console.log('链接被点击');
    };
    return {
      link,
      handleClick
    };
  }
};
</script>

整体项目结构规范

  1. 文件夹结构
    • 对于 Vue 3 项目,推荐采用清晰的文件夹结构。例如,可以将组件分为 components 文件夹存放基础组件,views 文件夹存放页面级组件;将 API 相关的代码放在 api 文件夹中;将样式文件放在 styles 文件夹中。这样的结构有助于项目的管理和维护。
    • 示例文件夹结构
project - root
├── src
│   ├── api
│   │   ├── userApi.js
│   │   ├── productApi.js
│   ├── components
│   │   ├── Button.vue
│   │   ├── Input.vue
│   ├── views
│   │   ├── Home.vue
│   │   ├── About.vue
│   ├── styles
│   │   ├── main.css
│   │   ├── variables.css
│   ├── App.vue
│   ├── main.js
├── public
├── package.json
  1. 使用 TypeScript 增强代码健壮性
    • 如果项目规模较大或对代码类型安全要求较高,可以使用 TypeScript 与 Vue 3 结合。Vue 3 对 TypeScript 有良好的支持,可以在组件中定义 props 的类型、函数的参数和返回值类型等,从而提高代码的可维护性和健壮性。
    • 代码示例
<template>
  <div>
    <p>{{ user.name }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
interface User {
  name: string;
}
export default defineComponent({
  data() {
    return {
      user: {
        name: '张三'
      } as User
    };
  }
});
</script>