luy
Version:
所谓类```React```框架就是**和React用法一模一样**的框架。其实当初制造这个框架的目的是为了能更好的学习React内部结构,了解其原理而制作的玩具。但是随着框架的渐渐成长,代码越来越多,我还是决定将其发展下去. 
517 lines (443 loc) • 19.1 kB
JavaScript
//@flow
import { typeNumber, isSameVnode, mapKeyToIndex, isEventName, extend, options } from "./utils";
import { flattenChildren, Vnode as VnodeClass } from './createElement'
import { mapProp, mappingStrategy, updateProps } from './mapProps'
import { setRef } from './Refs'
import { disposeVnode } from './dispose'
import { Com } from './component'
//Top Api
export function createPortal(children, container) {
let domNode;
if (container) {
if (Array.isArray(children)) {
domNode = mountChild(children, container)
} else {
domNode = render(children, container)
}
} else {
throw new Error('请给portal一个插入的目标')
}
//用于记录Portal的事物
const CreatePortalVnode = new VnodeClass('#text', "createPortal", null, null)
CreatePortalVnode._PortalHostNode = container
return CreatePortalVnode
}
let mountIndex = 0 //全局变量
var containerMap = {}
export var currentOwner = {
cur: null
};
function instanceProps(componentVnode) {
return {
oldState: componentVnode._instance.state,
oldProps: componentVnode._instance.props,
oldContext: componentVnode._instance.context,
oldVnode: componentVnode._instance.Vnode
}
}
function mountIndexAdd() {
return mountIndex++
}
function updateText(oldTextVnode, newTextVnode, parentDomNode: Element) {
let dom: Element = oldTextVnode._hostNode
if (oldTextVnode.props !== newTextVnode.props) {
dom.nodeValue = newTextVnode.props
}
}
function updateChild(oldChild, newChild, parentDomNode: Element, parentContext) {
newChild = flattenChildren(newChild)
oldChild = oldChild || []
if (!Array.isArray(oldChild)) oldChild = [oldChild]
if (!Array.isArray(newChild)) newChild = [newChild]
let oldLength = oldChild.length,
newLength = newChild.length,
oldStartIndex = 0, newStartIndex = 0,
oldEndIndex = oldLength - 1,
newEndIndex = newLength - 1,
oldStartVnode = oldChild[0],
newStartVnode = newChild[0],
oldEndVnode = oldChild[oldEndIndex],
newEndVnode = newChild[newEndIndex],
hascode = {};
if (newLength >= 0 && !oldLength) {
newChild.forEach((newVnode, index) => {
renderByLuy(newVnode, parentDomNode, false, parentContext)
newChild[index] = newVnode
})
return newChild
}
if (!newLength && oldLength >= 0) {
oldChild.forEach((oldVnode) => {
disposeVnode(oldVnode)
})
return newChild[0]
}
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
if (oldStartVnode === undefined || oldStartVnode === null) {
oldStartVnode = oldChild[++oldStartIndex];
} else if (oldEndVnode === undefined || oldEndVnode === null) {
oldEndVnode = oldChild[--oldEndIndex];
} else if (newStartVnode === undefined || newStartVnode === null) {
newStartVnode = newChild[++newStartIndex];
} else if (newEndVnode === undefined || newEndVnode === null) {
newEndVnode = newChild[--newEndIndex];
}
else if (isSameVnode(oldStartVnode, newStartVnode)) {
update(oldStartVnode, newStartVnode, newStartVnode._hostNode, parentContext)
oldStartVnode = oldChild[++oldStartIndex]
newStartVnode = newChild[++newStartIndex]
}
else if (isSameVnode(oldEndVnode, newEndVnode)) {
update(oldEndVnode, newEndVnode, newEndVnode._hostNode, parentContext)
oldEndVnode = oldChild[--oldEndIndex]
newEndVnode = newChild[--newEndIndex]
}
else if (isSameVnode(oldStartVnode, newEndVnode)) {
let dom = oldStartVnode._hostNode
parentDomNode.insertBefore(dom, oldEndVnode.nextSibling)
update(oldStartVnode, newEndVnode, oldStartVnode._hostNode._hostNode, parentContext)
oldStartVnode = oldChild[++oldStartIndex]
newEndVnode = newChild[--newEndIndex]
} else if (isSameVnode(oldEndVnode, newStartVnode)) {
let dom = oldEndVnode._hostNode
parentDomNode.insertBefore(dom, oldStartVnode._hostNode)
update(oldStartVnode, newEndVnode, oldStartVnode._hostNode, parentContext)
oldEndVnode = oldChild[--oldEndIndex]
newStartVnode = newChild[++newStartIndex]
}
else {
if (hascode === undefined) hascode = mapKeyToIndex(oldChild)
let indexInOld = hascode[newStartVnode.key]
if (indexInOld === undefined) {
let newElm = renderByLuy(newStartVnode, parentDomNode, true, parentContext)
parentDomNode.insertBefore(newElm, oldStartVnode._hostNode)
newStartVnode = newChild[++newStartIndex]
} else {
let moveVnode = oldChild[indexInOld]
update(moveVnode, newStartVnode, moveVnode._hostNode, parentContext)
parentDomNode.insertBefore(moveVnode._hostNode, oldStartVnode._hostNode)
oldChild[indexInOld] = undefined
newStartVnode = newChild[++newStartIndex]
}
}
if (oldStartIndex > oldEndIndex) {
for (; newStartIndex - 1 < newEndIndex; newStartIndex++) {
if (newChild[newStartIndex]) {
let newDomNode = renderByLuy(newChild[newStartIndex], parentDomNode, true, parentContext)
parentDomNode.appendChild(newDomNode)
// if (oldChild[oldChild.length - 1]) {
// } else {
// parentDomNode.insertBefore(newDomNode, oldChild[oldChild.length - 1]._hostNode)
// }
newChild[newStartIndex]._hostNode = newDomNode
}
}
} else if (newStartIndex > newEndIndex) {
for (; oldStartIndex - 1 < oldEndIndex; oldStartIndex++) {
if (oldChild[oldStartIndex]) {
let removeNode = oldChild[oldStartIndex]
disposeVnode(removeNode)
}
}
}
}
return newChild
}
/**
* 当我们更新组件的时候,并不需要重新创建一个组件,而是拿到久的组件的props,state,context就可以进行重新render
* 而且要注意的是,组件的更新并不需要比对或者交换state,因为组件的更新完全依靠外部的context和props
* @param {*} oldComponentVnode 老的孩子组件,_instance里面有着这个组件的实例
* @param {*} newComponentVnode 新的组件
* @param {*} parentContext 父亲context
* @param {*} parentDomNode 父亲节点
*/
function updateComponent(oldComponentVnode, newComponentVnode, parentContext, parentDomNode) {
const {
oldState,
oldProps,
oldContext,
oldVnode
} = instanceProps(oldComponentVnode)
const newProps = newComponentVnode.props
let newContext = parentContext
const instance = oldComponentVnode._instance
// const willReceive = oldContext !== newContext || oldProps !== newProps
//如果props和context中的任意一个改变了,那么就会触发组件的receive,render,update等
//但是依旧会继续往下比较
//更新原来组件的信息
oldComponentVnode._instance.props = newProps
if (instance.getChildContext) {
oldComponentVnode._instance.context = extend(extend({}, newContext), instance.getChildContext())
} else {
oldComponentVnode._instance.context = extend({}, newContext)
}
oldComponentVnode._instance.lifeCycle = Com.UPDATING
if (oldComponentVnode._instance.componentWillReceiveProps) {
oldComponentVnode._instance.componentWillReceiveProps(newProps, newContext)
let mergedState = oldComponentVnode._instance.state
oldComponentVnode._instance._penddingState.forEach((partialState) => {
mergedState = extend(extend({}, mergedState), partialState.partialNewState)
})
oldComponentVnode._instance.state = mergedState
}
if (oldComponentVnode._instance.shouldComponentUpdate) {
let shouldUpdate = oldComponentVnode._instance.shouldComponentUpdate(newProps, oldState, newContext)
if (!shouldUpdate) {
//无论shouldComponentUpdate结果是如何,数据都会给用户设置上去
//但是不一定会刷新
oldComponentVnode._instance.props = newProps
oldComponentVnode._instance.context = newContext
return
}
}
if (oldComponentVnode._instance.componentWillUpdate) {
oldComponentVnode._instance.componentWillUpdate(newProps, oldState, newContext)
}
let lastOwner = currentOwner.cur
currentOwner.cur = oldComponentVnode._instance
let newVnode = oldComponentVnode._instance.render ? oldComponentVnode._instance.render() : new newComponentVnode.type(newProps, newContext)
newVnode = newVnode ? newVnode : new VnodeClass('#text', "", null, null)
let fixedOldVnode = oldVnode ? oldVnode : oldComponentVnode._instance
currentOwner.cur = lastOwner
const willUpdate = options.dirtyComponent[oldComponentVnode._instance._uniqueId]//因为用react-redux更新的时候
if (willUpdate) {
delete options.dirtyComponent[oldComponentVnode._instance._uniqueId]
}
//更新真实dom,保存新的节点
update(fixedOldVnode, newVnode, oldComponentVnode._hostNode, instance.context)
oldComponentVnode._hostNode = newVnode._hostNode
if (oldComponentVnode._instance.Vnode) {//更新React component的时候需要用新的完全更新旧的component,不然无法更新
oldComponentVnode._instance.Vnode = newVnode
} else {
oldComponentVnode._instance = newVnode
}
if (oldComponentVnode._instance) {
if (oldComponentVnode._instance.componentDidUpdate) {
oldComponentVnode._instance.componentDidUpdate(oldProps, oldState, oldContext)
}
oldComponentVnode._instance.lifeCycle = Com.UPDATED
}
}
export function update(oldVnode, newVnode, parentDomNode: Element, parentContext) {
newVnode._hostNode = oldVnode._hostNode
if (oldVnode.type === newVnode.type) {
if (oldVnode.type === "#text") {
newVnode._hostNode = oldVnode._hostNode //更新一个dom节点
updateText(oldVnode, newVnode)
return newVnode
}
if (typeof oldVnode.type === 'string') {//原生html
updateProps(oldVnode.props, newVnode.props, newVnode._hostNode)
if (oldVnode.ref !== newVnode.ref) {
// if (typeNumber(oldVnode.ref) === 5) {
// oldVnode.ref(null)
// }
setRef(newVnode, oldVnode.owner, newVnode._hostNode)
}
//更新后的child,返回给组件
newVnode.props.children = updateChild(
oldVnode.props.children,
newVnode.props.children,
oldVnode._hostNode,
parentContext)
}
if (typeof oldVnode.type === 'function') {//非原生
if (!oldVnode._instance.render) {
const { props } = newVnode
const newStateLessInstance = new newVnode.type(props, parentContext)
update(oldVnode._instance, newStateLessInstance, parentDomNode, parentContext)
newStateLessInstance.owner = oldVnode._instance.owner
newStateLessInstance.ref = oldVnode._instance.ref
newStateLessInstance.key = oldVnode._instance.key
newVnode._instance = newStateLessInstance
return newVnode
}
updateComponent(oldVnode, newVnode, parentContext, parentDomNode)
newVnode.owner = oldVnode.owner;
newVnode.ref = oldVnode.ref;
newVnode.key = oldVnode.key;
newVnode._instance = oldVnode._instance;
newVnode._PortalHostNode = oldVnode._PortalHostNode ? oldVnode._PortalHostNode : void 666;
}
} else {
let dom = renderByLuy(newVnode, parentDomNode, true, parentContext)
const parentNode = parentDomNode.parentNode
if (newVnode._hostNode) {
parentNode.insertBefore(dom, oldVnode._hostNode)
disposeVnode(oldVnode)
} else {
parentNode.appendChild(dom)
newVnode._hostNode = dom
}
}
return newVnode
}
/**
* 渲染自定义组件
* @param {*} Vnode
* @param {Element} parentDomNode
*/
function mountComponent(Vnode, parentDomNode: Element, parentContext) {
const { type, props, key, ref } = Vnode
const Component = type
let instance = new Component(props, parentContext)
if (!instance.render) {
Vnode._instance = instance;//for react-redux
return renderByLuy(instance, parentDomNode, false, parentContext);
}
if (instance.getChildContext) {//如果用户定义getChildContext,那么用它生成子context
instance.context = extend(extend({}, instance.context), instance.getChildContext());
} else {
instance.context = extend({}, parentContext);
}
//生命周期函数
instance.componentWillMount && instance.componentWillMount();
let lastOwner = currentOwner.cur;
currentOwner.cur = instance;
let renderedVnode = instance.render();
currentOwner.cur = lastOwner;
if (renderedVnode === void 233) {
console.warn('你可能忘记在组件render()方法中返回jsx了');
return;
}
renderedVnode = renderedVnode ? renderedVnode : new VnodeClass('#text', "", null, null);
const domNode = renderByLuy(renderedVnode, parentDomNode, false, instance.context, instance);
if (instance.componentDidMount) {
instance.lifeCycle = Com.MOUNTTING;
instance.componentDidMount();
instance.componentDidMount = null;//防止用户调用
instance.lifeCycle = Com.MOUNT;
}
renderedVnode.key = key || null;
instance.Vnode = renderedVnode;
instance.Vnode._hostNode = domNode;//用于在更新时期oldVnode的时候获取_hostNode
instance.Vnode._mountIndex = mountIndexAdd();
Vnode._instance = instance; // 在父节点上的child元素会保存一个自己
Vnode._hostNode = domNode;
setRef(Vnode, instance, domNode);
if (renderedVnode._PortalHostNode) {//支持react createPortal
Vnode._PortalHostNode = renderedVnode._PortalHostNode;
renderedVnode._PortalHostNode._PortalHostNode = domNode;
}
instance._updateInLifeCycle(); // componentDidMount之后一次性更新
return domNode
}
function mountNativeElement(Vnode, parentDomNode: Element, instance) {
const domNode = renderByLuy(Vnode, parentDomNode, false, instance)
Vnode._hostNode = domNode
Vnode._mountIndex = mountIndexAdd()
return domNode
}
function mountTextComponent(Vnode, domNode: Element) {
let fixText = Vnode.props === 'createPortal' ? '' : Vnode.props
let textDomNode = document.createTextNode(fixText)
domNode.appendChild(textDomNode)
Vnode._hostNode = textDomNode
Vnode._mountIndex = mountIndexAdd()
return textDomNode
}
function mountChild(childrenVnode, parentDomNode: Element, parentContext, instance) {
let childType = typeNumber(childrenVnode)
let flattenChildList = childrenVnode;
if (childrenVnode === undefined) {
flattenChildList = flattenChildren(childrenVnode)
}
if (childType === 8 && childrenVnode !== undefined) { //Vnode
if (typeNumber(childrenVnode.type) === 5) {
flattenChildList._hostNode = renderByLuy(flattenChildList, parentDomNode, false, parentContext, instance)
} else if (typeNumber(childrenVnode.type) === 3 || typeNumber(childrenVnode.type) === 4) {
flattenChildList._hostNode = mountNativeElement(flattenChildList, parentDomNode, instance)
}
}
if (childType === 7) {//list
flattenChildList = flattenChildren(childrenVnode)
flattenChildList.forEach((item) => {
if (item) {
if (typeof item.type === 'function') { //如果是组件先不渲染子嗣
mountComponent(item, parentDomNode, parentContext)
} else {
renderByLuy(item, parentDomNode, false, parentContext, instance)
}
}
})
}
if (childType === 4 || childType === 3) {//string or number
flattenChildList = flattenChildren(childrenVnode)
mountTextComponent(flattenChildList, parentDomNode)
}
return flattenChildList
}
export function findDOMNode(ref) {
if (ref == null) {
return null;
}
if (ref.nodeType === 1) {
return ref;
}
return ref.__dom || null;
}
/**
* ReactDOM.render()函数入口
* 渲染组件,组件的子组件,都在这里
* @param {*} Vnode
* @param {Element} container
* @param {boolean} isUpdate
* @param {boolean} instance 用于实现refs机制
*/
let depth = 0
function renderByLuy(Vnode, container: Element, isUpdate: boolean, parentContext, instance) {
const {
type,
props
} = Vnode;
if (!type) return;
const { children } = props;
let domNode;
if (typeof type === 'function') {
const fixContext = parentContext || {};
domNode = mountComponent(Vnode, container, fixContext);
} else if (typeof type === 'string' && type === '#text') {
domNode = mountTextComponent(Vnode, container);
} else {
domNode = document.createElement(type);
}
if (typeof type !== 'function') {
if (typeNumber(children) > 2 && children !== undefined) {
const NewChild = mountChild(children, domNode, parentContext, instance)//flatten之后的child 要保存下来
props.children = NewChild
}
}
setRef(Vnode, instance, domNode)
mapProp(domNode, props, Vnode) //为元素添加props
Vnode._hostNode = domNode //缓存真实节点
if (isUpdate) {
return domNode
} else {
Vnode._mountIndex = mountIndexAdd()
if (container && domNode && container.nodeName !== '#text') {
container.appendChild(domNode)
}
}
return domNode
}
function areTheyEqual(aDom, bDom) {
if (aDom === bDom) return true
return false
}
export function render(Vnode, container) {
if (typeNumber(container) !== 8) {
throw new Error('Target container is not a DOM element.')
}
const UniqueKey = container.UniqueKey
if (container.UniqueKey) {//已经被渲染
const oldVnode = containerMap[UniqueKey]
const rootVnode = update(oldVnode, Vnode, container)
return Vnode._instance
} else {
//第一次渲染的时候
container.UniqueKey = Date.now()
containerMap[container.UniqueKey] = Vnode
renderByLuy(Vnode, container)
return Vnode._instance
}
}