最佳实践

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-23

为列表渲染设置属性key

在更新子节点时,需要从旧虚拟节点列表中查找与新虚拟节点相同的节点进行更新,如果这个查找过程中设置了属性key,那么查找速度会快很多,所以建议尽可能在v-for时提供key。 参考

在v-if/v-if-else/v-else中使用key

示例

vue在渲染元素时,处于效率考虑, 会尽量地复用已有的元素而非重新渲染。

比如上面的示例,如果先在输入框内输入内容,在点击切换按钮,虽然DOM变了,但是之前在输入框键入的内容并没有改变,只是替换了placeholder的内容,说明input元素被复用了,如果不希望这样做,可以使用vue.js提供的key属性,它可以让你自己决定是否要复用元素,key的值必须是唯一的。

示例2

以上示例,可以发现当赋予了key值,我们在输入之后再切换是完全的删除和插入,而不是原地复用。

路由切换时组件不变

const routes = [
  {
    path: '/detail/:id',
    name: 'detail',
    component: Detail
  }
]

当我们从路由 /detail/1 切换到 /detail/2 时,组件是不会发生任何变化的,因为 vue-router 会识别出两个路由使用的是同一个组件从而进行复用,并不会重新创建组件,因此组件的生命周期钩子函数也不会触发,vue-router 提供了 beforeRouteUpdate 钩子函数,会在当前路由改变且组件复用时调用,所以可以在组件内定义这个组件内的导航守卫来解决问题。

第二种方案是 watch $route 的变化:

export default {
  // ...
  watch: {
    '$route': (to, from) => {
      // 对路由变化作出响应
    },

    // 或者更细致的观察,针对对应的参数做对应的逻辑处理
    '$route.query.id': () => {

    },
    '$route.query.page': () => {

    },
  }
}

第三种方案是为 router-view 组件添加属性 key,利用虚拟DOM在渲染时通过key来对比两个节点是否是相同的原理。

通过给 router-view 组件设置 key,可以使每次切换路由时key都不一样,让虚拟DOM认为 router-view 组件是一个新的节点,从而销毁组件,然后重新创建新组件。

<router-view :key="$route.fullPath"></router-view>

这样,如果 url 变了,key 就变了,Vuejs会重新创建这个组件,所以组件内的生命周期会被重复触发,但是这种方式的坏处非常明显,非常浪费性能。优点就是简单粗暴

为所有的路由统一添加query

如果路由上的 query 中有一些是从上游链路上传下来的,那么需要在应用的任何路由中携带,但是在所有跳转路由的地方都设置以便会非常麻烦,理想状态是在全局统一配置一个基础的 query,它会在应用的所有路由中携带,并且不影响应用中的各个路由的切换,也无须在切换路由时进行任何特殊处理。

使用全局守卫 beforeEach

全局守卫 beforeEach 并不具备修改 query 的能力,但可以在其中使用 next 方法来中断当前导航,并切换到新导航,添加一些新的 query 进去。

单单这样做会出问题,因为在进入新导航后,依然会被全局守卫 beforeEach 拦截,然后再次开启新导航,从而导致无限循环。解决办法是 beforeEach 中判断这个全局添加的参数在路由对象中是否存在,如果存在,则不开启新导航。

const query = { referer: 'abc.com' }
router.beforeEach((to, from, next) => {
  to.query.referer
    ? next()
    : next({
      ...to,
      query: {
        ...to.query,
        ...query
      }
    })
})

这种方案的优点是可以统一配置全局公共的 query 参数,并且在组件内切换时无需进行特殊处理。缺点是,全局守卫 beforeEach 会执行两次,每次切换路由实际是切换两次。

使用函数劫持

通过拦截 router.history.transitionTo 方法,在 vue-router 内部在切换路由之前将参数添加到 query 中。

const query = { referer: 'abc.com' }
const transitionTo = router.history.transitionTo

router.history.transitionTo = function (location, onComplete, onAbort) {
  location = typeof location === 'object'
    ? { ...location, query: { ...location.query, ...query } }
    : { path: location, query }
  transitionTo.call(route.history, location, onComplete, onAbort)
}

优点是全局添加 query 参数并且不会导致路由切换两次,缺点是通过修改 vue-router 内部方法实现目的,这是一种很危险的操作。

避免 v-if 和 v-for 一起使用

Vuejs 官方建议不要把 v-if 和 v-for 同时用在同一个元素上。

  • 如果为了过滤一个列表中的某一项,请将列表变量替换为一个计算属性,让它返回过滤后的列表
  • 为了隐藏的某一个列表,请将v-if放在容器元素上,而不是v-for元素上。

Vuejs官方给出的解释是:当Vuejs处理指令时,v-for 比 v-if 具有更高的优先级,所以即使我们只渲染出列表中的一小部分元素,也得在每次重渲染的时候遍历整个列表,而不考虑需要渲染的项是否发生了变化,

为组件样式设置作用域

css 的规则都是全局的,任何一个组件的样式都对整个页面有效。因此,我们很容易在一个组件中写了某个样式,而不小心影响了另一个组件的样式,或者自己的组件被第三方库的css影响了。

在 Vuejs 中,可以通过 scoped 特性或 CSS Modules 来设置组件样式作用域。

1、使用 scoped:

<template>
  <button class="button button-close"></button>
</template>

<style scoped>
.button {
  border: none;
  border-radius: 2px;
}

.button-close {
  background-color: red;
}
</style>

2、使用 CSS Modules

<template>
  <button :class="[$style.button, $style.buttonClose]"></button>
</template>

<style module>
.button {
  border: none;
  border-radius: 2px;
}

/* 类名不能再用 - 分割 */
.buttonClose { 
  background-color: red;
}
</style>

单文件组件的命名

单文件组件的文件名应该始终是单词首字母大写(PascalCase),或者始终是横线连接的(kebab-case)。