vue-transition、slot等

本文详细介绍了Vue中transition的路由切换动画实现,包括定义路由深度、监听切换及样式设置。接着讨论了slot的使用,包括默认和具名slot的场景。接着讲解了mixin混入的原理和使用,以及不同策略的合并。此外,还涵盖了filter过滤器的使用和实现原理,并简单提及了Vue插件的使用和创建。
摘要由CSDN通过智能技术生成

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文主要记录一下Vue中transition、slot、mixin、filter、plugin的用法

一.transition路由切换动画

实现路由向左切换,向右切换的动画。

1.定义多个跳转路由

定义路由的深度,用于判断路由的方向 javascript { path: "/", name: 'home', component: Home, meta: { depth: 1 } }, { path: "/list", name: 'list', component: List, meta: { depth: 2 } }, { path: "/detail/:id", name: 'detail', component: Detail, meta: { depth: 3 } },

2.监听路由的切换

javascript @Watch("$route") onRouteChange(to: Route, from: Route) { this.transitionName = to!.meta!.depth > from!.meta!.depth ? "slide-left" : "slide-right"; }

3.transition样式

```css .slide-right-enter-active, .slide-right-leave-active, .slide-left-enter-active, .slide-left-leave-active { will-change: transform; transition: transform 350ms; position: absolute; overflow: hidden; }

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

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

二、插槽 - Slot

1.用法作用:在组件模板中占位,以复用组件的布局

默认slot使用

⼦组件⽤标签来确定渲染的位置,标签⾥⾯可以放DOM结构,当⽗组件使⽤的时候没有往插 槽传⼊内容,标签内DOM结构就会显示在⻚⾯

具名slot使用

⼦组件⽤v-slot来表示插槽的名字,不传为默认插槽 ⽗组件中在使⽤时在默认插槽的基础上加上slot属性,值为⼦组件插槽v-slot的属性值

```js
// 组件

我是默认头部文字
我是默认中间左边文字
我是默认中间右边文字

```

```js // 页面使用 导航栏 侧边栏 右侧内容 底部内容 回home

导航栏 侧边栏 右侧内容

count = {{slotProps.count}}

底部内容 回home
我是默认内容
' }) new Vue({ el: '#app', template: ' 我是slot传⼊内容 ', components:{buttonCounter} }) ``` 经过vue编译, 组件渲染函数会变成这样 ```typescript (function anonymous( ) { with(this){return _c('div',[_t("default",[_v("我是默认内容")])],2)} }) ``` ⽽这个_t就是slot渲染函数 ```typescript function renderSlot ( name, fallback, props, bindObject ) { // 得到渲染插槽内容的函数 var scopedSlotFn = this.$scopedSlots[name]; var nodes; // 如果存在插槽渲染函数,则执⾏插槽渲染函数,⽣成nodes节点返回 // 否则使⽤默认值 nodes = scopedSlotFn(props) || fallback; return nodes; } ``` ⽽scopedSlots其实就是递归解析各个节点, 获取slot ```typescript function resolveSlots ( children, context ) { if (!children || !children.length) { return {} } var slots = {}; for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; var data = child.data; // remove slot attribute if the node is resolved as a Vue slot node if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot; } // named slots should only be respected if the vnode was rendered in the // same context. if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { // 如果slot存在(slot="header") 则拿对应的值作为key var name = data.slot; var slot = (slots[name] || (slots[name] = [])); // 如果是tempalte元素 则把template的children添加进数组中,这也就是为什么你写的 template标签并不会渲染成另⼀个标签到⻚⾯ if (child.tag === 'template') { slot.push.apply(slot, child.children || []); } else { slot.push(child); } } else { // 如果没有就默认是default (slots.default || (slots.default = [])).push(child); } } // ignore slots that contains only whitespace for (var name$1 in slots) { if (slots[name$1].every(isWhitespace)) { delete slots[name$1]; } } return slots } ``` ## 三、Mixin 混入 > 本质其实就是⼀个js对象,可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等 我们一般将公共的功能呢或数据以对象的形式放到mixin中,其他组件或页面组件使用mixin对象时,就会将mixin组件中的功能和数据混入到使用的组件中。 - 当组件存在与mixin对象相同的数据的时候,进⾏递归合并的时候组件的数据会覆盖mixin的数据 - 如果相同数据为⽣命周期钩⼦的时候,会合并成⼀个数组,先执⾏mixin的钩⼦,再执⾏组件的钩⼦ ### 1.mixin使用 ```typescript // mixin import { Vue, Component } from 'vue-property-decorator'; @Component export class CommonMixin extends Vue { mixinName: string = 'haiyang'; enterTime: number = 0; mounted() { console.log('进入了mixin'); this.enterTime = Date.now(); } beforeDestroy() { console.log(`页面浏览了${Date.now() - this.enterTime}ms`); } } ``` ```typescript // 引入
页面内容

``` 在这里插入图片描述

2.四种策略以及实现原理

  • 优先递归处理 mixins,将子组件的mixins合并过来一同处理
  • 先遍历合并parent 中的key,调⽤mergeField⽅法进⾏合并,然后保存在变量options
  • 再遍历child,合并补上 parent 中没有的key,调⽤mergeField⽅法进⾏合并,保存在变量 options
  • 通过mergeField 函数进⾏了合并

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

}
return options

} ``` 其实主要的逻辑就是合并mixin和当前组件的各种数据, 细分为四种策略:

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

ts strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal const ret = Object.create(null) // 创建⼀个第三⽅对象 ret extend(ret, parentVal) // extend⽅法实际是把parentVal的属性复制到ret中 if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中 return ret }

合并型策略- data, 通过set⽅法进⾏合并和重新赋值

ts strats.data = function(parentVal, childVal, vm) { return mergeDataOrFn( parentVal, childVal, vm ) }; function mergeDataOrFn(parentVal, childVal, vm) { return function mergedInstanceDataFn() { var childData = childVal.call(vm, vm) // 执⾏data挂的函数得到对象 var parentData = parentVal.call(vm, vm) if (childData) { return mergeData(childData, parentData) // 将2个对象进⾏合并 } else { return parentData // 如果没有childData 直接返回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,原理是将函数存⼊⼀个数组,然后正序遍历依次执⾏

ts function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> { return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal } LIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook })

叠加性策略 - component、directives、filters,通过原型链进⾏层层的叠加

ts 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 }

四、filter 过滤器

1.使用

不改变原始值,返回加工过的值,是一个纯函数 vue3已经弃用,后面多用computed去实现过滤功能

```js // vue文件 手机号 {{phone | phoneFilter}}

import { Component, Vue} from "vue-property-decorator"; import { phoneFilter } from "../filters"; @Component({ components: { SlotComponent }, filters: { phoneFilter } }) export default class Home extends Vue() { readonly phone: string = '15829069561';

};

// filters export const phoneFilter = (value: string) => { if (!value) return ""; return value.replace(/(1[3-9]\d)\d{4}(\d{4})/, "$1**$2"); };
``` 在这里插入图片描述

2.实现原理

  • 在编译阶段通过parseFilters将过滤器编译成函数调⽤ 1.串联过滤器则是⼀个嵌套的函数调⽤, 2.前⼀个过滤器执⾏的结果是后⼀个过滤器函数的参数

ts function parseFilters (filter) { let filters = filter.split('|'); let expression = filters.shift().trim() // shift()删除数组第⼀个元素并将其返回,该⽅法会更改原数组 let i; if (filters) { for(i = 0;i < filters.length;i++){ experssion = warpFilter(expression,filters[i].trim()) // 这⾥传进去的expression实际上是管道符号前⾯的字符串,即过滤器的第⼀个参数 } } return expression } // warpFilter函数实现 function warpFilter(exp,filter){ // ⾸先判断过滤器是否有其他参数 const i = filter.indexof('(') if(i<0){ // 不含其他参数,直接进⾏过滤器表达式字符串的拼接 return `_f("${filter}")(${exp})` }else{ const name = filter.slice(0,i) // 过滤器名称 const args = filter.slice(i+1) // 参数,但还多了 ‘)’ return `_f('${name}')(${exp},${args}` // 注意这⼀步少给了⼀个 ')' } } - 编译后通过调⽤resolveFilter函数找到对应过滤器并返回结果

ts export function resolveFilter(id){ return resolveAsset(this.$options,'filters',id,true) || identity } // 因为我们找的是过滤器,所以在 resolveFilter函数中调⽤时 type 的值直接给的 'filters',实际这个函数还可以拿到其他很多东⻄ export function resolveAsset(options,type,id,warnMissing){ if(typeof id !== 'string'){ // 判断传递的过滤器id 是不是字符串,不是则直接返回 return } const assets = options[type] // 将我们注册的所有过滤器保存在变量中 // 接下来的逻辑便是判断id是否在assets中存在,即进⾏匹配 if(hasOwn(assets,id)) return assets[id] // 如找到,直接返回过滤器 // 没有找到,代码继续执⾏ const camelizedId = camelize(id) // 万⼀你是驼峰的呢 if(hasOwn(assets,camelizedId)) return assets[camelizedId] // 没找到,继续执⾏ const PascalCaseId = capitalize(camelizedId) // 万⼀你是⾸字⺟⼤写的驼峰呢 if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId] // 如果还是没找到,则检查原型链(即访问属性) const result = assets[id] || assets[camelizedId] || assets[PascalCaseId] // 如果依然没找到,则在⾮⽣产环境的控制台打印警告 if(process.env.NODE_ENV !== 'production' && warnMissing && !result){ warn('Failed to resolve ' + type.slice(0,-1) + ': ' + id, options) } // ⽆论是否找到,都返回查找结果 return result }

  • 执⾏结果作为参数传递给toString函数,⽽toString执⾏后,其结果会保存在Vnode的text属性 中,渲染到视图 ts function toString(value){ return value == null ? '' : typeof value === 'object' ? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可⽤来控制字符串⾥⾯的间距 : String(value) }

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

1.使用

使用剪切板插件 - install想要使用的插件(剪切板插件clipboard2) - 在入口文件引入并use ts // main.ts import VueClipboard from 'vue-clipboard2'; Vue.use(VueClipboard); - 在文件中使用

```html

底部内容

```

2.实现一个自定义的plugin

  • 什么是插件? 如何编写⼀个插件? ```ts MyPlugin.install = function (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(plugin, options); ```

  • Vue.use做了什么?

判断当前插件是否已经安装过, 防⽌重复安装 处理参数, 调⽤插件的install⽅法, 第⼀个参数是Vue实例.

ts // Vue源码⽂件路径:src/core/global-api/use.js import { toArray } from '../util/index' export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters const args = toArray(arguments, 1) args.unshift(this) if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } installedPlugins.push(plugin) return this } }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值