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. 插槽使用
- 默认插槽
- 具名插槽
- 作用域插槽 ---- 父子组件用来传递数据
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_开头,增强可读性
-
全局混⼊【全局混⼊常用于编写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')
-
局部混⼊
// 局部混入 // 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和当前组件的各种数据, 为四种策略:
-
替换型策略 - 同名的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 }
-
合并型策略 - 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 }
-
队列型策略 - 生命周期函数和watch,原理是将函数存入一个数组,然后正序遍历依次执⾏
-
叠加型策略 - 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做了什么?
-
判断当前插件是否已经安装过, 防止重复安装
-
处理参数, 调⽤插件的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 } }