vue高级用法

1. 动画特效----transition

vue自带的 用来实现过渡效果

基本使用
设置name属性(如:name =“xxx” xxx = “v”),在组件过渡过程中,会有六个CSS类名进行切换
请添加图片描述
transition.jpg
v-enter: 定义进入过渡的开始状态。在元素被插入时生效,只应用一帧后立刻删除。
v-enter-active: 定义过渡的状态。在元素整个过渡过程中作用,在元素被插入时生效,在 transition/animation 完成之后移除。 可以用来定义过渡的过程时间,延迟和曲线函数。
v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入一帧后生效(于此同时 v-enter 被删除),在 transition/animation 完成之后移除。
v-leave: 定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。
v-leave-active: 定义过渡的状态。在元素整个过渡过程中作用,在离开过渡被触发后立即生效,在 transition/animation 完成之后移除。 这个类可以被用来定义过渡的过程时间,延迟和曲线函数。
v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发一帧后生效(同时 v-leave 被删除),在 transition/animation 完成之后移除。

transition 实现路路由切换动画
home==>list==>detail
detail==>list==>home

// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../components/Home.vue'
import List from '../components/List.vue'
import Detail from '../components/Detail.vue'

Vue.use(VueRouter)

export default new VueRouter({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
      meta: {
        deep: 1
      }
    },
    {
      path: '/list',
      name: 'list',
      component: List,
      meta: {
        deep: 2
      }
    },
    {
      path: '/detail/:id',
      name: 'detail',
      component: Detail,
      meta: {
        deep: 3
      }
    }
  ]
});

//=========================================================
// 组件有关代码自行脑补,嘻嘻
//=========================================================

//App.vue
<template>
  <div id="app">
    <transition :name="transitionName">
      <router-view />
    </transition>
  </div>
</template>

<script>

export default {
  name: 'App',
  data() {
    return {
      transitionName: ''
    }
  },
  watch: {
    // 监听路由变化
    $route: {
      handler (to, from){
        this.transitionName = to.meta.deep > from.meta.deep ? 'slide-left' : 'slide-right'
      },
      // 深度观察监听
      deep: true
    }
  },
}
</script>

<style lang="less" scoped>
  #app {
    width: 100%;
    height: 100%;
  }
  .slide-left-enter-active,
  .slide-right-enter-active,
  .slide-left-leave-active,
  .slide-right-leave-active {
    will-change: transform;
    transition: transform 350ms;
    position: absolute;
    overflow: hidden;
  }
  .slide-right-enter-active,
  .slide-left-leave-active {
    transform: translate(-100%, 0);
  }

  .slide-left-enter-active,
  .slide-right-leave-active {
    transform: translate(100%, 0);
  }
</style>

2. 插槽 slot

插槽就是子组件中的提供给父组件使用的一个占位符,父组件可以对插槽填充任何内容。通过插槽可以让用户拓展组件,去更好地复用组件、对其做定制化处理

1. 插槽和组件的区别是:

①组件之间的传递是单纯的数据的传递,而插槽的传递是视图的传递。
②插槽的内容显示是由谁调用就由谁来决定插槽的内容,组件的内容是提前已经写好了,只进行数据的简单改变。

2. 插槽使用

  1. 默认插槽
  2. 具名插槽
  3. 作用域插槽 ---- 父子组件用来传递数据

vue2.6.0以后,使用新语法v-slot指令(缩写为#)

// 父组件
<template>
  <div>
    <use-slot>
      <template>
        <div>
          我是默认插槽
        </div>
      </template>
      <template v-slot:juming>
        <div>
          我是具名插槽
        </div>
      </template>
      <template v-slot:zuoyongyu='pros'>
        <div>
          我是作用域插槽:
          <br>
          data1 = {{pros.data1}}
          <br>
          data2 = {{pros.data2}}
        </div>
      </template>
    </use-slot>
  </div>
</template>

<script>
import UseSlot from '../components/UseSlot.vue'
export default {
  name: 'Home',
  components: { UseSlot }
}
</script>

//=========================================================

// 子组件
<template>
  <div>
    默认插槽
    <slot></slot>
    具名插槽
    <slot name="juming"></slot>
    作用域插槽
    <slot :data1="shuju1" :data2="shuju2"  name="zuoyongyu"></slot>
  </div>
</template>

<script>
export default {
  name: 'use-slot',
  data() {
    return {
      shuju1: '我是作用于插槽的数据',
      shuju2: '我也是作用于插槽的数据'
    }
  }
}
</script>

3. 插槽实现原理

先编译(先对父组件进行编译,执行渲染函数,获取slot)再替换(再遍历插槽组件,找到应该替换的位置,替换插槽内容)

3. Mixin

Mixin本质其实就是⼀个js对象,它可以包含我们组件中任意功能选项,如data、components、 methods、computed以及生命周期函数等等。我们只要将共⽤的功能以对象的⽅方式传入 mixins选项中,当组件使用 mixins对象时所有mixins 对象的选项都将被混⼊该组件本身的选项中来,以达到代码的复用。

1. mixin使用

建议mixin里定义的都以mixin_开头,增强可读性

  1. 全局混⼊【全局混⼊常用于编写vue插件】

    // main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router/router'
    
    Vue.config.productionTip = false
    
    // 全局混入
    Vue.mixin({
      methods: {
        showDialog() {
          confirm('欢迎使用,小猪猪')
        }
      }
    })
    
    new Vue({
      render: h => h(App),
      router
    }).$mount('#app')
    
  2. 局部混⼊

    // 局部混入
    // common.js
    export default {
      methods: {
        showDialog() {
          confirm('欢迎使用,小猪猪')
        }
      }
    }
    
    //=========================================================
    
    // List.vue
    <template>
      <div class="list">
        <h1 @click="showDialog">List</h1>
      </div>
    </template>
    
    <script>
    import comm from '../mixins/common'
    export default {
      name: 'List',
      mixins: [ comm ]
    }
    </script>
    

当组件存在与mixin对象相同的数据的时候,组件的数据会覆盖mixin的数据 。如果相同数据为生命周期函数的时候,会先执行mixin的钩⼦函数,再执⾏组件的钩⼦函数(原因见下文的mixin 实现原理)

2. mixin 实现原理

【可看作组件继承了mixins】
优先递归处理 mixins
先遍历合并parent (mixin)中的key,调用mergeField方法进⾏合并,然后保存在变量options
再遍历 child(组件),合并补上 parent (mixin)中没有的key,调⽤mergeField⽅法进行合并,保存在变量 options

export function mergeOptions (parent: Object,child: Object,vm?: Component): Object {
	if (child.mixins) { 
	// 判断有没有mixin⾥面挂mixin的情况,有的话递归进行合并 
		for (let i = 0, l = child.mixins.length; i < l; i++) {
			parent = mergeOptions(parent, child.mixins[i], vm)
		}
	}
	const options = {}
	for (let key in parent) {
		// 先遍历parent的key调对应的strats[XXX]⽅法进行合并 
		mergeField(key) 
	}
	for (key in child) {
		// 如果parent已经处理过某个key就不处理了
		if (!hasOwn(parent, key)) { 
			// 处理child中有的,parent中没有处理过的key 
			mergeField(key) 
		}
    }
   	function mergeField (key) {
     	const strat = strats[key] || defaultStrat
     	// 根据不同类型的options调用strats中不同的⽅法进行合并
		options[key] = strat(parent[key], child[key], vm, key)
	}
    return options
}
合并策略

合并mixin和当前组件的各种数据, 为四种策略:

  1. 替换型策略 - 同名的props、methods、inject、computed会被后来者代替

     strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ?Object {
     	// 如果parentVal没有值,直接返回childVal
    	if (!parentVal) 
    		return childVal 
    	// 创建一个第三方对象 ret
    	const ret = Object.create(null) 
    	// extend⽅法实际是把parentVal的属性复制到ret中
    	extend(ret, parentVal) 
    	// 把childVal的属性复制到ret中 
    	if (childVal) 
    		extend(ret, childVal) 
    	return ret
    }
    
  2. 合并型策略 - data, 通过set方法进行合并和重新赋值

    strats.data = function(parentVal, childVal, vm) {
        return mergeDataOrFn(parentVal, childVal, vm)
    };
    
    function mergeDataOrFn(parentVal, childVal, vm) {
        return function mergedInstanceDataFn() {
        	// 执行data挂的函数得到对象
    		var childData = childVal.call(vm, vm) 
    		var parentData = parentVal.call(vm, vm)
    		if (childData) {
    			// 将2个对象进行合并
    			return mergeData(childData, parentData) 
    		} else {
    			// 如果没有childData 直接返回parentData 
    			return parentData 
    		}
    	} 
    }
    
    function mergeData(to, from) {
        if (!from) 
        	return to
        var key, toVal, fromVal;
        var keys = Object.keys(from);
        for (var i = 0; i < keys.length; i++) {
    		key = keys[i];
    		toVal = to[key];
    		fromVal = from[key];
    		// 如果不存在这个属性,就重新设置 
    		if (!to.hasOwnProperty(key)) {
                set(to, key, fromVal);
            }else if (typeof toVal =="object" && typeof fromVal =="object") {
            	// 存在相同属性,合并对象
                mergeData(toVal, fromVal);
            }
    	}
    	return to 
    }
    
  3. 队列型策略 - 生命周期函数和watch,原理是将函数存入一个数组,然后正序遍历依次执⾏

  4. 叠加型策略 - component、filters、directives,通过原型链进行层层的叠加

    strats.components = strats.directives = strats.filters = function mergeAssets(parentVal, childVal, vm, key){
    	var res = Object.create(parentVal || null)
    	if (childVal) {
              for (var key in childVal) {
                  res[key] = childVal[key];
    		} 
    	}
    	return res 
    }
    

4. 过滤器

过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进⾏调⽤处理,可以理解其为一个纯函数。Vue3中已弃⽤了, 建议使⽤computed对数据加工处理.

过滤器的使用

filter 实现原理

在编译阶段通过parseFilters函数将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的结果是后一个过滤器函数的参数)

5. keep-alive的实现原理

keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹组件时,会缓存不活动的组件实例,而不是销毁它们。

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
      redirect: 'goods',
      children: [
        {
          path: 'goods',
          name: 'goods',
          component: Goods,
          meta: {
        	keepAlive: false // 不需要缓存
      	  }
        },
        {
          path: 'ratings',
          name: 'ratings',
          component: Ratings,
          meta: {
        	keepAlive: true  // 需要缓存
      	  }
        }
      ]
    }
  ]
})

<keep-alive>
   //会缓存路由配置中meta属性对象中keepAlive为true的页面
   //this.$route.meta.keepAlive可以读取路由配置中meta属性对象里的数据
  <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>

keep-alive可以设置以下props属性:

include - 字符串或正则表达式。定义缓存白名单,只有名称匹配的组件会被缓存
exclude - 字符串或正则表达式。定义缓存黑名单,任何名称匹配的组件都不会被缓存
max - 数字。最多可以缓存多少组件实例

  • 当组件在 < keep-alive> 内被缓存,该组件的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行,切换到该缓存组件时,该组件的activated生命周期钩子函数将会被对应执行,当离开该缓存组件时,该组件的deactivated 生命周期钩子函数将会被对应执行 。

6. vue.$nextTick(callback) / Vue.nextTick(callback)

Vue 的 nextTick 其本质是对 JavaScript 执行机制 EventLoop 的一种应用。 nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法来模拟对应的微/宏任务的实现,利用这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

  • vue.$nextTick()使用原理:
    Vue组件是异步渲染的,即数据改变后,DOM不会立刻渲染,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。vue.$nextTick()的回调会在DOM渲染之后被自动触发,以便获取最新的DOM节点。

  • vue.$nextTick()的应用场景

    1.在vue的生命周期created()钩子函数中进行dom操作,一定要放在 vue.$nextTick()函数中执行。在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进vue.$nextTick()的回调函数中。

    2.在数据变化后要执行某个随着数据改变而改变DOM结构的操作时,这个操作都是需要放在vue.$nextTick()的回调函数中。

7. Vue中ref和$refs

8. vue插件 - Plugin

插件就是指对Vue的功能的增强或补充。

1. 插件使用

以剪切板插件为例
npm install --save vue-clipboard2

//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/router'
import VueClipboard from 'vue-clipboard2'

Vue.config.productionTip = false

VueClipboard.config.autoSetContainer = true // add this line
Vue.use(VueClipboard)

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

//=========================================================

// detail.vue
<template>
  <div @click="copyDetail">内容: {{detail}}</div>
</template>

<script>
export default {
  name: 'Detail',
  data() {
    return {
      detail: '这是需要复制的内容'
    }
  },
  methods: {
    copyDetail() {
      // 使用插件扩展的方法$copyText
      this.$copyText(this.detail).then(function (e) {
        alert('Copied')
        console.log(e)
      }, function (e) {
        alert('Can not copy')
        console.log(e)
      })
    }
  }
}
</script>

2. vue插件编写

// 新建文件 MyPlugin.js
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
    // 1. 添加全局方法或 property
    // Vue.myGlobalMethod = function () {
    //     // 逻辑...
    // }


    // 2. 注入组件选项
    // Vue.mixin({
    //     created: function () {
    //     // 逻辑...
    //     }
    // })

    // 3. 添加实例方法
    Vue.prototype.$myMethod = function () {
        if (options.kkk === 1){
            confirm('HelloWorld----plugin')
        } else {
            confirm('加油小迟迟')
        }
    }

    // ....
}

export default MyPlugin

//============================================
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/router'
import MyPlugin from './plugin/MyPlugin'

Vue.config.productionTip = false

// 注册使用自定义插件
Vue.use(MyPlugin, {kkk: 1})

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

//============================================
// Detail.vue
<template>
 <div @click="showDetail">内容: {{detail}}</div>
</template>

<script>
export default {
  name: 'Detail',
  data() {
    return {
      detail: '这是详细内容'
    }
  },
  methods: {
    showDetail() {
	  // 使用自定义插件扩展的方法$myMethod
      this.$myMethod()
    }
  }
}
</script>

  • 进一步尝试编写vue插件

3. Vue.use做了什么?

  1. 判断当前插件是否已经安装过, 防止重复安装

  2. 处理参数, 调⽤插件的install⽅法, 第⼀个参数是Vue实例

    // util/index.ts
    export function toArray (list: any, start?: number): Array<any> {
        start = start || 0
        let i = list.length - start
        const ret: Array<any> = new Array(i)
        while (i--) {
          ret[i] = list[i + start]
        }
        return ret
      }
    
    // Vue源码⽂文件路路径:src/core/global-api/use.js
    import { toArray } from '../util/index'
    export function initUse (Vue: GlobalAPI) {
    	Vue.use = function (plugin: Function | Object) {
    		// this === Vue
    		// this._installedPlugins 曾经注册过的插件
    		const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    		// 判断vue是否已经注册过这个插件
    		if (installedPlugins.indexOf(plugin) > -1) {
    			// 返回this的原因是可以链式调用
    			return this
    		}
    		// 处理参数
    		const args = toArray(arguments, 1)
    		// 把Vue传进去
    		args.unshift(this)
    		if (typeof plugin.install === 'function') {
    		 	plugin.install.apply(plugin, args)
    		} else if (typeof plugin === 'function') {
    		 	plugin.apply(null, args)
    		}
    		//最后告知vue该插件已经注册过,保证每个插件只会注册一次。
    		this._installedPlugins.push(plugin)
    		return this
    	} 
    }
    

9. 常⻅组件库介绍

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值