diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts new file mode 100644 index 00000000..8a8d9f62 --- /dev/null +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -0,0 +1,79 @@ +import type { VNode } from '../Types'; + +import {cacheOldCtx, isOldProvider} from '../components/context/CompatibleContext'; +import { + ClassComponent, + ContextProvider, + DomComponent, + DomPortal, + TreeRoot, + SuspenseComponent, +} from '../vnode/VNodeTags'; +import { getContextChangeCtx, setContextCtx, setNamespaceCtx } from '../ContextSaver'; +import { FlagUtils } from '../vnode/VNodeFlags'; +import { createChildrenByDiff } from '../diff/nodeDiffComparator'; +import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; +import componentRenders from './index'; + +export function captureVNode(processing: VNode): Array | VNode | null { + const component = componentRenders[processing.tag]; + + if (processing.tag !== SuspenseComponent) { + // 该vNode没有变化,不用进入capture,直接复用。 + if ( + !processing.isCreated && + processing.oldProps === processing.props && + !getContextChangeCtx() && + !processing.shouldUpdate + ) { + // 复用还需对stack进行处理 + handlerContext(processing); + + return onlyUpdateChildVNodes(processing); + } + } + + const shouldUpdate = processing.shouldUpdate; + processing.shouldUpdate = false; + return component.captureRender(processing, shouldUpdate); +} + +// 复用vNode时,也需对stack进行处理 +function handlerContext(processing: VNode) { + switch (processing.tag) { + case TreeRoot: + setNamespaceCtx(processing, processing.outerDom); + break; + case DomComponent: + setNamespaceCtx(processing); + break; + case ClassComponent: { + const isOldCxtExist = isOldProvider(processing.type); + cacheOldCtx(processing, isOldCxtExist); + break; + } + case DomPortal: + setNamespaceCtx(processing, processing.outerDom); + break; + case ContextProvider: { + const newValue = processing.props.value; + setContextCtx(processing, newValue); + break; + } + } +} + +// 创建孩子节点 +export function createVNodeChildren(processing: VNode, nextChildren: any) { + const currentChildren = !processing.isCreated ? (processing.children ?? []) : []; + const isComparing = !processing.isCreated; + + return createChildrenByDiff(processing, currentChildren, nextChildren, isComparing); +} + +export function markRef(processing: VNode) { + const ref = processing.ref; + if ((processing.isCreated && ref !== null) || (!processing.isCreated && processing.oldRef !== ref)) { + FlagUtils.markRef(processing); + } +} diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts new file mode 100644 index 00000000..6236bffe --- /dev/null +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -0,0 +1,202 @@ +import type { VNode } from '../Types'; + +import { mergeDefaultProps } from './LazyComponent'; +import { getNewContext, resetDepContexts } from '../components/context/Context'; +import { + cacheOldCtx, + getOldContext, + isOldProvider, + resetOldCtx, + updateOldContext, +} from '../components/context/CompatibleContext'; +import { + callComponentWillMount, + callComponentWillReceiveProps, + callComponentWillUpdate, + callConstructor, + callDerivedStateFromProps, + callShouldComponentUpdate, + markComponentDidMount, + markComponentDidUpdate, + markGetSnapshotBeforeUpdate, +} from './class/ClassLifeCycleProcessor'; +import {FlagUtils} from '../vnode/VNodeFlags'; +import { createVNodeChildren, markRef } from './BaseComponent'; +import { + createUpdateArray, + processUpdates, +} from '../UpdateHandler'; +import { getContextChangeCtx, setContextChangeCtx } from '../ContextSaver'; +import ProcessingVNode from '../vnode/ProcessingVNode'; +import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; + +export function captureRender(processing: VNode): Array | null { + const clazz = processing.type; + const props = processing.props; + const nextProps = processing.isLazyComponent ? mergeDefaultProps(clazz, props) : props; + return captureClassComponent(processing, clazz, nextProps); +} + +export function bubbleRender(processing: VNode) { + if (isOldProvider(processing.type)) { + resetOldCtx(processing); + } +} + +// 用于未完成的类组件 +export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): Array | null { + mountInstance(clazz, processing, nextProps); + return createChildren(clazz, processing); +} + +// 用于类组件 +export function captureClassComponent(processing: VNode, clazz: any, nextProps: object): Array | null { + const isOldCxtExist = isOldProvider(clazz); + cacheOldCtx(processing, isOldCxtExist); + + resetDepContexts(processing); + + // 通过 shouldUpdate 判断是否要复用 children,该值和props,state,context的变化,shouldComponentUpdate,forceUpdate api的调用结果有关 + let shouldUpdate; + const inst = processing.realNode; + if (inst === null) { + // 挂载新组件,一定会更新 + mountInstance(clazz, processing, nextProps); + shouldUpdate = true; + } else { // 更新 + const newContext = getCurrentContext(clazz, processing); + + // 子节点抛出异常时,如果本class是个捕获异常的处理节点,这时候oldProps是null,所以需要使用props + let oldProps = processing.flags.DidCapture ? processing.props : processing.oldProps; + if (processing.isLazyComponent) { + oldProps = mergeDefaultProps(processing.type, oldProps); + } + inst.props = oldProps; + + if (oldProps !== processing.props || inst.context !== newContext) { + // 在已挂载的组件接收新的 props 之前被调用 + callComponentWillReceiveProps(inst, nextProps, newContext); + } + + inst.state = processing.state; + processUpdates(processing, inst, nextProps); + + // 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新 + shouldUpdate = oldProps !== processing.props || + inst.state !== processing.state || + getContextChangeCtx() || + processing.isForceUpdate; + + if (shouldUpdate) { + // derivedStateFromProps会修改nextState,因此需要调用 + callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); + if (!processing.isForceUpdate) { + // 业务可以通过 shouldComponentUpdate 函数进行优化阻止更新 + shouldUpdate = callShouldComponentUpdate(processing, oldProps, nextProps, processing.state, newContext); + } + if (shouldUpdate) { + callUpdateLifeCycle(processing, nextProps, clazz); + } + inst.state = processing.state; + inst.context = newContext; + } + + markLifeCycle(processing, nextProps, shouldUpdate); + // 不管有没有更新,props都必须更新 + inst.props = nextProps; + } + // 如果捕获了 error,必须更新 + const isCatchError = processing.flags.DidCapture; + shouldUpdate = isCatchError || shouldUpdate; + + // 更新ref + markRef(processing); + + // 不复用 + if (shouldUpdate) { + // 更新context + if (isOldCxtExist) { + updateOldContext(processing); + } + return createChildren(clazz, processing); + } else { + if (isOldCxtExist) { + setContextChangeCtx(processing, false); + } + return onlyUpdateChildVNodes(processing); + } +} + +// 挂载实例 +function mountInstance(clazz, processing: VNode, nextProps: object) { + if (!processing.isCreated) { + processing.isCreated = true; + FlagUtils.markAddition(processing); + } + + // 构造实例 + callConstructor(processing, clazz, nextProps); + + const inst = processing.realNode; + inst.props = nextProps; + inst.state = processing.state; + inst.context = getCurrentContext(clazz, processing); + inst.refs = {}; + + createUpdateArray(processing); + processUpdates(processing, inst, nextProps); + inst.state = processing.state; + + // 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用 + callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); + callComponentWillMount(processing, inst, nextProps); + + markComponentDidMount(processing); +} + +// 构建子节点 +function createChildren(clazz: any, processing: VNode) { + markRef(processing); + + ProcessingVNode.val = processing; + processing.state = processing.realNode.state; + + const inst = processing.realNode; + const isCatchError = processing.flags.DidCapture; + + // 按照已有规格,如果捕获了错误却没有定义getDerivedStateFromError函数,返回的child为null + const newElements = (isCatchError && typeof clazz.getDerivedStateFromError !== 'function') + ? null + : inst.render(); + + processing.children = createVNodeChildren(processing, newElements); + return processing.children; +} + +// 获取当前节点的context +export function getCurrentContext(clazz, processing: VNode) { + const context = clazz.contextType; + return typeof context === 'object' && context !== null + ? getNewContext(processing, context) + : getOldContext(processing, clazz, true); +} + +// 根据isUpdateComponent,执行不同的生命周期 +function callUpdateLifeCycle(processing: VNode, nextProps: object, clazz) { + const inst = processing.realNode; + const newContext = getCurrentContext(clazz, processing); + if (processing.isCreated) { + callComponentWillMount(processing, inst); + } else { + callComponentWillUpdate(inst, nextProps, processing.state, newContext); + } +} + +function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boolean) { + if (processing.isCreated) { + markComponentDidMount(processing); + } else if (processing.state !== processing.oldState || shouldUpdate) { + markComponentDidUpdate(processing); + markGetSnapshotBeforeUpdate(processing); + } +} diff --git a/libs/horizon/src/renderer/render/ClsOrFunComponent.ts b/libs/horizon/src/renderer/render/ClsOrFunComponent.ts new file mode 100644 index 00000000..7ded07fc --- /dev/null +++ b/libs/horizon/src/renderer/render/ClsOrFunComponent.ts @@ -0,0 +1,36 @@ +import type {VNode} from '../Types'; + +import {FunctionComponent} from '../vnode/VNodeTags'; +import {resetDepContexts} from '../components/context/Context'; +import {getOldContext} from '../components/context/CompatibleContext'; +import {FlagUtils} from '../vnode/VNodeFlags'; +import {exeFunctionHook} from '../hooks/HookMain'; +import {createVNodeChildren} from './BaseComponent'; + +export function captureRender(processing: VNode): Array | null { + return captureIndeterminateComponent(processing); +} + +export function bubbleRender() {} + +function captureIndeterminateComponent( + processing: VNode | null, +): Array | null { + const funcComp = processing.type; + + if (!processing.isCreated) { + processing.isCreated = true; + FlagUtils.markAddition(processing); + } + + const props = processing.props; + const context = getOldContext(processing, funcComp, false); + + resetDepContexts(processing); + + const newElements = exeFunctionHook(funcComp, props, context, processing); + + processing.tag = FunctionComponent; + processing.children = createVNodeChildren(processing, newElements); + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/ContextConsumer.ts b/libs/horizon/src/renderer/render/ContextConsumer.ts new file mode 100644 index 00000000..fb99f8ab --- /dev/null +++ b/libs/horizon/src/renderer/render/ContextConsumer.ts @@ -0,0 +1,23 @@ +import type {VNode, ContextType} from '../Types'; + +import {resetDepContexts, getNewContext} from '../components/context/Context'; +import {createVNodeChildren} from './BaseComponent'; + +export function captureRender(processing: VNode): Array | null { + return captureContextConsumer(processing); +} + +export function bubbleRender() {} + +function captureContextConsumer(processing: VNode) { + const context: ContextType = processing.type; + const props = processing.props; + const renderFunc = props.children; + + resetDepContexts(processing); + const contextVal = getNewContext(processing, context); + const newChildren = renderFunc(contextVal); + + processing.children = createVNodeChildren(processing, newChildren); + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts new file mode 100644 index 00000000..9f2f2e88 --- /dev/null +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -0,0 +1,105 @@ +import type {VNode, ContextType, ProviderType} from '../Types'; + +import {createVNodeChildren} from './BaseComponent'; +import {isSame} from '../utils/compare'; +import {ClassComponent, ContextProvider} from '../vnode/VNodeTags'; +import {pushForceUpdate} from '../UpdateHandler'; +import { + getContextChangeCtx, + resetContextCtx, + setContextCtx, +} from '../ContextSaver'; +import {getFirstChild, travelVNodeTree} from '../vnode/VNodeUtils'; +import {launchUpdateFromVNode} from '../TreeBuilder'; +import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; +import {setParentsChildShouldUpdate} from '../vnode/VNodeShouldUpdate'; + +export function captureRender(processing: VNode): Array | null { + return captureContextProvider(processing); +} + +export function bubbleRender(processing: VNode) { + resetContextCtx(processing); +} + +function captureContextProvider(processing: VNode): Array | null { + const providerType: ProviderType = processing.type; + const contextType: ContextType = providerType._context; + + const newProps = processing.props; + const oldProps = !processing.isCreated ? processing.oldProps : null; + + // 获取provider设置的context,即provider组件设置的value + const newCtx = newProps.value; + + // 更新processing的context值为newProps.value + setContextCtx(processing, newCtx); + + if (oldProps !== null) { + const oldCtx = oldProps.value; + const isSameContext = isSame(oldCtx, newCtx); + if (isSameContext) { + // context没有改变,复用 + if (oldProps.children === newProps.children && !getContextChangeCtx()) { + return onlyUpdateChildVNodes(processing); + } + } else { + // context更改,更新所有依赖的组件 + handleContextChange(processing, contextType); + } + } + + const newElements = newProps.children; + processing.children = createVNodeChildren(processing, newElements); + return processing.children; +} + +// 从依赖中找到匹配context的VNode +function matchDependencies(depContexts, context, vNode): boolean { + for (let i = 0; i < depContexts.length; i++) { + const contextItem = depContexts[i]; + if (contextItem === context) { + // 匹配到了更新的context,需要创建update。 + if (vNode.tag === ClassComponent) { + pushForceUpdate(vNode); + } + + vNode.shouldUpdate = true; + + // 找到需要更新的节点,所以祖先节点都需要改为shouldUpdate为true + setParentsChildShouldUpdate(vNode.parent); + + vNode.isDepContextChange = true; + // 由于我们已经找到匹配项,我们可以停止遍历依赖项列表。 + return true; + } + } + + return false; +} + +// 从当前子节点开始向下遍历,找到消费此context的组件,并更新 +function handleContextChange(processing: VNode, context: ContextType): void { + const vNode = getFirstChild(processing); + if (vNode === null) { + return; + } + + let isMatch = false; + + // 从vNode开始遍历 + travelVNodeTree(vNode, (node) => { + const depContexts = node.depContexts; + if (depContexts.length) { + isMatch = matchDependencies(depContexts, context, node) ?? isMatch; + } + }, (node) => { + // 如果这是匹配的provider,则不要更深入地扫描 + return node.tag === ContextProvider && node.type === processing.type; + }, processing); + + // 找到了依赖context的子节点,触发一次更新 + if (isMatch) { + launchUpdateFromVNode(processing); + } +} diff --git a/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts b/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts new file mode 100644 index 00000000..d6e21c2c --- /dev/null +++ b/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts @@ -0,0 +1,153 @@ +import type { VNode } from '../../Types'; +import type { Callback } from '../../UpdateHandler'; + +import {shallowCompare} from '../../utils/compare'; +import { + pushUpdate, + newUpdate, + UpdateState, + processUpdates, +} from '../../UpdateHandler'; +import { launchUpdateFromVNode } from '../../TreeBuilder'; +import { FlagUtils } from '../../vnode/VNodeFlags'; +import { getCurrentContext } from '../ClassComponent'; +import { PureComponent } from '../../components/BaseClassComponent'; + +export function callDerivedStateFromProps( + processing: VNode, + getDerivedStateFromProps: (props: object, state: object) => object, + nextProps: object, +) { + if (typeof getDerivedStateFromProps === 'function') { + const oldState = processing.state; + + // 调用class组件的getDerivedStateFromProps函数 + const newState = getDerivedStateFromProps(nextProps, oldState); + + // 组件未返回state,需要返回旧的preState + processing.state = newState === null || newState === undefined + ? oldState + : { ...oldState, ...newState }; + } +} + +function changeStateContent(type: UpdateState, content: object, callback: Callback) { + // @ts-ignore + const vNode = this._vNode; + + const update = newUpdate(); + update.type = type; + if (type === UpdateState.Update || type === UpdateState.Override) { + update.content = content; + } + if (callback !== undefined && callback !== null) { + update.callback = callback; + } + + pushUpdate(vNode, update); + launchUpdateFromVNode(vNode); +} + +export function callShouldComponentUpdate( + processing: VNode, + oldProps: object, + newProps: object, + newState: object, + newContext: object, +) { + const inst = processing.realNode; + + if (typeof inst.shouldComponentUpdate === 'function') { + return inst.shouldComponentUpdate(newProps, newState, newContext); + } + + if (inst instanceof PureComponent) { + return !shallowCompare(oldProps, newProps) || !shallowCompare(inst.state, newState); + } + + return true; +} + +function setStateAndForceUpdateImpl(inst): void { + inst.setState = changeStateContent.bind(inst, UpdateState.Update); + inst.forceUpdate = changeStateContent.bind(inst, UpdateState.ForceUpdate, null); +} + +export function callConstructor(processing: VNode, ctor: any, props: any): any { + const context = getCurrentContext(ctor, processing); + const inst = new ctor(props, context); + if (inst.state !== null && inst.state !== undefined) { + processing.state = inst.state; + } + + setStateAndForceUpdateImpl(inst); + // 双向绑定processing和inst + processing.realNode = inst; + inst._vNode = processing; + + return inst; +} + +export function callComponentWillMount(processing, inst, newProps?) { + const oldState = inst.state; + + if (typeof inst.componentWillMount === 'function') { + inst.componentWillMount(); + } + if (typeof inst.UNSAFE_componentWillMount === 'function') { + inst.UNSAFE_componentWillMount(); + } + + if (oldState !== inst.state) { + changeStateContent.call(inst, UpdateState.Override, inst.state, null); + } + + // 处理componentWillMount中可能存在的state更新行为 + processUpdates(processing, inst, newProps); + inst.state = processing.state; +} + +export function callComponentWillUpdate(inst, newProps, newState, nextContext) { + if (typeof inst.componentWillUpdate === 'function') { + inst.componentWillUpdate(newProps, newState, nextContext); + } + + if (typeof inst.UNSAFE_componentWillUpdate === 'function') { + inst.UNSAFE_componentWillUpdate(newProps, newState, nextContext); + } +} + +export function callComponentWillReceiveProps(inst, newProps: object, newContext: object) { + const oldState = inst.state; + if (typeof inst.componentWillReceiveProps === 'function') { + inst.componentWillReceiveProps(newProps, newContext); + } + if (typeof inst.UNSAFE_componentWillReceiveProps === 'function') { + inst.UNSAFE_componentWillReceiveProps(newProps, newContext); + } + if (inst.state !== oldState) { + changeStateContent.call(inst, UpdateState.Override, inst.state, null); + } +} + +export function markComponentDidMount(processing: VNode) { + const inst = processing.realNode; + if (typeof inst.componentDidMount === 'function') { + FlagUtils.markUpdate(processing); + } +} + +export function markGetSnapshotBeforeUpdate(processing: VNode) { + const inst = processing.realNode; + if (typeof inst.getSnapshotBeforeUpdate === 'function') { + FlagUtils.markSnapshot(processing); + } +} + +export function markComponentDidUpdate(processing: VNode) { + const inst = processing.realNode; + if (typeof inst.componentDidUpdate === 'function') { + FlagUtils.markUpdate(processing); + } +} +