nextTickのプロジェクトでの実践

12742 ワード

前言
プロジェクトでは、ビューレイヤですぐにデータを表示する必要があることがよくありますが、非同期データ転送のため、ページにすぐにページを表示しない場合があります.この場合、Vueが提供するnexttickという方法を使用する必要があります.その主な原因は、Vueのデータビューが非同期で新しいためです.公式の解釈では、
Vue実装応答式は,データが変化した直後にDOMが変化するのではなく,一定のポリシーでDOMの更新を行う.
その中でも、前端面接でよく聞かれることの一つとして、本文は具体的な展開をしないで、興味のある学生はこの文章Event Loopを一度に理解する(このような面接問題を徹底的に解決する)を参考にすることができます.
ピットディレクトリ
  • テンプレートケースデータ
  • をビューに表示する.
  • 兄弟コンポーネント間非同期データ転送
  • $nextTickソースコード実装解析
  • 踏み込みケース
    テンプレート・ケース・データがビューに表示されます.
    [バグの説明]ページでリセットをクリックすると、テンプレートビューが固定データの下のビューにレンダリングされます.
    [バグ解析]クリックするとすぐにページに表示されます.これは典型的なnexttickが適用するシーンです.
    [ソリューション]
    ここでもう1つのピットは、配列タイプに対するリスニングが1つのアドレスに基づいているため、VueのWatcherが必要であれば、配列リスニングに合致するいくつかの方法が必要であり、ここでは直接新しく作成され、毎回のアドレスが変化することに相当するので、リスニングすることができる.
        async resetTemplate() {
          this.template = [];
          await this.$nextTick(function() {
              this.template = [
              {
                week: '1',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '2',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '3',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '4',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '5',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '6',
                starttime: '00:00:00',
                endtime: '00:00:00'
              },
              {
                week: '7',
                starttime: '00:00:00',
                endtime: '00:00:00'
              }
            ];
          });
        }

    兄弟コンポーネント間非同期データ転送
    [バグの説明]ページ修正ポップアップの入力ボックスフィールドは対応するフィールドに複写する必要があります.Propsでデータを転送しても、直接データを変更することはありません.
    [bug解析]このシーンでのデータはサブコンポーネントemitを介して親コンポーネントに渡され、親コンポーネントがデータを取得した後propsを介してポップアップウィンドウに渡され、v-modelでデータを取得するのは非同期です
    [ソリューション]
    これは比較的一般的ではない$nexttickを使用してv-model非同期データ伝達を処理する方法です(ps:emit/onのパブリケーション購読に関する紹介ですが、興味のある方はこの記事を見てみてください[vueパブリケーション購読者モード$emit、$on](https://blog.csdn.net/qq_4277...は、親コンポーネントのデータが次のtickに遅延してサブコンポーネントに伝達され、サブコンポーネントが対応するページ上でタイムリーにレンダリングされる方法を利用しているが、この方法に加えて、この文書vue親コンポーネントがprops非同期データをサブコンポーネントに渡す問題を詳細に説明するを参照してください.
        edit(data) {
          this.isManu = true;
          let [content,pos] = data;
          this.manuPos = pos;
          this.form = content;
          this.$nextTick(function(){
            this.$refs.deviceEdit.form.deviceid = content.deviceId;
            this.$refs.deviceEdit.form.devicename = content.deviceName;
            this.$refs.deviceEdit.form.devicebrand = content.deviceBrand;
            this.$refs.deviceEdit.form.devicegroup = content.deviceGroup;
            this.$refs.deviceEdit.form.mediatrans = content.mediaTrans;
            this.$refs.deviceEdit.form.cloudstorage = content.cloudStorage;
            this.$refs.deviceEdit.form.longitude = content.longitude;
            this.$refs.deviceEdit.form.latitude = content.latitude;
            this.$refs.deviceEdit.form.altitude = content.altitude;
          })
        },

    $nextTickソースコード実装解析
    2.5以前のバージョン:
    /**
     * Defer a task to execute it asynchronously.
     */
    export const nextTick = (function () {
      const callbacks = []
      let pending = false
      let timerFunc
    
      function nextTickHandler () {
        pending = false
        const copies = callbacks.slice(0)
        callbacks.length = 0
        for (let i = 0; i < copies.length; i++) {
          copies[i]()
        }
      }
    
      // the nextTick behavior leverages the microtask queue, which can be accessed
      // via either native Promise.then or MutationObserver.
      // MutationObserver has wider support, however it is seriously bugged in
      // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
      // completely stops working after triggering a few times... so, if native
      // Promise is available, we will use it:
      /* istanbul ignore if */
      if (typeof Promise !== 'undefined' && isNative(Promise)) {
        var p = Promise.resolve()
        var logError = err => { console.error(err) }
        timerFunc = () => {
          p.then(nextTickHandler).catch(logError)
          // in problematic UIWebViews, Promise.then doesn't completely break, but
          // it can get stuck in a weird state where callbacks are pushed into the
          // microtask queue but the queue isn't being flushed, until the browser
          // needs to do some other work, e.g. handle a timer. Therefore we can
          // "force" the microtask queue to be flushed by adding an empty timer.
          if (isIOS) setTimeout(noop)
        }
      } else if (!isIE && typeof MutationObserver !== 'undefined' && (
        isNative(MutationObserver) ||
        // PhantomJS and iOS 7.x
        MutationObserver.toString() === '[object MutationObserverConstructor]'
      )) {
        // use MutationObserver where native Promise is not available,
        // e.g. PhantomJS, iOS7, Android 4.4
        var counter = 1
        var observer = new MutationObserver(nextTickHandler)
        var textNode = document.createTextNode(String(counter))
        observer.observe(textNode, {
          characterData: true
        })
        timerFunc = () => {
          counter = (counter + 1) % 2
          textNode.data = String(counter)
        }
      } else {
        // fallback to setTimeout
        /* istanbul ignore next */
        timerFunc = () => {
          setTimeout(nextTickHandler, 0)
        }
      }
    
      return function queueNextTick (cb?: Function, ctx?: Object) {
        let _resolve
        callbacks.push(() => {
          if (cb) {
            try {
              cb.call(ctx)
            } catch (e) {
              handleError(e, ctx, 'nextTick')
            }
          } else if (_resolve) {
            _resolve(ctx)
          }
        })
        if (!pending) {
          pending = true
          timerFunc()
        }
        if (!cb && typeof Promise !== 'undefined') {
          return new Promise((resolve, reject) => {
            _resolve = resolve
          })
        }
      }
    })()

    2.5以降のバージョン
    /* @flow */
    /* globals MutationObserver */
    
    import { noop } from 'shared/util'
    import { handleError } from './error'
    import { isIE, isIOS, isNative } from './env'
    
    export let isUsingMicroTask = false
    
    const callbacks = []
    let pending = false
    
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    
    // Here we have async deferring wrappers using microtasks.
    // In 2.5 we used (macro) tasks (in combination with microtasks).
    // However, it has subtle problems when state is changed right before repaint
    // (e.g. #6813, out-in transitions).
    // Also, using (macro) tasks in event handler would cause some weird behaviors
    // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
    // So we now use microtasks everywhere, again.
    // A major drawback of this tradeoff is that there are some scenarios
    // where microtasks have too high a priority and fire in between supposedly
    // sequential events (e.g. #4521, #6690, which have workarounds)
    // or even between bubbling of the same event (#6566).
    let timerFunc
    
    // The nextTick behavior leverages the microtask queue, which can be accessed
    // via either native Promise.then or MutationObserver.
    // MutationObserver has wider support, however it is seriously bugged in
    // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
    // completely stops working after triggering a few times... so, if native
    // Promise is available, we will use it:
    /* istanbul ignore next, $flow-disable-line */
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      const p = Promise.resolve()
      timerFunc = () => {
        p.then(flushCallbacks)
        // In problematic UIWebViews, Promise.then doesn't completely break, but
        // it can get stuck in a weird state where callbacks are pushed into the
        // microtask queue but the queue isn't being flushed, until the browser
        // needs to do some other work, e.g. handle a timer. Therefore we can
        // "force" the microtask queue to be flushed by adding an empty timer.
        if (isIOS) setTimeout(noop)
      }
      isUsingMicroTask = true
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      // PhantomJS and iOS 7.x
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {
      // Use MutationObserver where native Promise is not available,
      // e.g. PhantomJS, iOS7, Android 4.4
      // (#6466 MutationObserver is unreliable in IE11)
      let counter = 1
      const observer = new MutationObserver(flushCallbacks)
      const textNode = document.createTextNode(String(counter))
      observer.observe(textNode, {
        characterData: true
      })
      timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)
      }
      isUsingMicroTask = true
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      // Fallback to setImmediate.
      // Technically it leverages the (macro) task queue,
      // but it is still a better choice than setTimeout.
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else {
      // Fallback to setTimeout.
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }
    
    export function nextTick (cb?: Function, ctx?: Object) {
      let _resolve
      callbacks.push(() => {
        if (cb) {
          try {
            cb.call(ctx)
          } catch (e) {
            handleError(e, ctx, 'nextTick')
          }
        } else if (_resolve) {
          _resolve(ctx)
        }
      })
      if (!pending) {
        pending = true
        timerFunc()
      }
      // $flow-disable-line
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }

    異なるバージョンは主にtimeFuncの非同期関数の使用優先順位が異なり、2.5以降も若干異なるが、主にマイクロタスク関数とマクロタスク関数を暴露するかどうかの問題にある(ps:上の2.5以降のバージョンは2.6.11)
    2.5以前のバージョン:Promise=>MutationObserver=>settimeout
    2.5以降のバージョン:setImmediate=>MessageChannel=>Promise=>setTimeout
    まとめ
    jsの非同期実行メカニズムはフロントエンドの学生が身につけなければならない知識であり、その中でnexttickはその典型的な代表であり、nodeにもnexttickに関する方法があり、面接でも関連方法の実現をよく聞き、jsの基礎方法と特性を深く理解し、フロントエンドの開発中に穴を避けるのに役立つ.問題が発生するたびに、ほとんどの面接問題に関連する知識が現れ、基礎を築くことは永遠にエンジニアの上昇の堅固な基礎である.
    let callbacks = []
    let pending = false
    
    function nextTick (cb) {
        callbacks.push(cb)
    
        if (!pending) {
            pending = true
            setTimeout(flushCallback, 0)
        }
    }
    
    function flushCallback () {
        pending = false
        let copies = callbacks.slice()
        callbacks.length = 0
        copies.forEach(copy => {
            copy()
        })
    }

    リファレンス
  • Vue.nexttickの原理と用途
  • VueのnextTickを簡単に理解する
  • nextTickソースコード解析
  • Vue nextTickメカニズム
  • Vueソース解析のnextTick
  • 浅析NodeのnextTick
  • NodejsのnextTickとsetTimeout
  • Vue.js中this.$nexttick()の使用
  • vueパブリッシュ購読者モード$emit、$on
  • vue親コンポーネントがprops非同期データをサブコンポーネントに渡す問題を詳細に説明する
  • Event Loopを一度に理解する(このような面接問題を徹底的に解決する)