RubyLouvre/anu

View on GitHub
packages/fiber/commitWork.js

Summary

Maintainability
D
2 days
Test Coverage
import { emptyObject, returnFalse } from 'react-core/util';
import {
    NOWORK,
    WORKING,
    PLACE,
    CONTENT,
    DUPLEX,
    ATTR, //UPDATE
    DETACH, //DELETION
    HOOK,
    REF,
    CALLBACK,
    PASSIVE,
    CAPTURE,
    effectLength,
    effectNames
} from './effectTag';
import { guardCallback, removeFormBoundaries } from './ErrorBoundary';
import { fakeObject } from 'react-core/Component';
import { Renderer } from 'react-core/createRenderer';
import { Refs } from './Refs';

/**
 * COMMIT阶段也做成深度调先遍历
 * @param {*} fiber
 * @param {*} topFiber
 */
var domFns = ['insertElement', 'updateContent', 'updateAttribute'];
var domEffects = [PLACE, CONTENT, ATTR];
var domRemoved = [];
var passiveFibers = [];
function commitDFSImpl(fiber) {
    let topFiber = fiber;
    outerLoop: while (true) {
        //逐步向下执行所有移除与插入操作
        if (fiber.effects && fiber.effects.length) {
            //fiber里面是被重用的旧节点与无法重用的旧节点
            fiber.effects.forEach(disposeFiber);
            delete fiber.effects;
        }
        if (fiber.effectTag % PLACE == 0) {
            // DOM节点插入或移除
            domEffects.forEach(function (effect, i) {
                if (fiber.effectTag % effect == 0) {
                    Renderer[domFns[i]](fiber);
                    fiber.effectTag /= effect;
                }
            });
            fiber.hasMounted = true;
        } else {
            // 边界组件的清洗工件
            if (fiber.catchError) {
                removeFormBoundaries(fiber);
                disposeFibers(fiber);
            }
        }
        if (fiber.updateFail) {
            delete fiber.updateFail;
        }
        if (fiber.child && fiber.child.effectTag > NOWORK) {
            fiber = fiber.child;
            continue;
        }

        let f = fiber;
        while (f) {
            if (f.effectTag === WORKING) {
                f.effectTag = NOWORK;
                f.hasMounted = true;//做react hooks新时新加的
            } else if (f.effectTag > WORKING) {
                commitEffects(f);
                f.hasMounted = true;
                if (f.capturedValues) {
                    f.effectTag = CAPTURE;
                }
            }

            if (f === topFiber || f.hostRoot) {
                break outerLoop;
            }
            if (f.sibling) {
                fiber = f.sibling;
                continue outerLoop;
            }
            f = f.return;
        }
    }
}
export function commitDFS(effects) {
    Renderer.batchedUpdates(function () {
        var el;
        while ((el = effects.shift())) {
            //处理retry组件
            if (el.effectTag === DETACH && el.caughtError) {
                disposeFiber(el);
            } else {
                commitDFSImpl(el);
            }
            if (passiveFibers.length) {
                passiveFibers.forEach(function (fiber) {
                    safeInvokeHooks(fiber.updateQueue, 'passive', 'unpassive');
                });
                passiveFibers.length = 0;
            }
            if (domRemoved.length) {
                domRemoved.forEach(Renderer.removeElement);
                domRemoved.length = 0;
            }
        }
    }, {});

    let error = Renderer.catchError;
    if (error) {
        delete Renderer.catchError;
        throw error;
    }
}

/**
 * 执行其他任务
 *
 * @param {Fiber} fiber
 */
export function commitEffects(fiber) {
    let instance = fiber.stateNode || emptyObject;
    let amount = fiber.effectTag;
    let updater = instance.updater || fakeObject;
    for (let i = 0; i < effectLength; i++) {
        let effectNo = effectNames[i];
        if (effectNo > amount) {
            break;
        }
        if (amount % effectNo === 0) {
            amount /= effectNo;
            //如果能整除
            switch (effectNo) {
                case WORKING:
                    break;
                case DUPLEX:
                    Renderer.updateControlled(fiber);
                    break;
                case HOOK:
                    if (instance.__isStateless) {
                        safeInvokeHooks(fiber.updateQueue, 'layout', 'unlayout');
                    } else if (fiber.hasMounted) {
                        guardCallback(instance, 'componentDidUpdate', [
                            updater.prevProps,
                            updater.prevState,
                            updater.snapshot
                        ]);
                    } else {
                        fiber.hasMounted = true;
                        guardCallback(instance, 'componentDidMount', []);
                    }
                    delete fiber._hydrating;
                    //这里发现错误,说明它的下方组件出现错误,不能延迟到下一个生命周期
                    if (fiber.catchError) {
                        fiber.effectTag = amount;
                        return;
                    }
                    break;
                case PASSIVE:
                    passiveFibers.push(fiber);
                    break;
                case REF:
                    Refs.fireRef(fiber, instance);
                    break;
                case CALLBACK:
                    //ReactDOM.render/forceUpdate/setState callback
                    var queue = fiber.pendingCbs;
                    fiber._hydrating = true; //setState回调里再执行setState
                    queue.forEach(function (fn) {
                        fn.call(instance);
                    });
                    delete fiber._hydrating;
                    delete fiber.pendingCbs;
                    break;
                case CAPTURE: // 23
                    // console.log("进入CAPTURE");
                    var values = fiber.capturedValues;
                    fiber.caughtError = true;
                    var a = values.shift();
                    var b = values.shift();
                    if (!values.length) {
                        fiber.effectTag = amount;
                        delete fiber.capturedValues;
                    }
                    instance.componentDidCatch(a, b);
                    break;
            }
        }
    }
    fiber.effectTag = NOWORK;
}

export function disposeFibers(fiber) {
    let list = [fiber.oldChildren, fiber.children];
    for (let i = 0; i < 2; i++) {
        let c = list[i];
        if (c) {
            for (let i in c) {
                let child = c[i];
                if (!child.disposed && child.hasMounted) {
                    disposeFiber(child, true);
                    disposeFibers(child);
                }
            }
        }
    }
    delete fiber.child;
    delete fiber.lastChild;
    delete fiber.oldChildren;
    fiber.children = {};
}
function safeInvokeHooks(upateQueue, create, destory) {
    var uneffects = upateQueue[destory],
        effects = upateQueue[create], fn;
    if (!uneffects){
        return;
    }
    while ((fn = uneffects.shift())) {
        try {
            fn();
        } catch (e) { /** */ }
    }
    while ((fn = effects.shift())) {
        try {
            var f = fn();
            if (typeof f === 'function') {
                uneffects.push(f);
            }
        } catch (e) { /** */ }
    }
}
function disposeFiber(fiber, force) {
    let { stateNode, effectTag } = fiber;
    if (!stateNode) {
        return;
    }
    let isStateless = stateNode.__isStateless;
    if (!isStateless && fiber.ref) {
        Refs.fireRef(fiber, null);
    }
    if (effectTag % DETACH == 0 || force === true) {
        if (fiber.tag > 3) {
            domRemoved.push(fiber);
        } else {
            Renderer.onDispose(fiber);
            if (fiber.hasMounted) {
                if (isStateless) {
                    safeInvokeHooks(fiber.updateQueue, 'layout', 'unlayout');
                    safeInvokeHooks(fiber.updateQueue, 'passive', 'unpassive');
                }
                stateNode.updater.enqueueSetState = returnFalse;
                guardCallback(stateNode, 'componentWillUnmount', []);
                delete fiber.stateNode;
            }
        }
        delete fiber.alternate;
        delete fiber.hasMounted;
        fiber.disposed = true;
    }
    fiber.effectTag = NOWORK;
}