利用函數式組件作二次封裝

前言

隨着技術的快速發展,前端爲了快速開發,咱們通常會接入像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

相關文章
相關標籤/搜索