VUE使用技巧(vue2.x)

vue使用技巧

data 数据冻结#

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

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

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

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

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

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

debounce 使用#

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

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

Copy
<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), }

路由参数解耦#

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

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

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

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

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

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

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

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

Copy
export default {  props: {   id: {    type: String,    default""   }  } }

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

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

函数式组件#

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

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

watch 高阶使用#

立刻执行#

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

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

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

深度监听#

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

Copy
export default {  data() {   return {    user: {     name: {      firstName: "张",      lastName: "三"     }    }   }  },  watch: {   name: {    handler: function() {},    deep: true   }  } }

触发执行多个方法#

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

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

监听多个属性#

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

Copy
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进行事件触发,通知父组件。

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

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

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

可编程的事件监听器#

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

Copy
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实例属性
  • 设置和销毁代码分散在各处

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

Copy
export default {  methods: {   start() {    const timer = setInterval(() => {     console.log(Date.now())    }, 1000)    this.$once("hook:beforeDestroy"() => {     clearInterval(timer)    })   }  } }

手动刷新组件#

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

  • 更新key强制渲染组件

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

Copy
<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 中不存在。

Copy
<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强制渲染

Copy
<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
Copy
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
Copy
<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,在合适的时机把数据回抛给你,并且帮你去展示你传入的视图。

  • 封装前写法
Copy
<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>
  • 封装后写法
Copy
<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/
Copy
<style scoped lang="less">  .content /deep/ .el-button {   height60px;  } </style>
  • scss使用::v-deep
Copy
<style scoped lang="scss">  .content ::v-deep .el-button {   height60px;  } </style>
  • stylus使用>>>
Copy
<style scoped ang="stylus">  外层 >>> .custon-components {   height60px;  } </style>
posted @   大圣巴巴  阅读(515)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
目录