/*
* @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;
react-tree-walker 源码阅读
最新推荐文章于 2021-11-28 22:01:59 发布