记录日常开发中不常用的一些小技巧
require.context() 1. 场景:如页面需要导入多个组件,原始写法:
1 2 3 4 5 import titleCom from '@/components/home/titleCom' import bannerCom from '@/components/home/bannerCom' import cellCom from '@/components/home/cellCom' components:{titleCom,bannerCom,cellCom}
2. 这样就写了大量重复的代码,利用 require.context 可以写成
1 2 3 4 5 6 7 8 const path = require('path') const files = require.context('@/components/home', false, /\.vue$/) const modules = {} files.keys().forEach(key => { const name = path.basename(key, '.vue') modules[name] = files(key).default || files(key) }) components:modules
API 方法
1 2 3 4 5 6 7 实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用 require.context(directory,useSubdirectories,regExp) 接收三个参数: directory:说明需要检索的目录 useSubdirectories:是否检索子目录 regExp: 匹配文件的正则表达式,一般是文件名
watch 用法 常用用法 1 2 3 4 5 6 7 8 created(){ this.getList() }, watch: { inpVal(){ this.getList() } }
立即执行 可以直接利用 watch 的 immediate 和 handler 属性简写
1 2 3 4 5 6 watch: { inpVal:{ handler: 'getList', immediate: true } }
深度监听 watch 的 deep 属性,深度监听,也就是监听复杂数据类型
1 2 3 4 5 6 7 8 9 10 watch:{ inpValObj:{ handler(newVal,oldVal){ console.log(newVal) console.log(oldVal) }, deep:true } }
组件通信 props 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 数组:不建议使用 props:[] // 对象 props:{ inpVal:{ type:Number, //传入值限定类型 // type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认 required: true, //是否必传 default:200, //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[] validator:(value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } }
Attr 和 listeners attrs 场景:如果父传子有很多值,那么在子组件需要定义多个 props
attrs 获取未在 props 定义的值
1 2 3 4 5 6 7 // 父组件 <home title="这是标题" width="80" height="80" imgUrl="imgUrl"/> // 子组件 mounted() { console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80", imgUrl: "imgUrl"} }
相对应的如果子组件定义了 props, 打印的值就是剔除定义的属性
1 2 3 4 5 6 7 8 9 10 props: { width: { type: String, default: '' } }, mounted() { console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"} },
listeners 场景 子组件内需要调用父组件的方法解决: 父组件的方法可以通过 v-on=”listeners” 传入内部组件
1 2 3 4 5 6 7 // 父组件 <home @change="change"/> // 子组件 mounted() { console.log(this.$listeners) //即可拿到 change 事件 }
$root 1 2 3 4 5 6 7 // 父组件 mounted(){ console.log(this.$root) //获取根实例,最后所有组件都是挂载到根实例上 console.log(this.$root.$children[0]) //获取根实例的一级子组件 console.log(this.$root.$children[0].$children[0]) //获取根实例的二级子组件 }
.sync 在 vue@1.x 的时候曾作为双向绑定功能存在,即子组件可以修改父组件中的值;在 vue@2.0 的由于违背单项数据流的设计被干掉了;在 vue@2.3.0 + 以上版本又重新引入了这个 .sync 修饰符;
1 2 3 4 5 6 7 8 9 10 // 父组件 <home :title.sync="title" /> //编译时会被扩展为 <home :title="title" @update:title="val => title = val"/> // 子组件 // 所以子组件可以通过$emit 触发 update 方法改变 mounted(){ this.$emit("update:title", '这是新的title') }
路由传参方案 方案一
1 2 3 4 5 6 7 8 9 10 11 12 13 // 路由定义 { path: '/describe/:id', name: 'Describe', component: Describe } // 页面传参 this.$router.push({ path: `/describe/${id}`, }) // 页面获取 this.$route.params.id
方案二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 路由定义 { path: '/describe', name: 'Describe', omponent: Describe } // 页面传参 this.$router.push({ name: 'Describe', params: { id: id } }) // 页面获取 this.$route.params.id
方案三
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 路由定义 { path: '/describe', name: 'Describe', component: Describe } // 页面传参 this.$router.push({ path: '/describe', query: { id: id `} ) // 页面获取 this.$route.query.id
三种方案对比 方案二参数不会拼接在路由后面,页面刷新参数会丢失 方案一和三参数拼接在后面,丑,而且暴露了信息
render 函数 场景:有些代码在 template 里面写会重复很多,所以这个时候 render 函数就有作用啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // 根据 props 生成标签 // 初级 <template> <div> <div v-if="level === 1"> <slot></slot> </div> <p v-else-if="level === 2"> <slot></slot> </p> <h1 v-else-if="level === 3"> <slot></slot> </h1> <h2 v-else-if="level === 4"> <slot></slot> </h2> <strong v-else-if="level === 5"> <slot></slot> </stong> <textarea v-else-if="level === 6"> <slot></slot> </textarea> </div> </template> // 优化版,利用 render 函数减小了代码重复率 <template> <div> <child :level="level">Hello world!</child> </div> </template> <script type='text/javascript'> import Vue from 'vue' Vue.component('child', { render(h) { const tag = ['div', 'p', 'strong', 'h1', 'h2', 'textarea'][this.level-1] return h(tag, this.$slots.default) }, props: { level: { type: Number, required: true } } }) export default { name: 'hehe', data() { return { level: 3 } } } </script>
2.render 和 template 的对比 前者适合复杂逻辑,后者适合逻辑简单;后者属于声明是渲染,前者属于自定 Render 函数;前者的性能较高,后者性能较低。
路由的按需加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 webpack< 2.4 时 { path:'/', name:'home', components:resolve=>require(['@/components/home'],resolve) } webpack> 2.4 时 { path:'/', name:'home', components:()=>import('@/components/home') } import()方法由es6提出,import()方法是动态加载,返回一个Promise对象,then方法的参数是加载到的模块。类似于Node.js的require方法,主要import()方法是异步加载的。
动态组件 1 2 <component v-bind:is="currentTabComponent"></component>
但是这样每次组件都会重新加载,会消耗大量性能,所以 就起到了作用
1 2 3 4 <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
这样切换效果没有动画效果,这个也不用着急,可以利用内置的
1 2 3 4 5 <transition> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive> </transition>
函数式组件 1 2 3 <template functional> <div v-for="(item,index) in props.arr">{{item}}</div> </template>
mixins 场景:有些组件有些重复的 js 逻辑,如校验手机验证码,解析时间等,mixins 就可以实现这种混入 mixins 值是一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const mixin={ created(){ this.dealTime() }, methods:{ dealTime(){ console.log('这是mixin的dealTime里面的方法'); } } } export default{ mixins:[mixin] }
Vue.nextTick 2.1.0 新增 场景:页面加载时需要让文本框获取焦点 用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
1 2 3 4 5 mounted(){ //因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick this.$nextTick(() => { this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法 }) }
Vue.version 场景:有些开发插件需要针对不同 vue 版本做兼容,所以就会用到 Vue.version 用法:Vue.version () 可以获取 vue 版本
1 2 3 4 5 6 7 8 9 10 var version = Number(Vue.version.split('.')[0]) if (version === 2) { // Vue v2.x.x } else if (version === 1) { // Vue v1.x.x } else { // Unsupported versions of Vue }
监听性能
1 2 Vue.config.performance = true
Vue.config.warnHandler 2.4.0 新增 1. 场景:为 Vue 的运行时警告赋予一个自定义处理函数,只会在开发者环境下生效 2. 用法:
1 2 3 Vue.config.warnHandler = function (msg, vm, trace) { // `trace` 是组件的继承关系追踪 }
v-pre 场景:vue 是响应式系统,但是有些静态的标签不需要多次编译,这样可以节省性能
1 2 <span v-pre>{{ this will not be compiled }}</span> 显示的是{{ this will not be compiled }} <span v-pre>{{msg}}</span> 即使data里面定义了msg这里仍然是显示的{{msg}}
v-cloak 场景:在网速慢的情况下,在使用 vue 绑定数据的时候,渲染页面时会出现变量闪烁 用法:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕
1 2 3 4 5 6 7 8 9 // template 中 <div class="#app" v-cloak> <p>{{value.name}}</p> </div> // css 中 [v-cloak] { display: none; }
这样就可以解决闪烁,但是会出现白屏,这样可以结合骨架屏使用
v-once 场景:有些 template 中的静态 dom 没有改变,这时就只需要渲染一次,可以降低性能开销
1 2 <span v-once> 这时只需要加载一次的标签</span>
v-once 和 v-pre 的区别: v-once 只渲染一次;v-pre 不编译,原样输出
事件修饰符 1 2 3 4 5 .stop:阻止冒泡 .prevent:阻止默认行为 .self:仅绑定元素自身触发 .once: 2.1.4 新增,只触发一次 .passive: 2.3.0 新增,滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用
Vue.$router 1 2 3 this.$router.push():跳转到不同的url,但这个方法回向history栈添加一个记录,点击后退会返回到上一个页面 this.$router.replace():不会有记录 this.$router.go(n):n可为正数可为负数。正数返回上一个页面,类似 window.history.go(n)
Vue.$route 1 2 this.$route.params.id:获取通过 params 或/:id传参的参数 this.$route.query.id:获取通过 query 传参的参数
调试 template 场景:在 Vue 开发过程中,经常会遇到 template 模板渲染时 JavaScript 变量出错的问题,此时也许你会通过 console.log 来进行调试 这时可以在开发环境挂载一个 log 函数
1 2 3 4 5 // main.js Vue.prototype.$log = window.console.log; // 组件内部 <div>{{$log(info)}}</div>
vue-loader 小技巧 preserveWhitespace 场景:开发 vue 代码一般会有空格,这个时候打包压缩如果不去掉空格会加大包的体积 配置 preserveWhitespace 可以减小包的体积
{ vue: { preserveWhitespace: false } }
场景:以前在写 Vue 的时候经常会写到这样的代码:把图片提前 require 传给一个变量再传给组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // page 代码 <template> <div> <avatar :img-src="imgSrc"></avatar> </div> </template> <script> export default { created () { this.imgSrc = require('./assets/default-avatar.png') } } </script>
现在:通过配置 transformToRequire 后,就可以直接配置,这样 vue-loader 会把对应的属性自动 require 之后传给组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // vue-cli 2.x在vue-loader.conf.js 默认配置是 transformToRequire: { video: ['src', 'poster'], source: 'src', img: 'src', image: 'xlink:href' } // 配置文件,如果是vue-cli2.x 在vue-loader.conf.js里面修改 avatar: ['default-src'] // vue-cli 3.x 在vue.config.js // vue-cli 3.x 将transformToRequire属性换为了transformAssetUrls module.exports = { pages, chainWebpack: config => { config .module .rule('vue') .use('vue-loader') .loader('vue-loader') .tap(options => { options.transformAssetUrls = { avatar: 'img-src', } return options; }); } } // page 代码可以简化为 <template> <div> <avatar img-src="./assets/default-avatar.png"></avatar> </div> </template>
为路径设置别名 1. 场景:在开发过程中,我们经常需要引入各种文件,如图片、CSS、JS 等,为了避免写很长的相对路径(../),我们可以为不同的目录配置一个别名
2.vue-cli 2.x 配置
1 2 3 4 5 6 7 8 9 // 在 webpack.base.config.js中的 resolve 配置项,在其 alias 中增加别名 resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), } },
3.vue-cli 3.x 配置
1 2 3 4 5 6 7 8 9 10 11 12 // 在根目录下创建vue.config.js var path = require('path') function resolve (dir) { console.log(__dirname) return path.join(__dirname, dir) } module.exports = { chainWebpack: config => { config.resolve.alias .set(key, value) // key,value自行定义,比如.set('@@', resolve('src/components')) } }
img 加载失败 场景:有些时候后台返回图片地址不一定能打开,所以这个时候应该加一张默认图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // page 代码 <img :src="imgUrl" @error="handleError" alt=""> <script> export default{ data(){ return{ imgUrl:'' } }, methods:{ handleError(e){ e.target.src=reqiure('图片路径') //当然如果项目配置了transformToRequire,参考上面 27.2 } } } </script>
CSS 局部样式 1.Vue 中 style 标签的 scoped 属性表示它的样式只作用于当前模块,是样式私有化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 原始代码 <template> <div class="demo"> <span class="content"> Vue.js scoped </span> </div> </template> <style lang="less" scoped> .demo{ font-size: 16px; .content{ color: red; } } </style> // 浏览器渲染效果 <div data-v-fed36922> Vue.js scoped </div> <style type="text/css"> .demo[data-v-039c5b43] { font-size: 14px; } .demo .content[data-v-039c5b43] { //.demo 上并没有加 data 属性 color: red; } </style>
deep 属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 上面样式加一个 /deep/ <style lang="less" scoped> .demo{ font-size: 14px; } .demo /deep/ .content{ color: blue; } </style> // 浏览器编译后 <style type="text/css"> .demo[data-v-039c5b43] { font-size: 14px; } .demo[data-v-039c5b43] .content { color: blue; } </style>