4
优雅永不过时

​ ——卡密尔

以下不讨论加入TypeScript 和 JSX/TSX

一、使用 require.context 实现前端工程自动化

require.context 是一个 Webpack 提供的Api,通过执行require.context函数获取一个特定的上下文,主要是用于实现自动化导入模块。

什么时候用? 当一个js文件里面需要手动引入过多的其他文件时,就可以使用。

在Vue项目开发过程中,我们可能会遇到这些可能会用到require.context的场景

  1. 当页面内容较多,我们把页面细化为多个组件,然后再通过 import 引入到 index.js 主入口文件中的时候。
  2. 开发了一些基础组件,然后把所有组件都导入到 index.js 中,然后再将所有组件进行全局注册的时候。
  3. vuex 分割模块后,将各模块引入到 index.js 主文件中的时候。

对于上述的几个场景,如果我们要导入的文件比较少的情况下,通过 import 一个一个去导入还可以接受,但对于量比较大的情况,就变成了纯体力活,并且每次修改都需要在主入口文件内进行调整。这样做一点都不优雅,这时候我们就可以通过 require.context 去简化这个过程。

以第2条为例,讲一下 require.context 的用法

常规用法

在这里插入图片描述

组件通过常规方式全局注册(组件越多越繁琐)

// index.js
import Vue from 'vue'
import BaseIcon from './BaseIcon.vue'
import BaseButton from './BaseButton.vue'
import BaseInput from './BaseInput.vue'
import BaseLoading from './BaseLoading.vue'

const components = [
  BaseIcon,
  BaseButton,
  BaseInput,
  BaseLoading
]

components.forEach((component) => {
  Vue.component(component.name, component)
})

// --------------分割线--------------
// 而更多的同学可能会像下面这样进行注册
Vue.component('base-icon', BaseIcon)
Vue.component('base-button', BaseButton)
Vue.component('base-input', BaseInput)

使用 require.context

require.context 基本语法

require.context(directory, useSubdirectories, regExp);
directory:要搜索的文件夹目录

useSubdirectories:是否还去搜索它的子级目录

regExp:一个匹配要搜索文件的正则表达式。

通过 require.context 注册组件

import Vue from 'vue'
// 这里暂不考虑 JSX 和 TSX
const context = require.context('./', false, /\.vue$/)

context.keys().forEach((key) => {
  const component = context(key).default
  Vue.component(component.name, component)
})
context.keys() 会返回一个数组,包含所有匹配到的文件的路径

context(key)可以获取到对应的文件 .default表示 export default 导出的内容

现在代码简洁了很多,也更加优雅。

二、 v-model 原来可以这么用

v-model 属于语法糖

在用Vue开发前端时,不论使用原生还是封装好的UI库,对于表单组件,一般都会使用到 v-model ,而为了更好的表达 value 属性的用处(类似于单选框、复选框),也会进行适当的自定义。但是对于自定义组件同样可以使用 v-model

vue 对 v-model 的基本定义

一个组件上的 v-model 默认是在组件上面定义一个名为 value 的 prop ,同时对外暴露一个名为 input 的事件。

像这样:

<template>
  <div class="base-input">
    <input type="text" :value="value" @change="handleChange" />
  </div>
</template>
<script>
export default {
  name: 'BaseInput',
  props: {
    // 名为value 的属性
    value: {
      type: String,
      default: '',
    },
  },
  methods: {
    handleChange(e) {
      // 暴露一个input时间
      this.$emit('input', e.target.value)
    },
  },
}
</script>

用起来就是这样:

<BaseInput v-model="something" />

v-model 进行自定义

通过设置 model 选项, 以自定义开关组件为例

<template>
  <div :class="['base-switch', active && 'base-switch-active']" @click="handleClick">
    <div class="base-switch-pie"></div>
  </div>
</template>

<script>
export default {
  name: 'BaseSwitch',
  // 通过model属性自定义 属性名和事件名
  model: {
    event: 'change',
    prop: 'active',
  },
  props: {
    // 虽然自定义了 active属性,仍然要在 props 选项里面声明
    active: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    handleClick() {
      // 暴露change事件
      this.$emit('change', !this.active)
    },
  },
}
</script>

这么用:

<BaseSwitch v-model="isActive" />

这样的开关组件是不是更简洁了。

三、使用 .sync ,更优雅的实现数据“双向绑定”

在 Vue 中,props 属性是单向数据传输的,父级的 prop 的更新会影响到子组件,但是反过来不行。可是有些情况,我们需要对prop进行“双向绑定”。

上面,我们提到了使用 v-model 实现“双向绑定”。但有时候我们希望一个组件可以实现多个数据的“双向绑定”,而在 Vue2.x 中 v-model 一个组件只能有一个,这时候就需要使用到 .sync

.syncv-model 的异同

相同点:

  • 两者的本质都是语法糖,目的都是实现组件与外部数据的“双向绑定”
  • 两者都是通过 属性+事件 来实现的

不同点:

  • 一个组件只能定义一个 v-model ,但可以定义多个.sync
  • 定义方式不同,v-model 默认事件为 input ,可以通过配置 model 选项来修改,.sync事件名称固定为update:属性名

使用 .sync

比如写一个遮罩层

<template>
  <div class="base-mask" v-if="showMask" @click.stop="handleClickMask">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'BaseMask',
  props: {
    showMask: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    handleClickMask() {
      this.$emit('update:showMask', !this.showMask)
    },
  },
}
</script>

这样去使用

<template>
  <div class="about">
    <BaseSwitch v-model="isActive" />
    <BaseMask :show-mask.sync="isActive">这里可以加一些东西</BaseMask>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isActive: false,
    }
  },
}
</script>
代码简洁的同时,也更加优雅

四、动态组件,更优雅的按条件渲染页面

有这么一个场景,需要根据条件(多条件),在页面显示相应的内容,如果用 v-if 来判断要显示哪个组件,那就会是这个样子:

<template>
  <div>
    <ComponentA v-if="type === 'a'" />
    <ComponentB v-else-if="type === 'b'" />
    <ComponentC v-else-if="type === 'c'" />
    <!-- 这里还有 v-else-if -->
    <ComponentG v-else />
  </div>
</template>

条件越多,上面代码中那一堆 v-ifv-else-if 就越繁琐,时刻记得要优雅,这时候就该动态组件上场了。

<template>
  <div>
    <button v-for="type in types" :key="type" @click="currentType = type">
      {{ type }}
    </button>
    <component :is="currentComponent"></component>
  </div>
</template>

<script>
// 组件不多的情况下手动引入即可
const context = require.context('./', false, /\.vue$/)
let components = {}
context.keys().forEach((key) => {
  if (key.includes('index')) return
  const component = context(key).default
  components[component.name] = component
})

export default {
  components,
  data() {
    return {
      typeComponents: components,
      types: {},
      currentType: '',
    }
  },
  computed: {
    currentComponent() {
      return this.typeComponents[this.currentType]
    },
  },
  created() {
    this.types = Object.keys(this.typeComponents)
    this.currentType = this.types[0] // 默认第一个选项
  },
}
</script>

五、 mixins ,更高效的实现组件内容的复用

mixinsVue 提供的一种混合混入机制,可以更高效的实现组件内容的复用。

场景示例

在h5页面开发中,安卓端可以通过app提供的api实现像IOS那样,软键盘弹起时不遮挡底部内容,但是并不是任何页面都适用这种情况,所以就可以在需要的页面使用这个功能,并在离开这个页面的时候,关闭这个功能。

但是如果在每个需要的组件里面都去实现一段监听代码,代码重复太多了,此时就可以使用混入来解决这个问题

// 混入代码 upward-mixins.js 简例

export default {
  beforeCreate() {
    // ...省略
    this.$util.needUpwardOnAndroid(true)
  },
  beforeDestroy() {
    // ...省略
    this.$util.needUpwardOnAndroid(false)
  }
}

在需要使用这种功能的组件中使用即可

<template>
  <div></div>
</template>
<script>
import upwardMixins from '../mixins/upward-mixins'
export default {
  // mixins 传入一个数组,可以有多个混入对象
  mixins: [upwardMixins],
  // ...省略
}
</script>

混入规则

Vue 中,一个混入对象可以包含任意组件选项,但是对于不同的组件选项,会有不同的使用策略。

  1. data 选项,在混入时会进行递归合并,如果两个属性发生冲突,则以组件本身为主

    const mixin1 = {
      data() {
        return {
          a: 1,
          b: 2
        }
      }
    }
    
    export default {
      mixins: [mixin1],
      data() {
        return {
          a: 2
        }
      },
      created() {
        console.log(this.a, this.b)  // 输出 2,2
      }
    }
  2. 生命周期钩子函数

    对于生命周期钩子函数,混入时会依次执行。混入对象里面的钩子函数会优先于组件的钩子函数执行。如果一个组件混入了多个对象,对于混入对象里面的同名钩子函数,将按照数组顺序依次执行:

    const mixin1 = {
      created() {
        console.log('第一个输出')
      }
    }
    
    const mixin2 = {
      created() {
        console.log('第二个输出')
      }
    }
    export default {
      mixins: [mixin1, mixin2],
      created() {
        console.log('第三个输出')
      }
    }
  3. 其他选项,对于值为对象的选项,如methodscomponentsfilterdirectivesprops 等等,会被合并为同一个对象。两个对象键名冲突时,取组件本身对象的键值对。

全局混入

混入也可以进行全局注册。一旦使用全局混入,那么混入的选项将在所有的组件内生效,代码简例:

// 仅作简单的示例
const someMixin = {
  // ...一些内容
}

Vue.mixin(someMixin)

请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。

简例代码: https://github.com/ahh666/vue...


艾欢欢
415 声望20 粉丝