Vue2的高级用法
一、mixin复用
mixin代表了一种思考问题的方法———穿越,mixin实现复用的同时带来了很多问题:命名污染、依赖不透明,Vue3用Composition API替代了
mixin是什么
一种比较灵活的方式来分发vue组件可复用的功能,目的是公共逻辑和配置提取
用法
//创建mixin.js暴露对象
export default{
data(){
return{
msg:'mixins'
}
},
created(){
console.log('mixins created')
},
methods:{
hello(){
console.log( 'hello mixins' );
}
}
}
//单独vue组件中引入
import {mixin1,mixin2} from '../common/mixins'
...
mixins:[mixin1,mixin2],
//main.js中全局注册
Vue.mixin({
created:function () {
// console.log('mixin global');//全局混入,每个组件都会打印
}
})
合并
- 生命周期 合并为数组(mixin先触发)
- 数据对象内容 覆盖方式(mixin被覆盖)
- methods、计算属性等冲突(mixin被覆盖)
优势
- 提高代码复用性
- 无需传递状态
缺点
- 不能按需引入
- 溯源困难
tips
- vue2中对象混入,vue3中setup API混入
- 一个组件改动了mixin的数据,另一个组件引用了mixin的组件不受影响
- 若简单组件状态 props
- 跨组件 provide/inject
- 处理更复杂组件之间的共享 vuex pini(对比会写一份笔记记录 占坑)
详细解释见本博客《关于Vue进阶问题的总结》
二.Vue.js动画特效 & 常见组件库介绍
Js动画实现思路
- Gsap
- animate css
- 切换 class 预先定义好的动画相关类名实现动画
- js 操作 style transition
Vue中的动画
vue3 中,过渡的时候切换class类名,通过监听 transitionstart
、transitionend
实现
<template>
<div id="demo"></div>
</template>
<script>
export default{
mounted(){
// js原生实现,vue中transition组件类似也是这样的原理
const demoDom = document.querySelector('#demo')
demoDom.addEventListener('transitionstart',()=>{
console.log( 'start' );
})
demoDom.addEventListener('transitionend',()=>{
console.log( 'end' );
demoDom.parentElement.removeChild(demoDom)
})
},
}
</script>
<style scoped>
#demo{
width: 100px;
height: 100px;
background: rebeccapurple;
transition: all 1s ease;
}
#demo:hover{
transform: translateY(100px);
}
</style>
<template>
<button @click="isShow = !isShow">点击切换动画</button>
<transition name="fade">
<p v-if="isShow">切换当前内容</p>
</transition>
</template>
<script>
export default{
data(){
return{ isShow:false }
}
}
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active{
transition: opacity 1s;;
}
.fade-enter,
.fade-leave-to{
opacity: 0;
}
</style>
<template>
<transition-group name="fade">
<div v-for="(i,idx) in items" :key="idx">{{ i }}</div>
</transition-group>
<button @click="add">add</button>
<button @click="remove">remove</button>
</template>
<script>
export default{
data(){
return{ items:[1,2,3,4,5,6,7,8] }
},
methods:{
randomIndex(){
return Math.floor(Math.random()*this.items.length)
},
add(){
this.items.splice(this.randomIndex(),0,++this.items.length)
},
remove(){
this.items.splice(this.randomIndex(),1)
}
}
}
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active{
transition: opacity 1s;;
}
.fade-enter,
.fade-leave-to{
opacity: 0;
}
</style>
Vue源码位置
源码实现:重点看updated函数,对生成之前的preChildren通过传入的name,追加class类名,进行动画操作和事件监听。
updated() {
const children: Array<VNodeWithData> = this.prevChildren
const moveClass: string = this.moveClass || (this.name || 'v') + '-move'
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
return
}
// we divide the work into three loops to avoid mixing DOM reads and writes
// in each iteration - which helps prevent layout thrashing.
children.forEach(callPendingCbs)
children.forEach(recordPosition)
children.forEach(applyTranslation)
// force reflow to put everything in position
// assign to this to avoid being removed in tree-shaking
// $flow-disable-line
this._reflow = document.body.offsetHeight
children.forEach((c: VNode) => {
if (c.data!.moved) {
const el: any = c.elm
const s: any = el.style
addTransitionClass(el, moveClass)
s.transform = s.WebkitTransform = s.transitionDuration = ''
el.addEventListener(
transitionEndEvent,
(el._moveCb = function cb(e) {
if (e && e.target !== el) {
return
}
if (!e || /transform$/.test(e.propertyName)) {
el.removeEventListener(transitionEndEvent, cb)
el._moveCb = null
removeTransitionClass(el, moveClass)
}
})
)
}
})
}
也可以对打包之后的文件(node_modules/vue/dist)进行debug调试:
三.插槽
目的
外部组件自定义内部组件的内容,实现内容分发
- 占位(子组件),分发内容(父组件)
- 默认插槽、具名插槽(name控制)、作用域插槽关注一下区别
插槽本质
重点方法:
- $slots & $scopedSlots
- renderSlot
- processSlot、processSlotContent
- generate
函数生成对应虚拟节点。源码实现:重点看renderSlot
函数
//插槽的渲染过程
export function renderSlot(
name: string,
fallbackRender: ((() => Array<VNode>) | Array<VNode>) | null,
props: Record<string, any> | null,
bindObject: object | null
): Array<VNode> | null {
const scopedSlotFn = this.$scopedSlots[name]
let nodes
if (scopedSlotFn) {
// scoped slot
props = props || {}
if (bindObject) {
if (__DEV__ && !isObject(bindObject)) {
warn('slot v-bind without argument expects an Object', this)
}
props = extend(extend({}, bindObject), props)
}
nodes =
scopedSlotFn(props) ||
(isFunction(fallbackRender) ? fallbackRender() : fallbackRender)
} else {
nodes =
this.$slots[name] ||
(isFunction(fallbackRender) ? fallbackRender() : fallbackRender)
}
const target = props && props.slot
if (target) {
return this.$createElement('template', { slot: target }, nodes)
} else {
return nodes
}
}
四.插件
- 插件是在不侵入源码的情况下,对源码进行扩展。
- 插件可以是对象(对象需要提供install函数),也可以是函数(形态需同install函数保持一致)
- install 是组件安装的一个方法,npm install是命令,两者不等同
- webpack、tappable、vue、vue-router、pinia都是插件提供的内容
- 核心内容、插件基座、插件注册、插件卸载、插件生命周期
如何定义插件
在Vue中的插件,都是基于Vue实例,调用Vue的能力,对Vue生态去拓展功能。
const MyPlugin = {
install(Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
};
使用插件
Vue.use(MyPlugin);
{{ $myMethod }}
插件机制化原理
//Vue.use 原理
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 获取已经安装的插件🚩1.
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 看看插件是否已经安装,如果安装了直接返回
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// toArray(arguments, 1)实现的功能就是,获取Vue.use(plugin,xx,xx)中的其他参数。🚩2.
// 比如 Vue.use(plugin,{size:'mini', theme:'black'}),就会回去到plugin意外的参数
const args = toArray(arguments, 1)
// 在参数中第一位插入Vue,从而保证第一个参数是Vue实例,unshift方法让argus第一项是this🚩2.
args.unshift(this)
// 插件要么是一个函数,要么是一个对象(对象包含install方法)
if (typeof plugin.install === 'function') {
// 调用插件的install方法,并传入Vue实例 🚩3.
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
//调用插件plugin并传入args🚩3.
plugin.apply(null, args)
}
// 在已经安装的插件数组中,放进去🚩4.
installedPlugins.push(plugin)
return this
}
}
插件的具体实践
eg:Vue-Router
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}