react-tree-walker 源码阅读


/*
 * @Description: In User Settings Edit
 * @Author: your name
 * @Date: 2019-08-18 13:12:37
 * @LastEditTime: 2019-08-18 23:19:58
 * @LastEditors: Please set LastEditors
 */
var defaultOptions = {
    componentWillUnmount: false
};
class ReactWalker {
    constructor() {
        this.forwardRefSymbol = Symbol.for('react.forward_ref');

    }

    pReduce(iterable, reducer, initVal) {
        return new Promise(function (resolve, reject) {
            var iterator = iterable[Symbol.iterator]();
            var i = 0;

            var next = function next(total) {
                var el = iterator.next();

                if (el.done) {
                    resolve(total);
                    return;
                }

                Promise.all([total, el.value]).then(function (value) {
                    // eslint-disable-next-line no-plusplus
                    next(reducer(value[0], value[1], i++));
                }).catch(reject);
            };

            next(initVal);
        });
    }; // Lifted from https://github.com/sindresorhus/p-map-series
    // Thanks @sindresorhus! ?


    pMapSeries(iterable, iterator) {
        let { pReduce } = this;
        var ret = [];
        return pReduce(iterable, function (a, b, i) {
            return Promise.resolve(iterator(b, i)).then(function (val) {
                ret.push(val);
            });
        }).then(function () {
            return ret;
        });
    };

    ensureChild=(child)=> {
        let { ensureChild } = this;
        return child && typeof child.render === 'function' ? this.ensureChild(child.render()) : child;
    }; // Preact puts children directly on element, and React via props

    isClassComponent(Comp) {
        return Comp.prototype && (Comp.prototype.render || Comp.prototype.isReactComponent || Comp.prototype.isPureReactComponent);
    };


    getChildren(element) {
        return element.props && element.props.children ? element.props.children : element.children ? element.children : undefined;
    };

    getType(element) {
        if (element) {
            return element.type || element.nodeType;
        }

        return undefined;
    }

    getProps(element) {
        return element.props || element.attributes;
    };

    isReactElement = (element) => {
        let { getType } = this;
        return !!getType(element);
    }

    providesChildContext(instance) {
        return !!instance.getChildContext;
    };

    isForwardRef = (Comp) => {
        return Comp.type && Comp.type.$$typeof === this.forwardRefSymbol;
    };


    /**
     * 获取Component 
     * 获取props
     * 
     * @param {*} tree 
     * @param {*} cb 
     * @param {*} ctx 
     */
    apply(tree, visitor, context) {
        let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : defaultOptions;
        let { getType, getProps, providesChildContext, ensureChild, getChildren, isReactElement, pMapSeries, isForwardRef, isClassComponent } = this;
        getProps = getProps.bind(this)
        // let Componet = getType(tree);
        // let props = getProps(Componet, tree);
        // let instance = new Componet(props, ctx);
        return new Promise((resolve, reject) => {

            //用于处理外部传入的回调函数
            var safeVisitor = function safeVisitor() {
                
                3 {
                    return visitor.apply(undefined, arguments);
                } catch (err) {
                    reject(err);
                }

                return undefined;
            };

            //迭代函数用于遍历react-dom树
            var recursive = function recursive(currentElement, currentContext) {
                //判断目标元素是否是数组形式
                if (Array.isArray(currentElement)) {
                    return Promise.all(currentElement.map(function (item) {
                        return recursive(item, currentContext);
                    }));
                }

                //如果是目标元素是null则马上返回resolve
                if (!currentElement) {
                    return Promise.resolve();
                }

                //当currentElement是 string或者number类型时立即处理并返回
                if (typeof currentElement === 'string' || typeof currentElement === 'number') {
                    // Just visit these, they are leaves so we don't keep traversing.
                    safeVisitor(currentElement, null, currentContext);
                    return Promise.resolve();
                }

                //如果目标是上下文生产者或者消费者
                if (currentElement.type) {
                    if (currentElement.type._context) {
                        // eslint-disable-next-line no-param-reassign
                        currentElement.type._context._currentValue = currentElement.props.value;
                    }

                    if (currentElement.type.Provider && currentElement.type.Consumer) {
                        //当对象是provider或者consumer时children属性就是一个方法用于产生被包裹的element
                        var el = currentElement.props.children(currentElement.type.Provider._context._currentValue);
                        return recursive(el, currentContext);
                    }
                }

                //如果目标是react节点
                if (isReactElement(currentElement)) {
                    return new Promise(function (innerResolve) {

                        //用于遍历react节点
                        var visitCurrentElement = function visitCurrentElement(render, compInstance, elContext, childContext) {
                            return Promise.resolve(safeVisitor(currentElement, compInstance, elContext, childContext)).then(function (result) {
                                if (result !== false) {
                                    // A false wasn't returned so we will attempt to visit the children
                                    // for the current element.
                                    var tempChildren = render();
                                    var children = ensureChild(tempChildren);

                                    if (children) {
                                        if (Array.isArray(children)) {
                                            // If its a react Children collection we need to breadth-first
                                            // traverse each of them, and pMapSeries allows us to do a
                                            // depth-first traversal that respects Promises. Thanks @sindresorhus!
                                            return pMapSeries(children, function (child) {
                                                return child ? recursive(child, childContext) : Promise.resolve();
                                            }).then(innerResolve, reject).catch(reject);
                                        } // Otherwise we pass the individual child to the next recursion.


                                        return recursive(children, childContext).then(innerResolve, reject).catch(reject);
                                    }
                                }

                                return undefined;
                            }).catch(reject);
                        };

                        //如果节点对象type是function
                        if (typeof getType(currentElement) === 'function' || isForwardRef(currentElement)) {
                            var Component = getType(currentElement);
                            var props = Object.assign({}, Component.defaultProps, getProps(currentElement), // For Preact support so that the props get passed into render
                                // function.
                                {
                                    children: getChildren(currentElement)
                                });

                            //TODO:ForwardRef是什么?
                            if (isForwardRef(currentElement)) {
                                visitCurrentElement(function () {
                                    return currentElement.type.render(props);
                                }, null, currentContext, currentContext).then(innerResolve);
                            } else if (isClassComponent(Component)) {
                                // Class component
                                var instance = new Component(props, currentContext); // In case the user doesn't pass these to super in the constructor

                                Object.defineProperty(instance, 'props', {
                                    value: instance.props || props
                                });
                                instance.context = instance.context || currentContext; // set the instance state to null (not undefined) if not set, to match React behaviour

                                instance.state = instance.state || null; // Make the setState synchronous.

                                instance.setState = function (newState) {
                                    if (typeof newState === 'function') {
                                        // eslint-disable-next-line no-param-reassign
                                        newState = newState(instance.state, instance.props, instance.context);
                                    }

                                    instance.state = Object.assign({}, instance.state, newState);
                                };

                                if (Component.getDerivedStateFromProps) {
                                    var result = Component.getDerivedStateFromProps(instance.props, instance.state);

                                    if (result !== null) {
                                        instance.state = Object.assign({}, instance.state, result);
                                    }
                                } else if (instance.UNSAFE_componentWillMount) {
                                    instance.UNSAFE_componentWillMount();
                                } else if (instance.componentWillMount) {
                                    instance.componentWillMount();
                                }

                                var childContext = providesChildContext(instance) ? Object.assign({}, currentContext, instance.getChildContext()) : currentContext;
                                visitCurrentElement(  // Note: preact API also allows props and state to be referenced
                                    // as arguments to the render func, so we pass them through
                                    // here
                                    function () {
                                        return instance.render(instance.props, instance.state);
                                    }, instance, currentContext, childContext).then(function () {
                                        if (options.componentWillUnmount && instance.componentWillUnmount) {
                                            instance.componentWillUnmount();
                                        }
                                    }).then(innerResolve);
                            } else {
                                // Stateless Functional Component
                                visitCurrentElement(function () {
                                    return Component(props, currentContext);
                                }, null, currentContext, currentContext).then(innerResolve);
                            }
                        } else {
                            // A basic element, such as a dom node, string, number etc.
                            visitCurrentElement(function () {
                                return getChildren(currentElement);
                            }, null, currentContext, currentContext).then(innerResolve);
                        }
                    });
                } // Portals
                if (currentElement.containerInfo && currentElement.children && currentElement.children.props && Array.isArray(currentElement.children.props.children)) {
                    return Promise.all(currentElement.children.props.children.map(function (child) {
                        return recursive(child, currentContext);
                    }));
                }

                return Promise.resolve();
            }

            recursive(tree, context).then(resolve, reject);
        });



    }


}

export default ReactWalker;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值