Vue2高级用法
- 1、mixin复用【vue不会用了,了解一下】
- 1.1 基础使用
- 1.2 选项合并
- 1.3 全局混入
- 1.4 细数 mixin 存在的问题
- 2、vue.js 动画特效& 常见组件库介绍
- 2.1 进入/离开基础使用示例
- 2.2 进入/离开自定义过度类名
- 2.3 进入/离开动画钩子
- 2.4 多组件过渡与列表过渡
- 2.5 状态过渡
- 2.6 常用动画相关库
- 3、 插槽
- 3.1 插槽的三种使用方法
- 4、插件
- 4.1 定义插件
- 4.2 使用插件
- 4.3 插件化机制原理
- 4.4 具体实践
- 5、过滤器
- 5.1 使用过滤器
- 5.2 定义过滤器
- 5.3 串联过滤器
1、mixin复用【vue不会用了,了解一下】
mixin实现复用的同时带来了很多问题,例如:命名污染、依赖不透明
Vue3 用 Composition API替代
1.1 基础使用
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
例子:
// 定义一个混入对象
var myMixin = {created: function () {this.hello()},methods: {hello: function () {console.log('hello from mixin!')}}
}// 定义一个使用混入对象的组件
var Component = Vue.extend({mixins: [myMixin]
})var component = new Component() // => "hello from mixin!"
1.2 选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
- 值为对象的选项:合并成一个对象,同名的话,组件的值会覆盖混入对象
- 钩子函数:合并成一个数组,都执行,混入对象的钩子先执行,组件后执行(组件的函数覆盖混入的)
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var mixin = {data: function () {return {message: 'hello',foo: 'abc'}}
}new Vue({mixins: [mixin],data: function () {return {message: 'goodbye',bar: 'def'}},created: function () {console.log(this.$data)// => { message: "goodbye", foo: "abc", bar: "def" }}
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {created: function () {console.log('混入对象的钩子被调用')}
}new Vue({mixins: [mixin],created: function () {console.log('组件钩子被调用')}
})// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {methods: {foo: function () {console.log('foo')},conflicting: function () {console.log('from mixin')}}
}var vm = new Vue({mixins: [mixin],methods: {bar: function () {console.log('bar')},conflicting: function () {console.log('from self')}}
})vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
注意:Vue.extend() 也使用同样的策略进行合并。
1.3 全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({created: function () {var myOption = this.$options.myOptionif (myOption) {console.log(myOption)}}
})new Vue({myOption: 'hello!'
})
// => "hello!"
1.4 细数 mixin 存在的问题
- 命名冲突:vue组件的值会覆盖mixin选项,相同类型的生命周期钩子添加到同一个数组里依次执行
- 依赖不透明:mixin 和使用它的组件之间没有层次关系。组件可以用mixin的数据,mixin可以用设定在vue里的数据(如上文的this.$options.myOption)。这样要是修改mixin,就比较复杂。
2、vue.js 动画特效& 常见组件库介绍
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js;
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
2.1 进入/离开基础使用示例
<div id="demo"><button v-on:click="show = !show">Toggle</button><transition name="fade"><p v-if="show">hello</p></transition>
</div>
.fade-enter-active, .fade-leave-active {transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {opacity: 0;
}
export default {data() {show: true}
}
2.2 进入/离开自定义过度类名
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<div id="example-3"><button @click="show = !show">Toggle render</button><transitionname="custom-classes-transition"enter-active-class="animated tada"leave-active-class="animated bounceOutRight"><p v-if="show">hello</p></transition>
</div>
2.3 进入/离开动画钩子
<transitionv-on:before-enter="beforeEnter"v-on:enter="enter"v-on:after-enter="afterEnter"v-on:enter-cancelled="enterCancelled"v-on:before-leave="beforeLeave"v-on:leave="leave"v-on:after-leave="afterLeave"v-on:leave-cancelled="leaveCancelled"
><!-- ... -->
</transition>
2.4 多组件过渡与列表过渡
<div id="list-demo" class="demo"><button v-on:click="add">Add</button><button v-on:click="remove">Remove</button><transition-group name="list" tag="p"><span v-for="item in items" v-bind:key="item" class="list-item">{{ item }}</span></transition-group>
</div>
{data: {items: [1,2,3,4,5,6,7,8,9],nextNum: 10},methods: {randomIndex: function () {return Math.floor(Math.random() * this.items.length)},add: function () {this.items.splice(this.randomIndex(), 0, this.nextNum++)},remove: function () {this.items.splice(this.randomIndex(), 1)},}
}
.list-item {display: inline-block;margin-right: 10px;
}
.list-enter-active, .list-leave-active {transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {opacity: 0;transform: translateY(30px);
}
2.5 状态过渡
通过状态去驱动视图更新从而实现动画过渡
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script><div id="animated-number-demo"><input v-model.number="number" type="number" step="20"><p>{{ animatedNumber }}</p>
</div>
{data: {number: 0,tweenedNumber: 0},computed: {animatedNumber: function() {return this.tweenedNumber.toFixed(0);}},watch: {number: function(newValue) {gsap.to(this.$data, { duration: 0.5, tweenedNumber: newValue });//gsap js动画库, gsap.to('要变化的对象,数据对象或者dom对象',{对象里变化的参数},)}}
}
2.6 常用动画相关库
- gsap
- animated.css
- tween.js
3、 插槽
插槽 slot 是写在子组件的代码中,供父组件使用的占位符
3.1 插槽的三种使用方法
- 默认插槽:没有名字,普通的
- 具名插槽:带名字,父组件可以根据名字插入子组件的对应的位置(多个,区分占位符位置)<slot name='‘header’>(区分位置)
- 作用域插槽:父组件可以使用子组件插槽传过来的games数据(传值)
ps: vue 2.6.0版本之后的slot插槽: 用v-slot:default=‘ctx’ 替代slot=‘’。
默认和具名案例:
<template><div class="parent">我是父组件<Child></Child></div>
</template><script>
import Child from './components/Child.vue'export default {name: 'app',components: {Child}
}
</script><style>
.parent {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;padding:10px;background-color:#eee;
}
</style>
<template><div class="hello">我是子组件<!-- 如果父组件没有填充内容,就显示slot里的默认内容,如果父组件填充内容,显示父组件的内容 --><div><!-- 具名插槽 --><slot name="header">把头放这里</slot> </div><div><!-- 默认插槽 --><slot>这里是默认插槽</slot> </div><div><!-- 具名插槽 --><slot name="footer">把尾巴放这里</slot></div></div>
</template><script>
export default {name: 'Child',}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>.hello{background-color: #097493;color: #fff;padding:20px;
}
.hello>div{padding:10px;border: 1px solid red;margin: 5px;
}
</style>
当父组件什么都没传时,子组件按自己默认的显示
当父组件填入对应内容,将会替换子组件的默认占位(子组件也可以不设置默认内容)
父组件改为
<template><div class="parent">我是父组件<Child><div slot="header"> 替换头部内容</div><div slot="header">替换头部内容2</div><div slot="footer">替换底部内容</div><div>没有定义slot='',默认放默认插槽</div><div>如果再来一个默认插槽呢,都会进入默认插槽里</div></Child></div>
</template><script>
import Child from './components/Child.vue'export default {name: 'app',components: {Child}
}
</script><style>
.parent {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;padding:10px;background-color:#eee;
}
</style>
显示:
作用域插槽:子组件定义数据,传出去,在父组件用slot-scope接收并使用,就是作用域插槽的功能
<template><div class="parent">我是父组件<Child><div slot="header" > 替换头部内容</div><div slot="footer" slot-scope="user">替换底部内容{{user.games}}</div><div slot-scope="{games}">没有定义slot='',默认放默认插槽{{games}}</div></Child></div>
</template><script>
import Child from './components/Child.vue'export default {name: 'app',components: {Child}
}
</script><style>
.parent {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;padding:10px;background-color:#eee;
}
</style>
<template><div class="hello">我是子组件<!-- 如果父组件没有填充内容,就显示slot里的默认内容,如果父组件填充内容,显示父组件的内容 --><div><!-- 具名插槽 --><slot :games="games" name="header">把头放这里</slot> </div><div><!-- 默认插槽 --><slot :games="games">这里是默认插槽</slot> </div><div><!-- 具名插槽 --><slot :games="games" name="footer">把尾巴放这里</slot></div></div>
</template><script>
export default {name: 'Child',data() {return {games:['王者荣耀','吃鸡','斗地主']}},
}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>.hello{background-color: #097493;color: #fff;padding:20px;
}
.hello>div{padding:10px;border: 1px solid red;margin: 5px;
}
</style>
显示:
4、插件
插件可以是对象,或者是一个函数。如果是对象,那么对象中需要提供 install 函数,如果是函数,形态需要跟前面提到的 install 函数保持一致。
install 是组件安装的一个方法,跟 npm install 完全不一样,npm install 是一个命令
4.1 定义插件
const MyPlugin = {install(Vue, options) {// 1. 添加全局方法或 propertyVue.myGlobalMethod = function () {// 逻辑...}// 2. 添加全局资源Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}...})// 3. 注入组件选项Vue.mixin({created: function () {// 逻辑...}...})// 4. 添加实例方法Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...}}
};
4.2 使用插件
Vue.use(MyPlugin);{{ $myMethod }}
4.3 插件化机制原理
export function initUse (Vue: GlobalAPI) {Vue.use = function (plugin: Function | Object) {// 获取已经安装的插件const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))// 看看插件是否已经安装,如果安装了直接返回if (installedPlugins.indexOf(plugin) > -1) {return this}// toArray(arguments, 1)实现的功能就是,获取Vue.use(plugin,xx,xx)中的其他参数。// 比如 Vue.use(plugin,{size:'mini', theme:'black'}),就会回去到plugin意外的参数const args = toArray(arguments, 1)// 在参数中第一位插入Vue,从而保证第一个参数是Vue实例args.unshift(this)// 插件要么是一个函数,要么是一个对象(对象包含install方法)if (typeof plugin.install === 'function') {// 调用插件的install方法,并传入Vue实例plugin.install.apply(plugin, args)} else if (typeof plugin === 'function') {plugin.apply(null, args)}// 在已经安装的插件数组中,放进去installedPlugins.push(plugin)return this}
}
4.4 具体实践
Vue-Router
for Vue2
import View from './components/view'
import Link from './components/link'export let _Vueexport function install (Vue) {if (install.installed && _Vue === Vue) returninstall.installed = true_Vue = Vueconst isDef = v => v !== undefinedconst registerInstance = (vm, callVal) => {let i = vm.$options._parentVnodeif (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {i(vm, callVal)}}Vue.mixin({beforeCreate () {if (isDef(this.$options.router)) {this._routerRoot = thisthis._router = this.$options.routerthis._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 hooksstrats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
for Vue3
install(app: App) {const router = thisapp.component('RouterLink', RouterLink)app.component('RouterView', RouterView)app.config.globalProperties.$router = routerObject.defineProperty(app.config.globalProperties, '$route', {enumerable: true,get: () => unref(currentRoute),})// this initial navigation is only necessary on client, on server it doesn't// make sense because it will create an extra unnecessary navigation and could// lead to problemsif (isBrowser &&// used for the initial navigation client side to avoid pushing// multiple times when the router is used in multiple apps!started &¤tRoute.value === START_LOCATION_NORMALIZED) {// see abovestarted = truepush(routerHistory.location).catch(err => {if (__DEV__) warn('Unexpected error when starting the router:', err)})}const reactiveRoute = {} as {[k in keyof RouteLocationNormalizedLoaded]: ComputedRef<RouteLocationNormalizedLoaded[k]>}for (const key in START_LOCATION_NORMALIZED) {// @ts-expect-error: the key matchesreactiveRoute[key] = computed(() => currentRoute.value[key])}app.provide(routerKey, router)app.provide(routeLocationKey, reactive(reactiveRoute))app.provide(routerViewLocationKey, currentRoute)const unmountApp = app.unmountinstalledApps.add(app)app.unmount = function () {installedApps.delete(app)// the router is not attached to an app anymoreif (installedApps.size < 1) {// invalidate the current navigationpendingLocation = START_LOCATION_NORMALIZEDremoveHistoryListener && removeHistoryListener()removeHistoryListener = nullcurrentRoute.value = START_LOCATION_NORMALIZEDstarted = falseready = false}unmountApp()}// TODO: this probably needs to be updated so it can be used by vue-termuiif ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {addDevtools(app, router, matcher)}},}
5、过滤器
Vue.js允许我们自定义过滤器,对数据进行格式化。过滤器应该放在JS表达式的尾部,由管道符号连接。过滤器可以用在两个地方:双花括号差值和v-bind表达式。
5.1 使用过滤器
<!-- 在双花括号中 -->
{{ message | capitalize }}<!-- 在 `v-bind` 中 -->
<div v-bind:id="message | capitalize"></div>
5.2 定义过滤器
组件中定义过滤器:
filters: {capitalize: function (value) {if (!value) return ''value = value.toString()return value.charAt(0).toUpperCase() + value.slice(1)}
}
全局中定义过滤器:
Vue.filter('capitalize', function (value) {if (!value) return ''value = value.toString()return value.charAt(0).toUpperCase() + value.slice(1)
})new Vue({// ...
})
5.3 串联过滤器
我们可以同时使用多个过滤器,过滤器函数总接收表达式的值 (上一个过滤器的结果) 作为第一个参数。
{{ message | filterA | filterB }}
过滤器是 JavaScript 函数,因此可以接收参数:
ps:没有传参时候,默认传入当前值filterA
等价于filterA(message)
{{ message | filterA('arg1', 'arg2') }}