adodo0829 / blog

搭建知识体系
29 stars 4 forks source link

vue 项目中常用的一些技巧 #60

Open adodo0829 opened 4 years ago

adodo0829 commented 4 years ago

vue 项目中常用的一些技巧汇总

持续更新中...

复用性技巧

混入(mixin对象)

通常使用局部混入对象(建议)

import mixinObj from '@/mixin/mixinObj'

export default {
  // 组件中添加 mixins 字段, 组件初始化的时候会进行合并
  mixins: [mixinObj]
}
// 合并策略: 同名属性 & 非同名
/* data 数据: 同名字段 组件内部优先
 * 生命周期钩子: 合并为数组, mixin优先调用
 * methods对象: 同名字段 组件内部优先
 * /

自定义指令(directive对象)

通常使用全局的自定义指令, 局部貌似没太大必要

/*
 * 节流指令, 防止函数调用过多
 */ 
import { DirectiveOptions } from 'vue'

export const throttle: DirectiveOptions = {
  // bind钩子 → 第一次绑定到元素时调用
  bind: (el, binding) => {
    // value: support 200, 400, ...2000ms, defalut 2s
    let time = binding.value as number
    if (!time) {
      time = 2000
    }
    let timeId: any
    el.addEventListener('click', (event) => {
      // first execute
      if (!timeId) {
        timeId = setTimeout(() => {
          timeId = null
        }, time)
      } else {
        event && event.stopImmediatePropagation()
      }
    }, true)
  }
}

// 将directives目录下的多个自定义指令批量导出
export * from './throttle'
export * from './debounce'

// main.ts 下批量导入并注入
import * as directives from '@/directives'
// Register global directives
Object.keys(directives).forEach(key => {
  Vue.directive(key, (directives as { [key: string]: DirectiveOptions })[key])
})

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')
}

} })


#### 过滤器(filter函数)
通常用来处理文本字符串(格式化处理...)

```js
import { getPadFunc } from '@/utils/tool'

// 整数位补0
export const padOneZero = getPadFunc(1) // 1 => 1.0
export const padTwoZero = getPadFunc(2) // 1 => 1.00

// 多行溢出 ...
export const ellipsis = (str: string, len = 40) => {
  if (!str.trim()) return ''
  if (str.length <= len) {
    return str
  } else {
    return str.substr(0, len) + '...'
  }
}

/**
 * 批量导入注册
 */
import * as filters from '@/filters' // filters: Modules对象

// Register global filter functions
Object.keys(filters).forEach(key => {
  Vue.filter(key, (filters as { [key: string ]: Function })[key])
})

插件(plugin对象)

插件本质就是一个需要显式声明install方法的对象

// 实现http插件, 暴露install方法
export const pluginHttp = {
    // options对象可选, Vue 必传
    install(Vue, options) {
        Vue.prototype.$http = Axios;

        // 其他功能...自行扩展
    }
}

// 使用插件, 这样就可用插件对象里面的东西了
// 不过这里我们就实现了一个全局的$http
import { pluginHttp } from '@/api/http'
Vue.use(pluginHttp)

插槽(slot组件)

当组件被多个地方使用

- 2.具名插槽 子组件通过 name 属性 来匹配父组件分发的内容
```html
<!-- 父组件: 添加 slot 属性来作为标识 -->
<div slot="header">我是 header 分发的内容 111</div>
<div slot="main">我是 main 分发的内容222</div>
<div slot="footer">我是 footer 分发的内容333</div>

<!-- 在2.6.0 以上使用的是 v-slot:header; 默认插槽为: v-slot:default -->

<!-- 子组件child: slot 组件来标识 -->
<template>
    <slot name="header"></slot>
    <slot name="main"></slot>
    <slot name="footer"></slot>
</template>
vue3.0以后 slot 和 slot="xxx",slot-scope 的方式会被废弃...
新的用法slot, v-slot:xxx || v-slot:default, v-slot:xxx="slotProps"
简写: v-slot:header 可以被重写为 #header

#### 函数组件(render函数应用)
简化 template 模板代码
```html
<!-- 函数组件 -->
<script>
export default {
    props:{
        type:{
            type:String,
            default:'normal'
        },
        text:{
            type:String,
            default:'按钮'
        }
    },
    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>

// 返回一个模块对象 const requireComponent = require.context('../components/base', false, /.vue$/)

requireComponent.keys().forEach(fileName => { const componentConfig = requireComponent(fileName) const componentName = fileName.replace(/^.\//, '').replace(/.vue/, '')

Vue.component(componentName, componentConfig.default || componentConfig) })


## 路由技巧

#### router 分模块管理
将不同模块的路由文件单独定义 module1.ts, module2.ts等...
```js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

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

const routes = [
    ...routerList
]

const router = new VueRouter({
    routes
})

export default router
adodo0829 commented 4 years ago

update1