VUE使用技巧(vue2.x)

vue使用技巧

data 数据冻结

vuedata的数据默认会进行双向数据绑定,若将大量和渲染无关的数据直接放置在data中,将会浪费双向数据绑定时所消耗的性能,将这些和渲染无关的数据进行抽离并配合Object.freeze进行处理。

tablecolumns数据可单独提取到一个外部js文件作为配置文件,也可以在当前.vue文件中定义一个常量定义columns数据,因为无论如何都是固定且不会修改的数据,应该使用Object.freeze进行包裹,既可以提高性能还可以将固定的数据抽离,一些下拉框前端固定的数据也建议此操作。

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。

  • freeze() 返回和传入的参数相同的对象
  • 作为参数传递的对象与返回的对象都被冻结
  • 所以不必保存返回的对象(因为两个对象全等)
const obj = { a1b2 }
const obj2 = Object.freeze(obj)
obj === obj2 // => true

需要注意的是 Object.freeze() 冻结的是值,这时仍然可以将变量的引用替换掉,还有确保数据不会变才可以使用这个语法,如果要对数据进行修改和交互,就不适合使用冻结了。

export default {
 data() {
  return {
   // 冻结数据,避免vue进行响应式数据转换
   columns: Objet.freeze([{ title"姓名"key"name" }, { title"性别"key"gender" }])
  }
 }
}

debounce 使用

例如远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的。

当一个按钮多次点击时会导致多次触发事件,可以结合场景是否立即执行immediate

<template>
 <select :remote-method="remoteMethod">
  <option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}} </option>
 </select>
</template>
import {debounce} from 'lodash-es' methods:{ remoteMethod:debounce(function (query) { // to do ... // this 的指向没有问题 },
200), }

路由参数解耦

一般在组件内使用路由参数,都会写如下代码:

export default {
 computed: {
  id() {
   return this.$route.params.id
  }
 }
}

在组件中使用$route会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的URL上使用,限制了其灵活性。

如果一个组件既可以是路由组件又可以是普通组件,那该如何实现呢? 有人会说我做个判断就可以了啊

export default {
    props: {
        userId: {
            typeString,
            default''
        }
    }
    computed: {
        id() {
            return this.userId || this.$route.params.id
        }
    }
}

但是这种方式不够优雅,那还有更优雅的处理方式吗? 正确的做法是通过路由props进行参数解耦

const router = new VueRouter({
 routes: [
  {
   path"/user/:id",
   component: User,
   propstrue
  }
 ]
})

将路由的props属性设置为true,就可以在组件内通过props接收$route.prams参数,在模板中就可以直接使用id了,不用关系这个id是从外部传递过来的还是通过路由参数传递过来的。

export default {
 props: {
  id: {
   typeString,
   default""
  }
 }
}

另外你还可以通过函数模式来传递props

const router = new VueRouter({
 routes: [
  {
   path"/user/:id",
   component: User,
   propsroute => ({
    id: route.query.id
   })
  }
 ]
})

函数式组件

由于函数式组件只是函数,因此渲染性能开销也低很多,适合依赖外部数据变化而变化的组件。

  • 无状态,没有响应式data
  • 无实例,没有this上下文
  • 无生命周期方法,没有钩子函数
  • 不会出现在 Vue devtools 的组件树中
<template functional>
 <button class="btn btn-primary" v-bind="data.attrs" v-on="listeners"><slot /></button>
</template>

watch 高阶使用

立刻执行

watch是监听属性改变才触发,有时候我们系统在组件创建时也触发。

一般的想法是在created生命周期函数里调用一次,以后watch监听变化时触发。

export default {
 data() {
  return {
   name"Yori"
  }
 },
 watch: {
  name: {
   hander"sayName",
   immediatetrue // 立刻执行
  }
 },
 created() {
  // this.sayName()
 },
 methods: {
  sayName() {
   console.log(this.name)
  }
 }
}

深度监听

watch监听对象时,属性内对象的属性改变无法监听到,这时我们就需要设置深度监听模式。

export default {
 data() {
  return {
   user: {
    name: {
     firstName"张",
     lastName"三"
    }
   }
  }
 },
 watch: {
  name: {
   handlerfunction({},
   deeptrue
  }
 }
}

触发执行多个方法

watch运行以数组的形式传递的多个方法,方法会一一调用 可以是字符串函数名、匿名函数和对象形式。

export default {
 watch: {
  name: [
   "sayName",
   function(oldVal, newVal{},
   {
    handlerfunction({},
    immediatetrue
   }
  ]
 }
}

监听多个属性

watch本身不支持多个属性同时监听,我们可以通过计算属性的形式把多个属性合并成一个对象,供watch监听使用

export default {
 data() {
  return {
   msg1"apple",
   msg2"banana"
  }
 },
 computed: {
  msgObj() {
   const { msg1, msg2 } = this
   return {
    msg1,
    msg2
   }
  }
 },
 watch: {
  msgObj: {
   handler(newVal, oldVal) {
    if (newVal.msg1 != oldVal.msg1) {
     console.log("msg1 is change")
    }
    if (newVal.msg2 != oldVal.msg2) {
     console.log("msg2 is change")
    }
   },
   deeptrue
  }
 }
}

组件生命周期钩子

父子组件的加载顺序是先加载子组件,再加载父组件,但是父组件是无法知道子组件什么时候加载完成的,我们一般的做法是使用$emit进行事件触发,通知父组件。

  • 子组件,在生命周期方法里触发事件
export default {
 name"ComponentSub",
 mounted() {
  this.$emit("mounted")
 }
}
  • 父组件,监听事件,接收子组件的事件
<template>
 <component-sub @mounted="onSubMounted" />
</template>
<script>
 export default {
  methods: {
   onSubMounted() {
    //
   }
  }
 }
</script>

但是这种方式不够灵活,如果子组件是调用的三方框架组件,有没有更优雅的方式监听组件的生命周期呢。 通过@hook可以监听组件的生命周期方法,组件无需修改,如created,beforeDestroy

<template>
 <component-a @hook:created="onReady" />
</template>
<script>
 export default {
  methods: {
   onReady() {
    // ...
   }
  }
 }
</script>

可编程的事件监听器

我们在写组件时时经常能看到如下代码:

export default {
 data() {
  return {
   timernull
  }
 },
 methods: {
  start() {
   this.timer = setInterval(() => {
    console.log(Date.now())
   }, 1000)
  }
 },
 mounted() {
  this.start()
 },
 beforeDestroy() {
  if (this.timer) {
   clearInterval(this.timer)
   this.timer = null
  }
 }
}

我们使用timer来缓存定时器的引用以便后续使用,只为了在生命周期方法beforeDestroy中释放资源使用。

  • 多了一个timer实例属性
  • 设置和销毁代码分散在各处

通过可编程的事件监听器,把创建实例和销毁实例放在一起,使代码更加简洁

export default {
 methods: {
  start() {
   const timer = setInterval(() => {
    console.log(Date.now())
   }, 1000)

   this.$once("hook:beforeDestroy", () => {
    clearInterval(timer)
   })
  }
 }
}

手动刷新组件

是否遇到这种场景,一个组件包含了多个表单和信息展示,出于业务需求,我们需要清空组件里表单的信息还原到刚进来的初始状态 在表单组件少的情况下,我们通过设置初始值的方法来还原组件状态,但是太繁琐,有没有更简单的方式呢

  • 更新key强制渲染组件

通过更新组件的key来强制重新渲染组件

<template>
 <component-a :key="key" />
</template>
<script>
 export default {
  data() {
   return {
    key0
   }
  },
  methods: {
   forceRerender() {
    this.key++
   }
  }
 }
</script>

通过更新组件的key属性,强制使组件不复用缓存,生成一个新的组件,达到还原组件到初始状态的目的

  • v-if强制渲染

v-if指令,该指令仅在组件为 true 时才渲染。 如果为 false,则该组件在 DOM 中不存在。

<template>
 <component-a v-if="renderComponent" />
</template>
<script>
 export default {
  forceRerender() {
   // 从DOM删除组件
   this.renderComponent = false
   this.$nextTick().then(() => {
    // 在DOM中添加该组件
    this.renderComponent = true
   })
  }
 }
</script>
  • $forceUpdate()

迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

vue属性本身是响应式的,但是有时候属性改变,页面会出现不刷新情况 如果我们不用$set()方法来更新数据,比如我们改变数组的长度arr.length = 1,页面也要求同步更新,就需要手动调用$forceUpdate强制渲染

<template>
 <ul>
  <li v-for="(item) in list" :key="item">{{ item }}</li>
 </ul>
</template>
<script>
 export default {
  data() {
   return {
    list: ["apple""orange""banana"]
   }
  },
  methods: {
   resetHandler() {
    this.list.length = 1
    this.$forceUpdate()
   }
  }
 }
</script>

使用Vue.extend()写命令式 api

对应一些组件,使用命令式编程的方式更优雅调用组件,如$message,$confirm等弹窗自己使用的比较多,而不是在组件中切换组件的方式来实现效果,下面是简易版的命令式组件的实现。

  • components/Comfirm/index.js
import Vue from 'vue'
import Confirm from './Confirm.vue'

const ConfirmModal = {
  instance: null,
  show (title = '温馨提示', message = '') {
    if (!this.instance) {
      const ConfirmConstructor = Vue.extend(Confirm)
      this.instance = new ConfirmConstructor({
        propsData: {
          title,
          message
        }
      })
    } else {
      this.instance.title = title
      this.message = message
    }
    // $mount("#my") 挂载到指定选择器
    // $mount() 不传入选择器,生成文档之外的元素,类似‘document.creatElement()’在内存中生成DOM,
    this.instance.$mount()
    // this.instance.$el 获取对应的DOM元素
    // 把内存中的DOM挂载到body里
    document.body.appendChild(this.instance.$el)
    Vue.nextTick().then(() => {
      this.instance.show()
    })
  },
  hide () {
    if (this.instance) {
      // 等待动画执行完
      this.instance.hide().then(() => {
        // 删除DOM元素
        document.body.removeChild(this.instance.$el)
      })
    }
  }
}

export default {
  install () {
    Vue.prototype.$confirm = ConfirmModal
  }
}

export {
  Confirm
}

  • src/components/Confirm/Confirm.vue
<template>
 <transition>
  <div class="q-confirm" v-if="visible">
   <div class="q-confirm-overlay" @click="close"></div>
   <div class="q-confirm-content"><h1>Confirm</h1></div>
  </div>
 </transition>
</template>
<script>
 export default {
  data() {
   return {
    visiblefalse
   }
  },
  created() {
   console.log("Confirm created")
  },
  mounted() {
   console.log("Confirm mounted")
  },
  methods: {
   show() {
    this.visible = true
   },
   close() {
    this.visible = false
   },
   hide() {
    return new Promise((resolve, reject) => {
     this.visible = false
     setTimeout(() => resolve(), 200)
    })
   }
  }
 }
</script>
<style scoped>
 .q-confirm-content {
  width300px;
  height300px;
  position: absolute;
  top50%;
  left50%;
  background: white;
  border1px solid #dddddd;
  transformtranslate(-50%, -50%);
  z-index20;
 }
 .q-confirm-overlay {
  position: absolute;
  width100%;
  height100%;
  top0;
  left0;
  background-colorrgba(0000.2);
  z-index10;
 }
</style>

巧用slot-scope数据和 UI 分离组件

slot-scope语法V2.5添加,V2.6废弃,V2.6+请使用v-slot

如何写数据和 UI 分离组件,以vue-promised这个库为例。 Promised 组件并不关注你的视图展示成什么样,它只是帮你管理异步流程,并且通过你传入的 slot-scope,在合适的时机把数据回抛给你,并且帮你去展示你传入的视图。

  • 封装前写法
<template>
 <div>
  <p v-if="error">Error: {{ error.message }}</p>
  <p v-else-if="isLoading && isDelayElapsed">Loading...</p>
  <ul v-else-if="!isLoading">
   <li v-for="user in data">{{ user.name }}</li>
  </ul>
 </div>
</template>

<script>
 export default {
  data() => ({
   isLoadingfalse,
   errornull,
   datanull,
   isDelayElapsedfalse
  }),

  methods: {
   fetchUsers() {
    this.error = null
    this.isLoading = true
    this.isDelayElapsed = false
    getUsers()
     .then(users => {
      this.data = users
     })
     .catch(error => {
      this.error = error
     })
     .finally(() => {
      this.isLoading = false
     })
    setTimeout(() => {
     this.isDelayElapsed = true
    }, 200)
   }
  },

  created() {
   this.fetchUsers()
  }
 }
</script>
  • 封装后写法
<template>
 <Promised :promise="usersPromise">
  <!-- Use the "pending" slot to display a loading message -->
  <template v-slot:pending>
   <p>Loading...</p>
  </template>
  <!-- The default scoped slot will be used as the result -->
  <template v-slot="data">
   <ul>
    <li v-for="user in data">{{ user.name }}</li>
   </ul>
  </template>
  <!-- The "rejected" scoped slot will be used if there is an error -->
  <template v-slot:rejected="error">
   <p>Error: {{ error.message }}</p>
  </template>
 </Promised>
</template>

<script>
 export default {
  data() => ({ usersPromisenull }),

  created() {
   this.usersPromise = this.getUsers()
  }
 }
</script>

样式穿透

在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在 css 预处理器中使用才生效。

  • less 使用 /deep/
<style scoped lang="less">
 .content /deep/ .el-button {
  height: 60px;
 }
</style>
  • scss使用::v-deep
<style scoped lang="scss">
 .content ::v-deep .el-button {
  height60px;
 }
</style>
  • stylus使用>>>
<style scoped ang="stylus">
 外层 >>> .custon-components {
  height60px;
 }
</style>
posted @ 2021-02-15 11:11  大圣巴巴  阅读(465)  评论(0编辑  收藏  举报