この記事では、Vueのピュアな辛口情報と、あなたの知らないVue.nextTickについてご紹介します。
1. NextTick クイズNextTick を本当に理解していますか?さあ、直接質問に行きましょう~Vue を使用したことのある友人は多かれ少なかれそれを知っているでしょう
$nextTick
~ nextTick を正式に説明する前に、Vue について明確に理解しておく必要があると思います。 DOM を更新するときは、非同期で実行されます。次の説明プロセスはコンポーネントの更新と一緒に説明します~ 早速、本題に入りましょう (この記事では
v2.0 を使用します)。 6.14 バージョンの Vue ソース コードを説明します) [関連する推奨事項: vuejs ビデオ チュートリアル ]
<template> <div id="app"> <p ref="name">{{ name }}</p> <button @click="handleClick">修改name</button> </div> </template> <script> export default { name: 'App', data () { return { name: '井柏然' } }, mounted() { console.log('mounted', this.$refs.name.innerText) }, methods: { handleClick () { this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText)) } } } </script>
が「名前の変更」ボタン をクリックすると、'nextTick1',
'同期ログ' 、
'nextTick2' に対応する
this.$refs.name.innerText がそれぞれ出力されますか?
注、ここに出力されるのはDOMのinnerTextです~(答えは記事の最後に掲載されます)
Queue, pending
Status<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">const callbacks = [] // 存放cb的队列
let pending = false // 是否马上遍历队列,执行cb的标志</pre><div class="contentsignin">ログイン後にコピー</div></div>
コールバックをトラバースして各 cbfunction flushCallbacks () { pending = false // 注意这里,一旦执行,pending马上被重置为false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() // 执行每个cb } }
の非同期実装 実行環境のサポートの程度に応じて、さまざまな非同期実装戦略を採用する let timerFunc // nextTick异步实现fn if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise方案 const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 将flushCallbacks包装进Promise.then中 } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // MutationObserver方案 let counter = 1 const observer = new MutationObserver(flushCallbacks) // 将flushCallbacks作为观测变化的cb const textNode = document.createTextNode(String(counter)) // 创建文本节点 // 观测文本节点变化 observer.observe(textNode, { characterData: true }) // timerFunc改变文本节点的data,以触发观测的回调flushCallbacks timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate方案 timerFunc = () => { setImmediate(flushCallbacks) } } else { // 最终降级方案setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } }
はマクロ タスクの最初のラウンドで実行されますが、これは問題ありません
cb
、Promiseconst observer = new MutationObserver(() => console.log('观测到文本节点变化')) const textNode = document.createTextNode(String(1)) observer.observe(textNode, { characterData: true }) console.log('script start') setTimeout(() => console.log('timeout1')) textNode.data = String(2) // 这里对文本节点进行值的修改 console.log('script end')
詳細については、pending
が何に使用され、どのように機能するかを理解していますか? $nextTick
、<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 往全局的callbacks队列中添加cb
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, &#39;nextTick&#39;)
}
} else if (_resolve) {
// 这里是支持Promise的写法
_resolve(ctx)
}
})
if (!pending) {
pending = true
// 执行timerFunc,在下一个Tick中执行callbacks中的所有cb
timerFunc()
}
// 对Promise的实现,这也是我们使用时可以写成nextTick.then的原因
if (!cb && typeof Promise !== &#39;undefined&#39;) {
return new Promise(resolve => {
_resolve = resolve
})
}
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
画像を使用した方が直感的ですか?
Vue のコンポーネント化がある場合は # # ディストリビューションのアップデート
あまり詳しくない方は、Vue の 非同期更新DOM は、実際には nextTick を使用して実装されます。これは、実際には通常使用する $nextTick と同じです~ここで、何が起こるかを確認します。属性の値を変更するとき?
上図のディストリビューション更新プロセスに従って、watcher.update の先頭から開始し、例として
Rendering Watcher
queueWatcher
里1 と入力します。queueWatcher は何をしましたか? <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">this.$nextTick(() => console.log(&#39;nextTick1&#39;))
this.$nextTick(() => console.log(&#39;nextTick2&#39;))</pre><div class="contentsignin">ログイン後にコピー</div></div>
// 用来存放Wathcer的队列。注意,不要跟nextTick的callbacks搞混了,都是队列,但用处不同~ const queue: Array<Watcher> = [] function queueWatcher (watcher: Watcher) { const id = watcher.id // 拿到Wathcer的id,这个id每个watcher都有且全局唯一 if (has[id] == null) { // 避免添加重复wathcer,这也是异步渲染的优化做法 has[id] = true if (!flushing) { queue.push(watcher) } if (!waiting) { waiting = true // 这里把flushSchedulerQueue推进nextTick的callbacks队列中 nextTick(flushSchedulerQueue) } } }
1、如图所示,mounted
时候的innerText是“井柏然”的中文
2、接下来是点击按钮后,打印结果如图所示
没错,输出结果如下(意不意外?惊不惊喜?)
sync log 井柏然
nextTick1 井柏然
nextTick2 jngboran
下面简单分析一下每个输出:
this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
sync log
:这个同步打印没什么好说了,相信大部分童鞋的疑问点都不在这里。如果不清楚的童鞋可以先回顾一下EventLoop,这里不多赘述了~
nextTick1
:注意其虽然是放在$nextTick
的回调中,在下一个tick执行,但是他的位置是在this.name = 'jngboran'
的前。也就是说,他的cb会比App组件的派发更新(flushSchedulerQueue
)更先进入队列,当nextTick1
打印时,App组件还未派发更新,所以拿到的还是旧的DOM值。
nextTick2
就不展开了,大家可以自行分析一下。相信大家对它应该是最肯定的,我们平时不就是这样拿到更新后的DOM吗?
最后来一张图加深理解
写在最后,nextTick其实在Vue中也算是比较核心的一个东西了。因为贯穿整个Vue应用的组件化、响应式的派发更新与其息息相关~深入理解nextTick的背后实现原理,不仅能让你在面试的时候一展风采,更能让你在日常开发工作中,少走弯路少踩坑!好了,本文到这里就暂告一段落了,如果读完能让你有所收获,就帮忙点个赞吧~画图不易、创作艰辛鸭~
以上がVue の Vue.nextTick を理解するのに役立つ情報を共有するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。