VUEのジッタ防止とスロットルの最適な解決策--関数式コンポーネント

13552 ワード

前言
echartsの使用経験のある学生はwindowでこのようなシーンに出会ったことがあるかもしれません.onresizeイベントコールバックでechartsBoxがトリガーされました.resize()メソッドは、再描画の目的を達成します.resizeイベントは連続的にトリガーされます.これは、echartsインスタンスが連続的に再描画されることを意味します.これは、非常にパフォーマンスを消費します.もう1つの一般的なシーンは、inputラベルのinputイベントでバックエンドインタフェースを要求し、inputイベントも連続的にトリガーされます.私が「12」を入力すると、2回のインタフェースパラメータがそれぞれ「1」と「12」であることを要求します.ネットワークリソースを浪費するよりも、パラメータが「1」の要求がデータを返す時間がパラメータが「12」のインタフェースよりも遅い場合、私たちが得たデータは期待に合わない.もちろんaxiosに基づいて多くのパッケージを作成することは、前のリクエストをキャンセルしたり、ブロックしたりすることができますが、ジッタ防止から始めたほうが簡単です.
防震と節流とはいったい何なのか.
関数ジッタ防止(debounce)
説明:あるイベントをトリガし続けると、一定時間間隔内にイベントが再トリガされない場合、イベント処理関数は一度実行され、設定された時間間隔が来る前に、またイベントがトリガされると、遅延が再開されます.ケース:scrollイベントが継続的にトリガーされると、handle関数はすぐに実行されず、1000ミリ秒以内にscrollイベントがトリガーされない場合、handle関数は遅延してトリガーされます.
function debounce(fn, wait) {
  let timeout = null
  return function() {
    if(timeout !== null) clearTimeout(timeout)     
    timeout = setTimeout(fn, wait);
  }
}
function handle() {   
  console.log(Math.random())
}
window.addEventListener('scroll', debounce(handle, 1000))

addEventListenerの2番目のパラメータは実際にdebounce関数でreturn回の方法であり、let timeout = nullという行のコードはaddEventListenerの時に1回のトリガイベントを実行した場合にのみ実行されず、scrollイベントをトリガーするたびに前回の遅延器を消去して新しい遅延器を同時に記録し、scrollイベントが停止してトリガされた後に最後に記録された遅延器がクリアされない場合、debounce関数の原理です.
関数スロットル
説明:イベントが継続的にトリガーされると、規則的に1時間間隔おきにイベント処理関数が実行されます.ケース:scrollイベントが継続的にトリガーされると、handle関数はすぐに実行されず、1000ミリ秒ごとにhandle関数が実行されます.
function throttle(fn, delay) { 
  var prev = Date.now()         
  return function() {               
    var now = Date.now()               
    if (now - prev > delay) {                   
      fn()                
      prev = Date.now()             
    }         
  }       
}       
function handle() {           
  console.log(Math.random())      
}
window.addEventListener('scroll', throttle(handle, 1000))

原理はジッタ防止と同様で、fn関数を実行するたびにprevを更新して今回の実行時間を記録し、次のイベントがトリガーされたときに時間間隔が予め設定されているか否かを判断し、上記の操作を繰り返す.
ジッタ防止とスロットルはmousemove、scroll、resize、inputなどのイベントに使用できます.彼らの違いは、ジッタ防止は連続したイベントサイクルの終了時に1回しか実行されず、スロットルはイベントサイクル内で間隔時間で規則的に複数回実行されることです.
vueでの実践
vueで振れ止めを実現するのは、次の2つの方法にほかならない.
  • パッケージutilsツール
  • パッケージ
  • パッケージutilsツール
    上記のケースを改造すると簡単なutilsツールがパッケージされます
    utils.js
    let timeout = null
    function debounce(fn, wait) {
      if(timeout !== null) clearTimeout(timeout)
      timeout = setTimeout(fn, wait)
    }
    export default debounce
    
    app.js
    type="text" @input="debounceInput($event)">
    
    import debounce from './utils'
    export default {
      methods: {
        debounceInput(E){
          debounce(() => {
            console.log(E.target.value)
          }, 1000)
        }
      }
    }
    

    パッケージングアセンブリ
    コンポーネントのパッケージについては、$listeners、$attrsという2つの属性を使用します.2人ともvue 2です.4新しい内容は、公式サイトの紹介が難しいので、彼ら二人が何をしているのか見てみましょう.$listeners:親コンポーネントは、サブコンポーネントをバインドするときにサブコンポーネントに多くの属性をバインドし、サブコンポーネントにprops登録によって使用されると、props登録されていないものは$listenersに格納され、classおよびstyleはもちろん含まれず、v-bind="$attrs"を介してサブコンポーネントの内部コンポーネントに転送することができる.$listeners:親アセンブリは、子アセンブリにバインドする含まない.native修飾器のイベントは$listeners に配置され、v-on="$listeners"を介して内部コンポーネントに伝達され得る.
    簡単に言えば$listeners、$attrs二人は属性とイベントの引き受けをしており、これはコンポーネントを二次パッケージ化する際に非常に有用である.
    element-uiのel-inputコンポーネントを例に、振れ止め付きdebounce-inputコンポーネントをパッケージします.
    debounce-input.vue
    
    
    <span class="hljs-built_in">export</span> default {
      <span class="hljs-function"><span class="hljs-title">data</span></span>() {
        <span class="hljs-built_in">return</span> {
          timeout: null
        }
      },
      methods: {
        debounceInput(value){
          <span class="hljs-keyword">if</span>(this.timeout !== null) clearTimeout(this.timeout)     
          this.timeout = <span class="hljs-built_in">set</span>Timeout(() => {
            this.<span class="hljs-variable">$emit</span>(<span class="hljs-string">'input'</span>, value)
          }, 1000)
        }
      }
    }
    
    
    app.vue
    
    
    import debounceInput from <span class="hljs-string">'./debounce-input'</span>
    <span class="hljs-built_in">export</span> default {
      methods: {
        inputEve(value){
          console.log(value)
        }
      },
      components: {
        debounceInput
      }
    }
    
    

    上のコンポーネントのパッケージには$attrsが用いられており,開発者が属性の伝達に注目する必要はないが,使用上は不便であり,el-inputを内部にパッケージするというスタイルの限定も限られているためである.react高次コンポーネントに接触したことがある学生は、react高次コンポーネントは本質的に関数であり、小包を通じて伝達されたReactコンポーネントであり、一連の処理を経て、最終的に相対的に強化されたReactコンポーネントを返す可能性があることを理解している.では、vueではこのような考え方を参考にすることができますか.vueの関数式コンポーネントを理解してみましょう.
    vue関数コンポーネントについて
    関数コンポーネントとは?
    関数コンポーネントとは、vueコンポーネントをFunctionでレンダリングすることです.このコンポーネントはいくつかのpropのみを受け入れます.このようなコンポーネントをfunctionalとマークすることができます.これは、ステータス(応答データなし)、インスタンス(thisコンテキストなし)がないことを意味します.
    関数コンポーネントは、次のようになります.
    export default () => {
      functional: true, 
      props: { 
        // Props     
      },
      //          ,             
      render: function (createElement, context) {
        return vNode
      }
    }
    

    注:2.3.0以前のバージョンでは、関数コンポーネントがpropを受信したい場合は、propsオプションが必要です.2.3.0以降のバージョンでは、propsオプションを省略することができ、すべてのコンポーネントの特性が自動的に暗黙的にpropとして解析されます.しかし、propを登録すると、登録されたpropだけがcontextに表示されます.propの中.
    render関数の2番目のパラメータcontextは、コンテキストthisの代わりに、次のフィールドを含むオブジェクトです.
  • props:すべてのpropを提供するオブジェクト
  • children:VNodeサブノードの配列
  • slots:すべてのスロットを含むオブジェクト
  • を返す関数です.
  • scopedSlots:(2.6.0+)入力された役割ドメインスロットを露出するオブジェクト.通常のスロットも関数として露出します.
  • data:コンポーネントに渡されたデータオブジェクト全体がcreateElementの2番目のパラメータとしてコンポーネント
  • に伝達される.
  • parent:親コンポーネントへの参照
  • listeners:(2.3.0+)現在のコンポーネントに登録されているすべての親コンポーネントを含むイベントリスナーのオブジェクト.これはdataです.onの別名.
  • injections:(2.3.0+)injectオプションが使用されている場合、オブジェクトには注入すべき属性が含まれます.

  • vm.$slots APIには何が入っていますか?
    slotsは、スロットによって配布されたコンテンツにアクセスするために使用されます.各名前付きスロットには、対応するプロパティがあります(v-slot:fooの内容はvm.$slots.fooで見つかります).defaultプロパティには、名前付きスロットに含まれていないすべてのノード、またはv-slot:defaultの内容が含まれます.
    slots()とchildrenの比較
    なぜslots()とchildrenが同時に必要なのか知りたいかもしれません.slots().defaultはchildrenと似ているのではないでしょうか.いくつかのシーンでは、そうですが、サブノード付きの関数コンポーネントは次のようになりますか?
    
      

    first

    second


    このコンポーネントに対してchildrenは2つの段落ラベルを与えますが、slots()です.defaultは2番目の匿名段落ラベル、slots()のみを渡します.fooは、最初の名前付き段落ラベルを渡します.childrenとslots()を同時に持つため、コンポーネントにスロットメカニズムを感知させるか、childrenを簡単に渡すか、他のコンポーネントに渡すかを選択できます.
    関数コンポーネントの使用シーン
    aコンポーネントが1つあると仮定し、a 1,a 2,a 3の3つのコンポーネントが導入され、aコンポーネントの親コンポーネントがaコンポーネントにtype属性を伝達し、typeの値aコンポーネントに基づいてa 1,a 2,a 3を表示するコンポーネントを決定する.このようなシーンaコンポーネントは関数式コンポーネントで非常に便利である.では、なぜ関数コンポーネントを使うのでしょうか.関数コンポーネントは関数にすぎないため、レンダリングコストが低いです.
    関数コンポーネントで振れ止めを実現
    ビジネス関係のため、このジッタ防止コンポーネントのパッケージはinput、button、el-input、el-buttonの使用を同時にサポートし、inputクラスコンポーネントであればinputイベントに対してジッタ防止処理を行い、buttonクラスコンポーネントであればclickイベントに対してジッタ防止処理を行う.
    const debounce = (fun, delay = 500, before) => {
      let timer = null
      return (params) => {
        timer && window.clearTimeout(timer)
        before && before(params)
        timer = window.setTimeout(() => {
           // click  fun Function  input  fun Array
          if (!Array.isArray(fun)) {
            fun = [fun]
          }
          for (let i in fun) {
            fun[i](params)
          }
          timer = null
        }, parseInt(delay))
      }
    }
    export default {
      name: 'Debounce',
      functional: true, //          functional                   
      render(createElement, context) {
        const before = context.props.before
        const time = context.props.time
        const vnodeList = context.slots().default
        if (vnodeList === undefined){
          console.warn('          ')
          return null
        }
        const vnode = vnodeList[0] || null //        dom
        if (vnode.tag === 'input') {
          const defaultFun = vnode.data.on.input
          const debounceFun = debounce(defaultFun, time, before) //       
          vnode.data.on.input = debounceFun
        } else if (vnode.tag === 'button') {
          const defaultFun = vnode.data.on.click
          const debounceFun = debounce(defaultFun, time, before) //       
          vnode.data.on.click = debounceFun
        } else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-input') {
          const defaultFun = vnode.componentOptions.listeners.input
          const debounceFun = debounce(defaultFun, time, before) //       
          vnode.componentOptions.listeners.input = debounceFun
        } else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-button') {
          const defaultFun = vnode.componentOptions.listeners.click
          const debounceFun = debounce(defaultFun, time, before) //       
          vnode.componentOptions.listeners.click = debounceFun
        } else {
          console.warn('                     el-button、el-input、button、input')
          return vnode
        }
        return vnode
      }
    }
    
    
    
    
    import debounce from <span class="hljs-string">'./debounce'</span>
    <span class="hljs-built_in">export</span> default {
      <span class="hljs-function"><span class="hljs-title">data</span></span>() {
        <span class="hljs-built_in">return</span> {
          inpModel: 1
        }
      },
      methods: {
        inputChange(e){
          console.log(e.target.value, <span class="hljs-string">'  '</span>)
        },
        beforeFun(e){
          console.log(e.target.value, <span class="hljs-string">'   '</span>)
        }
      },
      components: {
        debounce
      }
    }
    
    

    原理も簡単ですが、vNodeでonの下のclick、inputイベントを遮断してブレ止め処理を行うと、使用上非常に簡単です.
    カスタム命令directive
    関数コンポーネントパッケージのジッタ防止関節はvNodeを取得するので、カスタムコマンドでもvNodeを取得することができ、オリジナルのDomを得ることができ、カスタムコマンドで処理するのがより便利になります...
    関連読書$attrs or $listeners cn.vuejs.org/v2/api/#vm-… cn.vuejs.org/v2/guide/re… cn.vuejs.org/v2/guide/cu…
    転載先:https://juejin.im/post/5ce3e400f265da1bab298359