Vue组件进阶用法与实践
一 、基础组件自动化全局注册
使用场景:模块系统内频繁使用的基础组件;
使用方法:使用 require.context 全局注册;
比如说我们的内部网站,目前我们的页面使用的是局部注册,按需加载:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
}
但是可能有很多类似于输入框、按钮等在各个组件中频繁的使用,我们有时候会把它们称为基础组件;
我们内部网站的“没有权限查看当前页”也可以算是一个基础组件,每次使用的时候都需要在当前页面引入、注册然后再使用,但是如果使用了webpack ,那么就可以使用 require.context 只全局注册这些非常通用的基础组件:
import ComponentOne from "./ComponentOne";
components: {
ComponentOne
}
<ComponentOne></ComponentOne>
const requireComponent = require.context(
'./components',// 其组件目录的相对路径
false, // 是否查询其子目录
/Base[A-Z]\w+\.(vue|js)$/ // 匹配基础组件文件名的正则表达式
)
requireComponent.keys().forEach(fileName => {
console.log(fileName);
const componentConfig = requireComponent(fileName)// 获取组件配置
console.log(componentConfig);
const componentName = upperFirst( // 获取组件名
camelCase(
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
console.log(componentName)
Vue.component(// 全局注册组件
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
但是自动化全局注册在打包时会将组建代码直接打包进项目中,增加项目大小,不能按需加载,所以推荐频繁的使用组件才有必要进行全局自动化注册。
二、递归组件
使用场景:树状结构;
使用方法:自己调用自己;
函数的递归是我们最常用的一个递归,通俗的说就是自己调用自己吗,那么递归需要满足哪些条件?
1.有名字;
2.有结束条件;
组件的递归其实和函数 的递归一样,也需要满足上边两个条件,常用到的地方就是树形结构的设计,评论回复功能等等,Let's have a try。
由于是递归,必须结构一致,稍微有点局限性,这也就是为啥两个组件必须分离,而绝不可能通过一个组件来实现递归。
这是一个最最简单的组件递归,但是后续的所有的复杂功能,折叠,选中,拖拽都是基于这个来进行的。
三、动态组件
简单来说,让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以实现动态组件,
使用场景:
1.router的实现原理就是动态渲染组件,适合解决复杂的页面级别的组件的动态渲染;
2.而动态组件比较适用于组件内的切换,比如同一数据不同展示效果;
使用方法:
<template>
<div>
<el-button
v-for="item in componentsList"
:key="item.id"
style="margin-left:10px"
size="mini"
@click="currentView=item.value"
>
<span>{{item.name}}</span>
</el-button>
<component :is="currentView"></component>
</div>
</template>
<script>
export default {
name: "DynamicComponents",
data() {
return {
currentView: "DynamicComponentsOne",
componentsList: [
{
id: 1,
name: "组件一",
value: "DynamicComponentsOne",
},
{
id: 2,
name: "组件二",
value: "DynamicComponentsTwo",
},
{
id: 3,
name: "组件三",
value: "DynamicComponentsThree",
},
],
};
},
};
</script>
但是你会发现,动态组件切换也会把数据清空;
提一下keep-alive组件缓存:keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个DOM元素。
但是同样也存在一个问题就是被keep-alive包裹的组件我们请求获取的数据不会再重新渲染页面,解决方法就是使用include 和 exclude 属性允许组件有条件地缓存,或者需要我们在特定的情况下强制刷新某些组件。
当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行,在这里可以做一些需要的操作。
<keep-alive>
<loading></loading>
</keep-laive>
四、异步组件
在使用异步之前,每次打包项目后所有的数据都会被打包到一起生成一个app.js的文件,网站首次加载时加载这个文件,虽然说很多数据都是当前页面不需要的;
异步组件说白了就是按需加载,只有等到在页面里显示该组件的时候才会从服务器加载并缓存,不显示的话就不会加载,这样即可提高客户端的访问速度也可以降低对服务器的请求次数,可谓优化的一个利器。
按需加载的三种方式:
1. vue异步组件技术
{
path: '/home',
name: 'HomePage',
component: resolve => require(['@/views/HomePage'], resolve)
}
2. es提案的import();(需要webpack > 2.4)
路由懒加载:
const HomePage = () => import(/* webpackChunkName: "HomePage" */ '@/views/HomePage')
{
path: '/home',
name: 'HomePage',
component: HomePage
}
异步组件:
components: {
HomePage: () => import('@/views/HomePage')
},
3. webpack提供的require.ensure()
{
path: '/home',
name: 'HomePage',
component: resolve => require.ensure([], () => resolve(require('@/views/HomePage')), 'home')
}
配置:vue.config.js
module.exports={
chainWebpack:config=>{
config.plugins.delete("prefetch") //取消预加载
}
}
内部网站为例:
未使用路由懒加载,打包后生成一个app.js文件,首次加载时载入;
使用路由懒加载,打包后生成多个js文件,每个路由对应的页面首次加载只会加载当前页面需要的js文件并缓存;
五、组件插槽
是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定;
1.单个/默认/匿名插槽-一个组件只能有一个该类插槽
//子组件
<template>
<div>
<span>我是子组件</span>
<slot>我是子组件的插槽</slot>
</div>
</template>
//父组件
<template>
<div>
<span>我是父组件</span>
<SlotChild>
<div style="color:#999900">
<span>我是父组件在子组件安置的</span>
</div>
</SlotChild>
</div>
</template>
2.具名插槽-可出现多次
//子组件
<template>
<div>
<span>我是子组件</span>
<slot name="up">我是子组件的具名插槽up</slot>
<slot name="down">我是子组件的具名插槽down</slot>
</div>
</template>
//父组件
<template>
<div>
<span>我是父组件</span>
<SlotChild>
<div style="color:#999900" slot="up">
<span>我是父组件在子组件安置的up</span>
</div>
<div style="color:#009900" slot="down">
<span>我是父组件在子组件安置的down</span>
</div>
</SlotChild>
</div>
</template>
3.作用域插槽/带数据的插槽-样式交给父组件,数据交给子组件
//子组件中
<slot name="region" :data="clildData">我是子组件的作用域插槽</slot>
data() {
return {
clildData: {
name: "我是子组件的数据",
type: "type",
},
};
},
//父组件中
<SlotChild>
<div style="color:#000099" slot="region" slot-scope="childData">
<span>我来自父组件,我在子组件的作用域插槽内使用它的数据:{{childData.data}}</span>
</div>
</SlotChild>
使用场景
六、函数组件
对于函数式组件,可以这样定义:
Stateless(无状态):组件自身是没有状态的;
Instanceless(无实例):组件自身没有实例,也就是没有this;
由于函数式组件拥有的这两个特性,我们就可以把它用作高阶组件(High order components),所谓高阶,就是可以生成其它组件的组件。
创建一个函数组件非常容易,你需要做的就是在单文件组件 (SFC)中添加一个 functional: true 属性,或者在模板中添加 functional。由于它像函数一样轻巧,没有实例引用,所以渲染性能提高了不少。
函数组件依赖于上下文,并随着其中给定的数据而突变。
//基本写法
{
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文组件需要的data、prop、slots、children、parent都是通过这个上下文来传递。比如this.level要改写为context.props.level,this.$slots.default改变为context.children。
render: function (createElement, context) {
// ...
}
}
函数化组件不常用,它应该应用于以下场景:
一个简单的展示组件,也就是dumb组件,比如buttom、tags、cards.....。
children、props 或者 data 在传递给子组件之前,处理它们。
简单的展示。
同样函数式组件可能不适用于许多情况。毕竟,使用 JavaScript 框架的目的是构建响应式的应用程序。在 Vue 中,如果没有响应式,就无法做到这一点。
七、自定义组件双向绑定
自定义组件实现 v-model 双向绑定,首先要先明白 v-model,这个指令到底实现了什么?
v-model实际做的事情就是:传入一个value属性值,双向绑定会默认监听input事件并返回一个值
在子组件 emit 一个 input 事件,并返回一个值,父组件调用 v-model 的时候会默认执行这个时间;
# 正常写法 <input type="text" v-model="userName" /> # 等价于这种写法 <input type="text" :value="userName" @input="userName = $event.target.value" />
八、强制更新
如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。
你可能还没有留意到数组或对象的变更检测注意事项,或者你可能依赖了一个未被 Vue 的响应式系统追踪的状态。
然而,如果你已经做到了上述的事项仍然发现在极少数的情况下需要手动强制更新,那么你可以通过 $forceUpdate 来做这件事。