vue中data的数据默认会进行双向数据绑定,若将大量和渲染无关的数据直接放置在data中,将会浪费双向数据绑定时所消耗的性能,将这些和渲染无关的数据进行抽离并配合Object.freeze进行处理。
table中columns数据可单独提取到一个外部js文件作为配置文件,也可以在当前.vue文件中定义一个常量定义columns数据,因为无论如何都是固定且不会修改的数据,应该使用Object.freeze进行包裹,既可以提高性能还可以将固定的数据抽离,一些下拉框前端固定的数据也建议此操作。
Object.freeze()方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。
freeze() 返回和传入的参数相同的对象const obj = { a: 1, b: 2 }
const obj2 = Object.freeze(obj)
obj === obj2 // => true
需要注意的是 Object.freeze() 冻结的是值,这时仍然可以将变量的引用替换掉,还有确保数据不会变才可以使用这个语法,如果要对数据进行修改和交互,就不适合使用冻结了。
export default {
 data() {
  return {
   // 冻结数据,避免vue进行响应式数据转换
   columns: Objet.freeze([{ title: "姓名", key: "name" }, { title: "性别", key: "gender" }])
  }
 }
}
例如远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的。
当一个按钮多次点击时会导致多次触发事件,可以结合场景是否立即执行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: {
            type: String,
            default: ‘‘
        }
    }
    computed: {
        id() {
            return this.userId || this.$route.params.id
        }
    }
}
但是这种方式不够优雅,那还有更优雅的处理方式吗?
正确的做法是通过路由props进行参数解耦
const router = new VueRouter({
 routes: [
  {
   path: "/user/:id",
   component: User,
   props: true
  }
 ]
})
将路由的props属性设置为true,就可以在组件内通过props接收$route.prams参数,在模板中就可以直接使用id了,不用关系这个id是从外部传递过来的还是通过路由参数传递过来的。
export default {
 props: {
  id: {
   type: String,
   default: ""
  }
 }
}
另外你还可以通过函数模式来传递props
const router = new VueRouter({
 routes: [
  {
   path: "/user/:id",
   component: User,
   props: route => ({
    id: route.query.id
   })
  }
 ]
})
由于函数式组件只是函数,因此渲染性能开销也低很多,适合依赖外部数据变化而变化的组件。
datathis上下文钩子函数<template functional>
 <button class="btn btn-primary" v-bind="data.attrs" v-on="listeners"><slot /></button>
</template>
watch是监听属性改变才触发,有时候我们系统在组件创建时也触发。
一般的想法是在
created生命周期函数里调用一次,以后watch监听变化时触发。
export default {
 data() {
  return {
   name: "Yori"
  }
 },
 watch: {
  name: {
   hander: "sayName",
   immediate: true // 立刻执行
  }
 },
 created() {
  // this.sayName()
 },
 methods: {
  sayName() {
   console.log(this.name)
  }
 }
}
watch监听对象时,属性内对象的属性改变无法监听到,这时我们就需要设置深度监听模式。
export default {
 data() {
  return {
   user: {
    name: {
     firstName: "张",
     lastName: "三"
    }
   }
  }
 },
 watch: {
  name: {
   handler: function() {},
   deep: true
  }
 }
}
watch运行以数组的形式传递的多个方法,方法会一一调用
可以是字符串函数名、匿名函数和对象形式。
export default {
 watch: {
  name: [
   "sayName",
   function(oldVal, newVal) {},
   {
    handler: function() {},
    immediate: true
   }
  ]
 }
}
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")
    }
   },
   deep: true
  }
 }
}
父子组件的加载顺序是先加载子组件,再加载父组件,但是父组件是无法知道子组件什么时候加载完成的,我们一般的做法是使用$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 {
   timer: null
  }
 },
 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 {
    key: 0
   }
  },
  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.jsimport 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 {
    visible: false
   }
  },
  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 {
  width: 300px;
  height: 300px;
  position: absolute;
  top: 50%;
  left: 50%;
  background: white;
  border: 1px solid #dddddd;
  transform: translate(-50%, -50%);
  z-index: 20;
 }
 .q-confirm-overlay {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.2);
  z-index: 10;
 }
</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: () => ({
   isLoading: false,
   error: null,
   data: null,
   isDelayElapsed: false
  }),
  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: () => ({ usersPromise: null }),
  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 {
  height: 60px;
 }
</style>
stylus使用>>><style scoped ang="stylus">
 外层 >>> .custon-components {
  height: 60px;
 }
</style>
原文:https://www.cnblogs.com/iPing9/p/14403400.html