7. 대량 비동기 업데이트 정책 및 nextTick 원리

6363 단어

대량 비동기 업데이트 정책 및 nxtTick 원리


비동기식 업데이트가 필요한 이유


앞의 몇 장에서 우리가 소개한 바와 같이 여러분들은 이미 Vue를 이해하셨을 거라고 믿습니다.js는 우리가 수정한 data 의 데이터를 어떻게 보기를 수정했습니까?간단하게 돌이켜 보면 이 안은 사실'setter -> Dep -> Watcher -> patch -> 의 과정이다.
가령 우리가 다음과 같은 상황이 있다고 가정한다면.


export default {
    data () {
        return {
            number: 0
        };
    },
    methods: {
        handleClick () {
            for(let i = 0; i < 1000; i++) {
                this.number++;
            }
        }
    }
}


클릭 버튼을 눌렀을 때 number 1000번 순환됩니다.
그러면 이전의 이해에 따르면 매번number이 +1이 될 때마다number 방법을 촉발setter하여 위의 절차에 따라 계속 뛰어내려 마지막에 실제 DOM을 수정한다.그러면 이 과정에서 DOM은 1000번 업데이트됩니다!너무 무서워요.
Vue.js는 이렇게 비효율적인 방법으로 처리하지 않을 것이다.Vue.js는 기본적으로 어떤 데이터를 터치할 때마다setter 방법은 대응하는 Watcher 대상이 push 하나의 대기열queue에 들어가고 다음tick에서 이 대기열queue을 모두 꺼낸다runWatcher 대상의 한 방법으로 터치patch 작업에 사용한다.
그럼 다음 틱은 뭘까요?

nextTick


Vue.js는 nextTick 함수를 실현하여 cb에 전송했습니다. 이 cb는 하나의 대기열에 저장되고 다음tick에서 대기열의 모든 cb 이벤트를 터치합니다.
현재 브라우저 플랫폼이 실현nextTick 방법이 없기 때문에 Vue.js 원본에서 각각 Promise, setTimeout, setImmediate 등 방식으로 마이크로task(또는task)에서 이벤트를 만듭니다. 현재 호출 창고가 실행된 후에(또는 즉각적으로) 이 이벤트를 실행하기 위해서입니다.
필자는setTimeout로 이 방법을 모의했다. 물론 실제 원본 코드는 더욱 복잡할 것이다. 필자는 소책에서 원리만 이야기하고 원본 코드nextTick의 구체적인 실현에 관심이 있는 학생은next-tick을 참고할 수 있다.
우선 callbacks수조를 정의하여 저장nextTick하고 다음tick에서 이 리셋 함수를 처리하기 전에 모든 cb수조가 이 callbacks수조에 존재합니다.pending는 하나의 표시 위치로 기다리는 상태를 대표한다.setTimeouttask에서 이벤트flushCallbacks를 만들고flushCallbacks는 실행할 때callbacks의 모든cb를 순서대로 실행합니다.
let callbacks = [];
let pending = false;

function nextTick (cb) {
    callbacks.push(cb);

    if (!pending) {
        pending = true;
        setTimeout(flushCallbacks, 0);
    }
}

function flushCallbacks () {
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; i < copies.length; i++) {
        copies[i]();
    }
}


그리고 왓쳐.


첫 번째 예에서 우리가 number를 1000번 늘릴 때 대응하는 Watcher 대상을 push에 한 대열queue에 넣고 다음 tick을 기다렸다가 실행하는 것이 옳다.그런데 또 다른 문제가 생겼는지 발견됐나요?number++ 조작을 실행한 후에 대응하는Watcher 대상은 모두 동일하기 때문에 우리는 다음tick에서 1000개의 동일Watcher 대상을 실행하여 인터페이스를 수정할 필요가 없고 하나Watcher 대상을 실행하여 인터페이스의 0을 1000으로 바꾸면 된다.
그러면 우리는 필터를 실행해야 한다. 같은 Watcher는 같은 tick에서 한 번만 실행되어야 한다. 즉, 대기열queue에 중복된 Watcher 대상이 나타나지 말아야 한다.
그럼 우리 Watcher 대상에게 이름을 지어주자~ id로 대상마다Watcher 대상을 표시해서'다르지 않다'고 하자.
실현update 방법은 데이터를 수정한 후Dep에 호출되고 run 방법이야말로 진정한 터치patch로 보기를 업데이트하는 방법이다.
let uid = 0;

class Watcher {
    constructor () {
        this.id = ++uid;
    }

    update () {
        console.log('watch' + this.id + ' update');
        queueWatcher(this);
    }

    run () {
        console.log('watch' + this.id + ' ~');
    }
}


queueWatcher


눈치 채셨는지 모르겠어요.필자는 이미 Watcherupdate에서의 실현을 바꾸었다
queueWatcher(this);

Watcher 대상 자체를 queueWatcher 방법에 전달한다.
우리 queueWatcher 방법을 실현해 봅시다.
let has = {};
let queue = [];
let waiting = false;

function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);

        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}


우리는 has라는 맵을 사용하여 id->true(false)를 저장하는 형식으로 같은 Watcher 대상이 존재하는지 판단한다(이렇게 하면 매번 훑어보는 것보다 효율이 훨씬 높다).
만약 현재 대기열queue에 이queue 대상이 없다면 이 대상은 Watcher에 대기열push에 들어갈 것이다.queue는 표기 위치입니다. 표기가 waiting에 전달되었는지 여부nextTick 방법입니다. 다음 tick에서 실행flushSchedulerQueue 방법으로flush 대기열flushSchedulerQueue을 실행하고 그 안에 있는 모든queue 대상의 Watcher 방법을 실행합니다.

flushSchedulerQueue

function flushSchedulerQueue () {
    let watcher, id;

    for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        id = watcher.id;
        has[id] = null;
        watcher.run();
    }

    waiting  = false;
}


예를 들다

let watch1 = new Watcher();
let watch2 = new Watcher();

watch1.update();
watch1.update();
watch2.update();


우리는 현재 두 개run의 대상을 수정Watcher의 데이터로 인해 시뮬레이션을 두 번datawatch1update와 한 번watch2update를 촉발했다.
만약에 대량 비동기 업데이트 전략이 없다면 이론적으로 Watcher 대상의 run을 실행해야 한다. 그러면 인쇄할 것이다.
watch1 update
watch1 ~
watch1 update
watch1 ~
watch2 update
watch2 ~


실제로 집행하다
watch1 update
watch1 update
watch2 update
watch1 ~
watch2 ~


이것이 바로 비동기 업데이트 전략의 효과이다. 같은 Watcher 대상은 이 과정에서 제거되고 다음tick에서 보기를 업데이트하여 우리의 첫 번째 예에 대한 최적화를 실현한다.
다시 한 번 첫 번째 예를 들어 number는 끊임없이 ++ 조작을 하고 그에 대응하는 Dep 중의 Watcher 대상의 update 방법을 끊임없이 촉발한다.그리고 최종적으로queue에는 동일idWatcher 대상을 선별했기 때문에queue에는 사실상 하나number에 대응하는Watcher 대상만 존재할 수 있다.다음 tick 때(이때number는 1000이 되었고) 트리거Watcher 대상의 run 방법으로 보기를 업데이트하여 보기의 number를 0에서 1000으로 직접 바꿉니다.
여기서 대량 비동기 업데이트 전략과nextTick 원리는 이미 설명했으니 다음은 Vuex 상태 관리의 작업 원리를 배워봅시다.
주: 이 코드는 을 참고합니다.

좋은 웹페이지 즐겨찾기