Vue2高级用法

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.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

1.4 细数 mixin 存在的问题

  1. 命名冲突:vue组件的值会覆盖mixin选项,相同类型的生命周期钩子添加到同一个数组里依次执行
  2. 依赖不透明: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>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>

2.3 进入/离开动画钩子

<transition
  v-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. 添加全局方法或 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) {
        // 逻辑...
      }
    }
};

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

for Vue3

 
    install(app: App) {
      const router = this
      app.component('RouterLink', RouterLink)
      app.component('RouterView', RouterView)

      app.config.globalProperties.$router = router
      Object.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 problems
      if (
        isBrowser &&
        // used for the initial navigation client side to avoid pushing
        // multiple times when the router is used in multiple apps
        !started &&
        currentRoute.value === START_LOCATION_NORMALIZED
      ) {
        // see above
        started = true
        push(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 matches
        reactiveRoute[key] = computed(() => currentRoute.value[key])
      }

      app.provide(routerKey, router)
      app.provide(routeLocationKey, reactive(reactiveRoute))
      app.provide(routerViewLocationKey, currentRoute)

      const unmountApp = app.unmount
      installedApps.add(app)
      app.unmount = function () {
        installedApps.delete(app)
        // the router is not attached to an app anymore
        if (installedApps.size < 1) {
          // invalidate the current navigation
          pendingLocation = START_LOCATION_NORMALIZED
          removeHistoryListener && removeHistoryListener()
          removeHistoryListener = null
          currentRoute.value = START_LOCATION_NORMALIZED
          started = false
          ready = false
        }
        unmountApp()
      }

      // TODO: this probably needs to be updated so it can be used by vue-termui
      if ((__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') }}
  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 2 中的高级组件开发可以通过 mixin、插槽和自定义指令等技术来实现。下面是一些常见的技巧: 1. Mixin:Mixin 是一种重用组件逻辑的方式。通过将常用的选项合并到 mixin 对象中,然后在组件中使用 mixins 选项来引入 mixin。这样可以有效地提高代码的复用性。例如,您可以创建一个 mixin 对象,包含一些常用的方法和生命周期钩子函数,然后在需要的组件中引入。 2. 插槽(Slot):插槽是一种在父组件中向子组件传递内容的方式。通过在父组件中使用<slot>标签,并在子组件中使用<slot>标签的 name 属性来定义插槽。这样可以实现组件的灵活性,使父组件能够根据需要向子组件传递不同的内容。 3. 自定义指令(Custom Directive):自定义指令是一种在 HTML 元素上添加特定行为的方式。通过使用 Vue.directive 函数来定义一个全局的指令,然后在模板中使用 v-directive 指令来调用。自定义指令可以用于处理 DOM 元素的事件、样式、属性等。 4. 动态组件(Dynamic Component):动态组件允许您根据条件渲染不同的组件。通过使用<component>标签并通过 is 属性来指定要渲染的组件,可以根据需要切换不同的组件。 5. 渲染函数(Render Function):Vue 2 中可以使用 render 函数来编写组件的模板。使用 render 函数可以实现更灵活、动态的组件渲染。通过编写 JavaScript 代码来生成组件的虚拟 DOM 树,并将其返回给 Vue 实例进行渲染。 这些是一些基本的高级组件开发技巧,Vue 2 还提供了更多强大的特性和工具,您可以根据具体需求进一步深入学习和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值