利用函数式组件作二次封装

前言

随着技术的快速发展,前端为了快速开发,咱们通常会接入像elementui这样的库,以element为例,一些组件没法知足咱们的需求,就须要作二次封装。今天想着尝试利用vue的函数式组件作一下二次封装。html

先来看一个最简单的demo来补充点基础知识

// demo.vue
<template>  <div class="demo">  <DeInput @debounce="debounce" maxlength='5' @blur="inputBlur"/>  </div> </template> <script> import DeInput from './DeInput' export default {  name: 'Demo',  components: {  DeInput  },  methods: {  debounce(value) {  console.log('防抖后:', value)  },  inputBlur() {  console.log('失去焦点')  }  } }  // deinput.vue <template>  <div>  <el-input v-model="inputValue" @input="deInput"></el-input>  </div> </template> <script> export default {  data() {  return {  inputValue: ''  }  },  methods: {  deInput() {  this.$emit('debounce', this.inputValue)  }  } } 复制代码

若是去运行这段代码就会发现inputBlur这个函数根本没有执行,maxlength这个属性也没有生效,这是由于@blur和 maxlength是el-input内部方法和属性。若是想要调用,就须要作透传,换句话说就是让el-input知道它的方法或者属性被调用。其实只要vue提供的\$attrs和$listeners属性便可。前端

  • $attrs:包含了父做用域中不做为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含全部父做用域的绑定 (class 和 style 除外),而且能够经过 v-bind="$attrs" 传入内部组件——在建立高级别的组件时很是有用。
  • $listeners:包含了父做用域中的 (不含 .native 修饰器的) v-on 事件监听器。它能够经过 v-on="$listeners" 传入内部组件——在建立更高层次的组件时很是有用。

咱们来加一下这两个属性, 再次去执行的时候发现inputBlur这个函数已经能够被调用了,maxlength也生效了,因为太过简单,就不作过多解释vue

// deinput.vue
<el-input v-model="inputValue" @input="deInput" v-bind="$attrs" v-on="$listeners"></el-input> 复制代码

其实这已经给咱们提供了大部分的思路,接下来咱们试试用函数式组件的思路是否能知足咱们的需求web

函数式组件

定义:咱们能够将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。一个函数式组件就像这样:数组

Vue.component('my-component', {
 functional: true,  // Props 是可选的  props: {  // ...  },  // 为了弥补缺乏的实例  // 提供第二个参数做为上下文  render: function (createElement, context) {  // ...  } }) 复制代码

为何要用函数式组件?编辑器

  • 由于函数式组件只是函数,因此渲染开销也低不少。

试着函数式组件的封装一个能够防抖的input标签

// debouce.js
const debounce = (fn, delay=500, Switch=true) => {  let timeout = null;  return (params) => {  clearTimeout(timeout)   if (!Switch) {  return fn(params)  }   timeout = window.setTimeout(() => {  fn(params)  }, Number(delay))  } }  export default {  functional: true,  render: function(createElement, context) {  const vNodeLists = context.slots().default // 这里其实能够替换为context.children  const time = context.props.time  const Switch = context.props.Switch   if (!vNodeLists) {  console.warn('必需要有一个子元素')  return null  }   const vNode = vNodeLists[0] || {}   // 咱们获取到其input方法进行二次封装  if (vNode.tag && vNode.tag === 'input') {  const funDefault = vNode.data.on && vNode.data.on.input  if (!funDefault) {  console.warn('请传入input方法(@input)')  return null  }  const fun = debounce(funDefault, time, Switch)   vNode.data.on.input = fun  } else {  console.warn('仅支持input')  return null  }  return vNode  } } 复制代码

看一下这个组件如何被使用

<template>
 <div class="home">  <Debounce time='1000' :Switch='true'>  <input type="text" @input="debounce"/>  </Debounce>  </div> </template>  <script> import Debounce from '../components/debounce'  export default {  components: {  Debounce  },  methods: {  debounce(e) {  console.log('防抖后:', e.target.value)  }  } } </script> 复制代码

咱们再来尝试封装一个elementui的el-button组件

// debounce.js 关键代码
if (vNode.componentOptions && vNode.componentOptions.tag === 'el-button') {  const funDefault = vNode.componentOptions.listeners && vNode.componentOptions.listeners.click  if (!funDefault) {  console.warn('请传入click方法(@click)')  return null  }  const fun = debounce(funDefault, time, Switch)   vNode.componentOptions.listeners.click = fun  } 复制代码

咱们elementui的组件和原生标签的区别是须要经过vNode.componentOptions获取,接下来贴出完整的代码函数

const debounce = (fn, delay=500, Switch=true) => {
 let timeout = null;  return (params) => {  clearTimeout(timeout)   if (!Switch) {  return fn(params)  }   timeout = window.setTimeout(() => {  // el-button获取到的是数组,input获取到的是function  if (!Array.isArray(fn)) {  fn = [fn]  }   fn[0](params)  }, 1000)  } }  export default {  functional: true,  render: function(createElement, context) {  const vNodeLists = context.slots().default  const time = context.props.time  const Switch = context.props.Switch   if (!vNodeLists) {  console.warn('必需要有一个子元素')  return null  }   const vNode = vNodeLists[0] || {}   if (vNode.componentOptions && vNode.componentOptions.tag === 'el-button') {  const funDefault = vNode.componentOptions.listeners && vNode.componentOptions.listeners.click  if (!funDefault) {  console.warn('请传入click方法(@click)')  return null  }  const fun = debounce(funDefault, time, Switch)   vNode.componentOptions.listeners.click = fun  } else if (vNode.tag && vNode.tag === 'input') {  const funDefault = vNode.data.on && vNode.data.on.input  if (!funDefault) {  console.warn('请传入input方法(@input)')  return null  }  const fun = debounce(funDefault, time, Switch)   vNode.data.on.input = fun  } else {  console.warn('仅支持input和el-button')  return null  }  return vNode  } } 复制代码

简单看一下效果吧

思考:以这种方式封装el-input是否会有问题?

本文使用 mdnice 排版flex

相关文章
相关标签/搜索