前端开发那些事儿零基础转行前端前端启示录

Vue开发技巧总结

2020-08-07  本文已影响0人  前端辉羽

本文目录:

1.sync优雅更新props

更新 prop 在业务中是很常见的需求,但在子组件中不允许直接修改 prop,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit 触发自定义事件,在父组件中接收该事件的传值来更新 prop。
child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('change-title', 'hello')
        }
    }
}

parent.vue:

<child :title="title" @change-title="changeTitle"></child>
export default {
    data(){
        return {
            title: 'title'
        }  
    },
    methods: {
        changeTitle(title){
            this.title = title
        }
    }
}

这种做法没有问题,我也常用这种手段来更新 prop。但如果你只是想单纯的更新 prop,没有其他的操作。那么 sync 修饰符能够让这一切都变得特别简单。
parent.vue:

<child :title.sync="title"></child>

child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('update:title', 'hello')
        }
    }
}

只需要在绑定属性上添加 .sync,在子组件内部就可以触发 update:属性名 来更新 prop。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。

2.卸载watch观察

通常定义数据观察,会使用选项的方式在 watch 中配置:

export default {
    data() {
        return {
            count: 1      
        }
    },
    watch: {
        count(newVal) {
            console.log('count 新值:'+newVal)
        }
    }
}

除此之外,数据观察还有另一种函数式定义的方式:

export default {
    data() {
        return {
            count: 1      
        }
    },
    created() {
        this.$watch('count', function(){
            console.log('count 新值:'+newVal)
        })
    }
}

它和前者的作用一样,但这种方式使定义数据观察更灵活,而且 $watch 会返回一个取消观察函数,用来停止触发回调:

let unwatchFn = this.$watch('count', function(){
    console.log('count 新值:'+newVal)
})
this.count = 2 // log: count 新值:2
unwatchFn()
this.count = 3 // 什么都没有发生...

$watch 第三个参数接收一个配置选项:

this.$watch('count', function(){
    console.log('count 新值:'+newVal)
}, {
    immediate: true // 立即执行watch
})

3.巧用template

相信 v-if 在开发中是用得最多的指令,那么你一定遇到过这样的场景,多个元素需要切换,而且切换条件都一样,一般都会使用一个元素包裹起来,在这个元素上做切换。

<div v-if="status==='ok'">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</div>

如果像上面的 div 只是为了切换条件而存在,还导致元素层级嵌套多一层,那么它没有“存在的意义”。
我们都知道在声明页面模板时,所有元素需要放在 <template> 元素内。除此之外,它还能在模板内使用,<template> 元素作为不可见的包裹元素,只是在运行时做处理,最终的渲染结果并不包含它。

<template>
    <div>
        <template v-if="status==='ok'">
          <h1>Title</h1>
          <p>Paragraph 1</p>
          <p>Paragraph 2</p>
        </template>
    </div>
</template>

同样的,我们也可以在 <template> 上使用 v-for 指令,这种方式还能解决 v-for 和 v-if 同时使用报出的警告问题。

<template v-for="item in 10">
    <div v-if="item % 2 == 0" :key="item">{{item}}</div>
</template>

4.过滤器复用

过滤器被用于一些常见的文本格式化,被添加在表达式的尾部,由“管道”符号指示。

<div>{{ text | capitalize }}</div>
export default {
    data() {
        return {
            text: 'hello'
        }  
    },
    filters: {
        capitalize: function (value) {
            if (!value) return ''
            value = value.toString()
            return value.charAt(0).toUpperCase() + value.slice(1)
         }
    }
}

试想一个场景,不仅模板内用到这个函数,在 method 里也需要同样功能的函数。但过滤器无法通过 this 直接引用,难道要在 methods 再定义一个同样的函数吗?
要知道,选项配置都会被存储在实例的 options 中,所以只需要获取 this.options.filters 就可以拿到实例中的过滤器。

export default {
    methods: {
        getDetail() {
            this.$api.getDetail({
                id: this.id
            }).then(res => {
                let capitalize = this.$options.filters.capitalize
                this.title = capitalize(res.data.title)
            })
        }
    }
}

除了能获取到实例的过滤器外,还能获取到全局的过滤器,因为 this.$options.filters 会顺着 proto 向上查找,全局过滤器就存在原型中。

5.长列表性能优化

我们应该都知道vue会通过object.defineProperty对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要vue来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止vue劫持我们的数据呢?可以通过object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

另外需要说明的是,这里只是冻结了users的值,引用不会被冻结,当我们需要reactive数据的时候,我们可以重新给users赋值。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  },
  methods:{
    // 改变值不会触发视图响应
    this.data.users[0] = newValue
    // 改变引用依然会触发视图响应
    this.data.users = newArray
  }
};

6.监听组件的生命周期

比如有父组件Parent和子组件Child,如果父组件监听到子组件挂载mounted就做一些逻辑处理,常规的写法可能如下:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit("mounted");
}

这里提供一种特别简单的方式,子组件不需要任何处理,只需要在父组件引用的时候通过@hook来监听即可,代码重写如下:

<Child @hook:mounted="doSomething"/>

当然这里不仅仅是可以监听mounted,其它的生命周期事件,例如:created,updated等都可以.

7.批量引用组件

场景:如页面需要导入多个组件,原始写法:

import titleCom from '@/components/home/titleCom'
import bannerCom from '@/components/home/bannerCom'
import cellCom from '@/components/home/cellCom'
components:{titleCom,bannerCom,cellCom}

我们写了一堆基础UI组件,然后每次我们需要使用这些组件的时候,都得先import,然后声明components,很繁琐!秉持能偷懒就偷懒的原则,我们要想办法优化!
我们需要借助一下神器webpack,使用 require.context() 方法来创建自己的(模块)上下文,从而实现自动动态require组件。这个方法需要3个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。
我们在components文件夹添加一个叫global.js的文件,在这个文件里借助webpack动态将需要的基础组件统统打包进来。

import Vue from 'vue'
function capitalizeFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1)
}
const requireComponent = require.context(
  '.', false, /\.vue$/
   //找到components文件夹下以.vue命名的文件
)
requireComponent.keys().forEach(fileName => {
  const componentConfig = requireComponent(fileName)
  const componentName = capitalizeFirstLetter(
    fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
    //因为得到的filename格式是: './baseButton.vue', 所以这里我们去掉头和尾,只保留真正的文件名
  )
  Vue.component(componentName, componentConfig.default || componentConfig)
})

最后我们在main.js中import 'components/global.js',然后我们就可以随时随地使用这些基础组件,无需手动引入了。

8.全局组件注册

组件是我们非常常用的东西,很多人使用组件都是通过一个一个文件去引用和注册。如果一个组件在整个项目里面的使用次数较多,每一次使用都需要引用并注册,就会显得特别麻烦

一般组件应用弊端
傻瓜式,太笨拙
繁琐,低效

<template>
  <div>
    <h1>I am HelloWorld</h1>
    <Child1></Child1>
  </div>
</template>

<script>
import Child1 from './child1.vue'   // 引入
export default {
  name: 'HelloWorld',
  data(){
    return{
    }
  },
  components:{   // 注册
    Child1
  },
  props: {
    msg: String
  },
  methods:{
  }
}
</script>

<style scoped lang="less">
</style>

当我们在项目需要重复多次使用该组件,会导致出现很多重复的引入和注册代码,既繁琐又不雅观。因此我们可以通过一个全局的Js文件来管理,将需要多次使用的组件进行全局注册
创建全局.js文件管理全局组件


image.png
// 1 - globalComponent.js

import Vue from 'vue' // 引入vue

// 处理首字母大写 abc => Abc
function changeStr(str){
    return str.charAt(0).toUpperCase() + str.slice(1)
}

/*
    require.context(arg1,arg2,arg3)
        arg1 - 读取文件的路径
        arg2 - 是否遍历文件的子目录
        arg3 - 匹配文件的正则
    关于这个Api的用法,建议小伙伴们去查阅一下,用途也比较广泛
*/
const requireComponent = require.context('.', false, /\.vue$/)
console.log('requireComponent.keys():',requireComponent.keys())  // 打印
requireComponent.keys().forEach(fileName => {
    const config = requireComponent(fileName)
    console.log('config:',config)  // 打印
    const componentName = changeStr(
        fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')   // ./child1.vue => child1
    )
    
    Vue.component(componentName, config.default || config) // 动态注册该目录下的所有.vue文件
})
// 2 - 将globalComponent.js引入main.js
import global from './components/globalComponent'
// 3 - 使用这类组件不再需要引入和注册,直接标签使用即可
<template>
  <div>
    <h1>I am HelloWorld</h1>
    <Child1></Child1>
  </div>
</template>

运行程序,我们看下是否能够正常显示并分析两句打印


image.png

9.路由分区以及动态添加路由

假设我们有很多路由,每一个路由都通过傻瓜式的引入方式,会导致整个项目代码量增多,繁琐,更重要的一点是增加后期维护的难度。因此我们也可以通过上面类似的方式,对路由的引入和使用进行管理,实现分区引入路由,将不同功能下的路由进行区分,通过动态的方式进行引入,即方便快捷又增加可维护

创建专门的路由.js文件管理所有的路由

总路由管理文件 - index.js
分区路由
    - index.routes.js
    - login.routes.js
在大型项目中,往往会有很多互不关联的模块,例如电商平台中的商城,个人信息,这种情况下就可以对路由进行分区
image.png
// 分区路由文件写法

export default {
    path:'/index',
    name:'Index',
    component: () => import('../views/Index.vue'),  // 懒加载式引入,当跳转到时才进行引入chunk
    children: [...]
}
// 总路由管理文件 index.js 写法
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routerList = []  // 路由数组 - 存放所有路由
function importAll(routerArr){
    // 该函数用于将所有分区路由中的路由添加到路由数组
    routerArr.keys().forEach( key => {
        console.log(key)
        routerList.push(routerArr(key).default)
    })
}
importAll(require.context('.',true,/\.routes\.js/))

const routes = [
    ...routerList
]

const router = new VueRouter({
    routes
})

export default router

运行程序,我们看下是否能够正常显示并分析两句打印


image.png

优化之后的代码,会更灵活,更具有观赏性,既便捷高效,又方便维护

10.拯救繁乱的template

很多人在写组件的时候,会依赖脚手架中的<template></template>标签,其实template也存在一定的缺陷,例如:
template里存在一值多判断
过多使用template会使代码冗余,杂乱
template里存在一值多判断
过多使用template会使代码冗余,杂乱

VUE给我们提供了一个render函数,我们可以通过这个函数巧妙的解决template造成的问题
实战 - 处理多个button

<template>
  <div>
    <h1>I am Home</h1>
    <!-- 假设按钮有多种类型,通过value来显示不同类型 -->
    <div v-if='value === 1'>
      <button>button1</button>
    </div>
    <div v-else-if='value === 2'>
      <button>button2</button>
    </div>
    <div v-else>
      <button>button3</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data(){
    return{
        value:1
    }
  },
  methods:{
  }
}
</script>

<style scoped lang="less">
</style>

上面这种写法,当出现多种类型的button,就会显得杂乱无章,当然,很多人会选择去封装一个button组件,那么这个组件的封装,又是一个技巧点,利用VUE的render函数,减少不必要的template,因此ru我们可以这样写

// 创建一个button.vue文件 写法如下

<script>
export default {
    props:{
        type:{
            type:String,
            default:'normal'
        },
        text:{
            type:String,
            default:'button'
        }
    },
    render(h){
        /*
            h 类似于 createElement, 接受2个参数
            1 - 元素
            2 - 选项
         */
        return h('button',{
            // 相当于 v-bind:class
            class:{
                btn:true,
                'btn-success':this.type === 'success',
                'btn-danger':this.type === 'danger',
                'btn-warning':this.type === 'warning',
                'btn-normal':this.type === 'normal',
            },
            domProps:{
                innerText: this.text || '默认'
            },
            on:{
                click:this.handleClick
            }
        })
    },
    methods:{
        handleClick(){
            this.$emit('myClick')
        }
    }
}
</script>

<style scoped>
.btn{
    width: 100px;
    height:40px;
    line-height:40px;
    border:0px;
    border-radius:5px;
    color:#ffff;
}
.btn-success{
    background:#2ecc71;
}
.btn-danger{
    background:#e74c3c;
}
.btn-warning{
    background:#f39c12;
}
.btn-normal{
    background:#bdc3c7;
}
</style>
//  引入

<template>
  <div>
    <h1>I am Home</h1>
    <!-- 按钮根据value显示不同类型的button -->
    <Button type='success' text='button1' @myClick='...'></Button>
  </div>
</template>

<script>
import Button from './button.vue'
export default {
  name: 'Home',
  data(){
    return{
        value:1
    }
  },
  components:{
      Button
  },
  methods:{
  }
}
</script>
<style scoped lang="less">
</style>

上面这种写法,根据value来显示不同类型的button,我们只需要通过value去修改type,text等,就可以实现这一目的,而不需要去创建多个<button>,通过v-if去判断
优化之后的代码,避免了一值多判断的缺点,减少冗余,更加灵活, 这种方式较适合业务简单,使用次数多的组件

11.高精度全局权限处理

权限的控制由前端处理的场景很多,例如根据后台返回内容,判断该人是否对此功能有权限,进而去修改元素v-if / v-show,这种情况下,当这个功能在多处地方出现,就会导致我们做很多很多不必要的重复代码,如果判断条件繁琐的情况,更加冗余,代码量也会增加很多。因此我们可以造一个小车轮,挂在全局上对权限进行处理

实战 - 处理某按钮显示权限问题
这种场景出现几率极高,尤其是处理含有多种角色的项目,如果这一类型的权限判断有多次处理,每一次出现都经历判断的话,代码将会异常难看且冗余,因此我们可以通过全局权限判断来处理

/*
    在项目里新建一个common文件夹用于存放全局 .js 文件
    这种全局文件夹做法相当普遍,一般项目里都应该有这样一个文件夹来管理全局的东西
*/
// common/jurisdiction.js  用于存放与权限相关的全局函数/变量
export function checkJurisdiction(key) {
    // 权限数组
    let jurisdictionList = ['1', '2', '3', '5']
    let index = jurisdictionList.indexOf(key)
    console.log('index:',index)
    if (index > -1) {
        // 有权限
        return true
    } else {
        // 无权限
        return false
    }
}
// 将全局权限Js挂载到全局中
// main.js

import { checkJurisdiction } from './common/jurisdiction'

// 优雅操作 - VUE自定义指令
Vue.directive('permission',{
  inserted(el, binding){
    // inserted → 元素插入的时候
    
    let permission = binding.value // 获取到 v-permission的值

    if(permission){
      let hasPermission = checkJurisdiction(permission)
      if(!hasPermission){
        // 没有权限 移除Dom元素
        el.parentNode && el.parentNode.removeChild(el)
      }
    }else{
      throw new Error('需要传key')
    }
  }
})
// 使用方式

<template>
  <div>
    <h1>I am Home</h1>
    <!-- 按钮根据value -->
    <div v-permission="'10'">
      <button>权限1</button>
    </div>
    <div v-permission="'5'">
      <button>权限2</button>
    </div>
  </div>
</template>

// 无需再通过value去判断,直接通过v-permission的值进行判断即可

运行程序,我们看下是否能够正常显示并分析打印


image.png

可以看到 v-permission = "'10'"是没有权限且不显示,v-permission = "'5'"是具有权限且显示

上一篇 下一篇

猜你喜欢

热点阅读