vue函数式组件之节流组件

  • 2019-09-29
  • 0
  • 0

什么是函数式组件

官方文档:https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

我理解的就是组件是使用render函数来渲染的;这个组件只接受一些 prop,我们标记组件为 functional,这样组件就是无状态(没有响应式数据),也没有实例 (没有this上下文),这样可以减少渲染开销,因为它只是一个函数。

抽象组件

如果一个函数式组件没有渲染实际的DOM,而是直接返回以及操作它的子元素。例如vue内置的keep-alivetransition组件。前辈们把这种称作为抽象组件。

例如:(throttle是个抽象组件)

<throttle>
  <button>点击</button>
</throttle>

渲染出的结果就是

<button>点击</button>

封装节流组件

节流:就是规定时间内触发事件只执行一次

场景:项目中经常有测试提某个按钮快速点击导致的一些bug,这时候我们可以用节流来处理,比如规定800ms内只执行一次

开搞上代码

// throttle.js文件
const throttle = (fn, wait) => {
  let lastTime = 0
  return function() {
    let nowTime = new Date().getTime()
    if(nowTime - lastTime > wait) {
      fn.apply(this,arguments)
      lastTime = nowTime
    }
  }
}
export default {
  name: 'throttle',
  props: {
    wait: {                 // 规定的时间参数,单位ms 默认800
      type: Number,
      default: 800
    }
  },
  functional: true,
  render(createElement, context) {
    const vnode = context.children[0] // 获取子元素虚拟dom
    const fn = vnode.data.on.click  // 拿到绑定click事件触发的函数
    const throttled = throttle(fn, context.props.wait)  // 节流处理后的函数
    vnode.data.on.click = throttled // 修改子VNode的事件
    return vnode    // 再把子VNode返回回去,渲染出来
  }
}

// app.vue
<template>
  <throttle :wait="600">
    <button @click="test">点击</button>
  </throttle>
</template>
<script>
import throttle from '@/components/throttle'
export default {
  components: {
    throttle
  },
  methods: {
    test() {
      setTimeout(() => {
        console.log('iamc')
      },1000)
    }
  }
}
</script>

这样就达到了点击事件节流的效果,其它事件,比如滚动、输入,同样的道理,可以通过props传递事件类型来做不同的处理,集成到一个组件上,这里就不多阐述了,需要注意的是:

  1. 该组件下只能有一个根元素,因为组件render函数就是获取的第一个子元素,然后做一些处理,再把第一个子元素返回,就算你写了两个,那么也只会渲染出第一个。如:
    <throttle :wait="600">
    <button @click="test">点击</button>
     <button @click="test2">点击2</button>
    </throttle>
    // 只会渲染出第一个元素
    <button @click="test">点击</button>
    
  2. 绑定事件修饰符不能用once修饰符(其它修饰符都可以),子元素使用once修饰符,在throttle组件中获取不到绑定的函数,什么原因,恕在下道行不够,还没摸透,如果你知道,大佬,教教我,这个问题是vue.2.6.6中,不知是设计如此,还是就是个bug

我为什么不用自定义指令做节流

节流组件的关键是获取vNode,自定义指令也可以拿到vNode,为什么不用自定义指令呢,且听我细细道来。一开始计划做节流,我就是用的自定义指令,然后写指令的过程中发现一些不足点,我们先来看下指令实现的代码

const throttle = (fn, wait) => {
  let lastTime = 0
  return function() {
    let nowTime = new Date().getTime()
    if(nowTime - lastTime > wait) {
      fn.apply(this,arguments)
      lastTime = nowTime
    }
  }
}

Vue.directive('throttle-click',{
    bind(el, binding, vnode) {
    const fn = binding.value    // 通过参数拿到click事件触发的函数
    const throttled = throttle(fn, 800) // 节流处理后的函数 默认800ms
    el.addEventListener("click", throttled) // 绑定上
    el._throttled = throttled   // 把函数赋值到el对象上,方便其它钩子函数访问
  },
  unbind(el) {
        // 指令与元素解绑时 解绑事件
    el.removeEventListener('click', el._debounced)
  }
})

// 使用
<button v-throttle-click="e => test(e,'测试')">点击</button>

不用的理由:

1、绑定事件不能这样写<button v-throttle-click="test('测试')">点击</button>因为指令binding.value的求值过程和事件绑定是不同的,不支持这样的写法所以就只能包一层return,如果说不需要传参,那么就可以<button v-throttle-click="test">点击</button>,要考虑传参就不能这样写,绑定事件不能传参,那就限制了使用场景,这是封装的忌讳

2、vue的v-on内置了很多的事件修饰符,而我们自己用指令做事件节流处理,就不能再用v-on绑定事件,如果要用那些修饰符,就得自己去实现那些修饰符的功能

上面两个理由让我放弃使用指令来做这件事,最终我选择使用抽象组件来完成

评论

还没有任何评论,你来说两句吧