在 Vue 项目开发中,很容易产生一些问题,好比代码重复、繁杂等,其实 Vue 项目开发中有不少技巧可使用,本文将列出一些简单且很好用的几个技巧,帮助咱们写出漂亮的代码。用到的技术栈是 Vue2.0 + TypeScript + vue-property-decorator + ElementUI。将用到如下几个技巧:javascript
$attrs
和 $listeners
进行多层级的数据和事件传递$attrs
和 $listeners
进行多层级的数据和事件传递先聊聊如何传递 Prop,能够分为静态和动态的 Prop:css
<!-- 静态的prop -->
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态的prop -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态的prop传递能够简写成 -->
<blog-post :title="post.title"></blog-post>
<!-- 须要传递多个prop的时候,能够一块儿写在v-bind上 -->
<blog-post v-bind="{ editable, title: post.title}"></blog-post>
复制代码
了解了 Props 的传递方式,在看看官方文档是怎么定义 $attrs
的, 在尤大大的文档中这样介绍了 $attrs
:html
$attrs
: 包含了父做用域中不做为 prop 被识别 (且获取) 的 attribute 绑定 class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含全部父做用域的绑定 (class 和 style 除外),而且能够经过v-bind="$attrs"
传入内部组件vue
$attrs
包含了传入到父做用域中没有在 props 声明的其余 props,所以咱们能够用 $attrs
去代替那些父组件中不须要的而子组件须要的 props, 经过 v-bind="$attrs"
统一传递给后代。这样就避免了一个个声明而后再一个个传递。java
<blog-post v-bind="$attrs"></blog-post>
复制代码
上面这一行代码就经过 v-bind="$attrs"
的方式将本做用域中不做为 prop 的其余属性传递给了 blog-post 组件。webpack
父组件经过 $attrs
传递给后代组件后,后代组件若是想经过触发事件来更新父组件状态该如何处理?若是一级一级地往上 emit 事件,会不会弄得代码太繁琐复杂了?在 Vue 中能够经过 $listeners
解决这个问题,先看看官方文档关于 $listeners
的说明:web
包含了父做用域中的 (不含
.native
修饰器的)v-on
事件监听器。它能够经过v-on="$listeners"
传入内部组件——在建立更高层次的组件时很是有用。正则表达式
文档中说了 $listeners
包含了父做用域中的事件监听器。意思就是 $listeners
表示了父组件中的事件监听器集合,只要是触发父组件的事件,而不是本身的,就能够用一个 v-on="$listeners"
表示。markdown
<!-- 父组件(第一层组件) -->
<componentA @on-change="handleChange" v-bind="{ editable, title: post.title}" />
<!-- 中间层的组件 -->
<Child v-bind="$attrs" v-on="$listeners"/>
<!-- 数据传递的目标组件,事件触发的组件 -->
<div @click="handleClick">{{ title }} </div>
<script> export default { props: { title: String } handleClick() { this.$emit('on-change', 'New Title'); } } </script>
复制代码
上面的代码示例中,中间层的组件内经过 v-bind="$attrs"
将其他的 Prop 传递给了 Child 组件,再经过 v-on="$listeners"
绑定父做用域中的事件监听器,一旦 emit 就会传给了父组件。ide
有不少这样的场景,父组件须要传递数据给子组件,且在子组件触发数据更新的时候,立刻反馈给父组件,父组件数据更新,单向数据流向子组件,最后子组件更新。一般状况用 props + $emit
的方式去更新状态,可是这种处理方式有点笨拙,且不易维护,因此能够经过实现数据的“双向绑定”来提升代码的可维护性。能够经过这如下方式去实现:
在 v-bind prop
的时候添加 .sync 修饰符,赋新值的时候用 this.$emit('update:propName', newValue)
<!-- .sync是 v-on:update这种模式的一种缩写 -->
<Child v-on:update:title="title" />
<!-- 至关于 -->
<Child :title.sync="title" />
复制代码
若是要更新上述代码中的 title 值,只须要 this.$emit('update:title', '新标题')
,完成数据更新。
model 是2.2.0+ 新增的选项,一个组件上的 v-model 默认会利用名为 value 的 Prop 和名为 input 的事件, 而 model 选项能够规定 Prop 名称和事件名称来实现 v-model,好处是在实现 v-model 的同时也避免了 Prop 和事件名的冲突。
<!-- 父组件 -->
<Model v-model="checked"/>
<!-- Model组件 -->
<div @click="handleClick">
<p>自定义组件的 v-model</p>
checked {{checked}}
</div>
<script lang="ts"> export default { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, methods: { handleClick() { this.$emit('change', !this.checked); } } 复制代码
在上述代码中,只须要在 model 选项中添加 prop 和 event,就能够实现了 v-model。而在 Vue + TS 项目中 vue-property-decorator 中提供了 Model 的装饰器,须要这么写:
@Model('change', { type: Boolean }) readonly checked!: boolean
handleClick() {
this.$emit('change', !this.checked);
}
复制代码
只须要经过 .sync 和 model 就能够实现数据的“双向绑定”,这样书写代码能够必定程度上减小咱们的代码,并且另代码变得更优雅且可维护。
Mixins 能够用于两种场景:
首先写一个公共的 mixin 文件, 把高复用的状态和函数写进去。
export default class CommonMixins extends Vue{
public paginations = {
pageSize: 20,
total: 0,
currentPage: 1,
}
handleChangePageSize (pageSize: number, cb: Function) {
this.paginations.pageSize = pageSize;
cb();
}
handleChangePageNum (currentPage: number, cb: Function) {
this.paginations.currentPage = currentPage;
cb();
}
}
复制代码
vue-property-decorator 提供了 Mixins 的装饰器,在业务页面中引入 Mixin 只须要往里 Mixins 传入 , 能够传多个,表示混入多个 Mixin。
<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator';
import CommonMixins from "./common-mixin";
import PermissionMixins from "./permission-mixin";
@Component({})
export default class Parent extends Mixins(CommonMixins, PermissionMixins) {
}
</script>
复制代码
若是只须要一个的话,也能够直接继承
<script lang="ts"> import { Component, Mixins } from 'vue-property-decorator'; import CommonMixins from "./common-mixin"; @Component({}) export default class Parent extends CommonMixins { } </script>
复制代码
在遇到功能点多,代码量大的页面时,咱们能够利用 Mixin 抽离一些功能点,经过文件去管理这些功能,这样会比较方便去管理代码。
组件在加载都是同步的,但当页面内容不少,有些组件并不须要一开始就加载出来的好比弹窗类的组件,这些就能够用动态组件,当用户执行了某些操做后再加载出来,这样能够提升主模块加载的性能, 在 Vue 中可使用 component 动态组件, 依 is 的值,来决定哪一个组件被渲染。
<template>
<div>
主页面 <br/>
<button @click="handleClick1">点击记载组件1</button><br/>
<button @click="handleClick2">点击记载组件2</button><br/>
<component :is="child1"></component>
<component :is="child2"></component>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; @Component({}) export default class AsyncComponent extends Vue { public child1:Component = null; public child2:Component = null; handleClick1() { this.child1 = require('./child1').default; } handleClick2() { this.child2 = require('./child2').default; } } </script>
复制代码
示例代码中,只有当点击的时候才会去加载组件。component 还能够配合 v-show
去控制显示和隐藏,这样这个component 只会 mounted 一次,优化性能。
::v-deep
修改组件样式有不少场景想更改 UI 组件样式,而后怕影响别人的使用,加上 scoped
后又不能生效,可使用 ::v-deep
深度做用选择器去修改组件做用域内的 CSS
的样式。在 CSS
中咱们可使用 >>>
操做符,但在预处理器中的写法就要用 /deep/
或 ::v-deep
。
<style scoped>
>>> .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
<style lang="scss" scoped>
/deep/ .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
<style lang="scss" scoped>
::v-deep .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
复制代码
::v-deep
和 /deep/
做用是同样的,但不推荐使用 /deep/
, 在 Vue3.0
中将不支持 /deep/
这种写法。
装饰器增长了代码的可读性,清晰地表达了意图,并且提供一种方便的手段,增长或修改类的功能,好比给类其中的方法提供防抖的功能。
import debounce from 'lodash.debounce';
export function Debounce(delay: number, config: object = {}) {
return (target: any, prop: string) => {
return {
value: debounce(target[prop], delay, config),
};
};
}
复制代码
这样的好处是使用起来很是方便,另外增长了代码的可读性。
@Debounce(300)
onIdChange(val: string) {
this.$emit('idchange', val);
}
复制代码
require.context
去获取项目目录信息关于 require.context
,webpack 文档是这么描述的:
能够给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。
webpack 会在构建中解析代码中的
require.context()
。若是想引入一个文件夹下面的全部文件,或者引入能匹配一个正则表达式的全部文件,这个功能就会颇有帮助
根据这个提示,咱们能够引用到一个文件夹下面的全部文件,由此能够利用获取的文件信息去作一些操做,好比在注册组件的时候,本来咱们注册组件的时候须要一个个引入而且一个个注册,并且后面想加新的,又要再写上,如今能够经过 require.context
去优化这一段代码。
// import WmsTable from './wms-table/table/index';
import Table from './table/index.vue';
import CustomHooks from './custom-hooks/custom-hooks-actions/index';
import SFilter from './s-filter/filter-form';
import WButton from './button/index';
import CreateForm from './createForm/create-form/CreateForm.vue';
import Action from './table/action-table-column.vue';
import DetailItem from './detail-item.vue';
Vue.component('w-filter', SFilter);
Vue.component('w-button', WButton);
Vue.component('custom-hooks', CustomHooks);
Vue.component('create-form', CreateForm);
Vue.component('w-table', Table);
Vue.component('w-table-action', Action);
Vue.component('zonetime-date-picker', ZonetimeDatePicker);
Vue.component('detail', DetailItem);
复制代码
注册全局组件的时候,不须要一个一个 import,和一个个去注册,使用 require.context
能够自动导入模块,这样的好处在于,当咱们新建一个组件,不用本身再去手写注册,而在一开始就帮咱们自动完成。
const contexts = require.context('./', true, /\.(vue|ts)$/);
export default {
install (vm) {
contexts.keys().forEach(component => {
const componentEntity = contexts(component).default;
if (componentEntity.name) {
vm.component(componentEntity.name, componentEntity);
}
});
}
};
复制代码
本文介绍了在 Vue 实战中常常用到的一些技巧,这些技巧的目的都是为了提高开发效率,好比简单地实现双向数据绑定和数据跨级传递等,另外也能够提升代码的可维护性、可读性,好比很实用的装饰器和利用 Mixin 拆分代码和管理功能点。