Match-id-a7737a9b08d4a26858a5dd9abe72a04f686b0b36
This commit is contained in:
commit
37df3e728e
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Component的api setState和forceUpdate在实例生成阶段实现
|
||||||
|
*/
|
||||||
|
class Component {
|
||||||
|
props;
|
||||||
|
context;
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
this.props = props;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容三方件 react-lifecycles-compat,它会读取 isReactComponent 属性值,不添加会导致 eview-ui 官网白屏
|
||||||
|
Component.prototype.isReactComponent = true;
|
||||||
|
/**
|
||||||
|
* 支持PureComponent
|
||||||
|
*/
|
||||||
|
class PureComponent extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Component, PureComponent };
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {TYPE_PORTAL} from '../utils/elementType';
|
||||||
|
import type {PortalType} from '../Types';
|
||||||
|
|
||||||
|
export function createPortal(
|
||||||
|
children: any,
|
||||||
|
outerDom: any,
|
||||||
|
key: string = null,
|
||||||
|
): PortalType {
|
||||||
|
return {
|
||||||
|
vtype: TYPE_PORTAL,
|
||||||
|
key: key == null ? null : '' + key,
|
||||||
|
children,
|
||||||
|
outerDom,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type {RefType} from '../Types';
|
||||||
|
|
||||||
|
export function createRef(): RefType {
|
||||||
|
return {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import {TYPE_FORWARD_REF} from '../utils/elementType';
|
||||||
|
|
||||||
|
export function forwardRef(render: Function) {
|
||||||
|
return {
|
||||||
|
vtype: TYPE_FORWARD_REF,
|
||||||
|
render,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import type {PromiseType} from '../Types';
|
||||||
|
|
||||||
|
import {TYPE_LAZY} from '../utils/elementType';
|
||||||
|
|
||||||
|
enum LayStatus {
|
||||||
|
UnProcessed = 'UnProcessed',
|
||||||
|
Pending = 'Pending',
|
||||||
|
Fulfilled = 'Fulfilled',
|
||||||
|
Rejected = 'Rejected',
|
||||||
|
}
|
||||||
|
|
||||||
|
type LazyContent<T> = {
|
||||||
|
_status: string,
|
||||||
|
_value: () => PromiseType<{default: T}> | PromiseType<T> | T | any
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LazyComponent<T, P> = {
|
||||||
|
vtype: Symbol | number,
|
||||||
|
_content: P,
|
||||||
|
_load: (content: P) => T,
|
||||||
|
};
|
||||||
|
|
||||||
|
// lazyContent随着阶段改变,_value改变:
|
||||||
|
// 1. 未初始化 -> promiseCtor: () => promise
|
||||||
|
// 2. pending -> promise
|
||||||
|
// 3. fulfilled -> module
|
||||||
|
// 4. rejected -> error
|
||||||
|
function lazyLoader<T>(lazyContent: LazyContent<T>): any {
|
||||||
|
if (lazyContent._status === LayStatus.UnProcessed) {
|
||||||
|
// 执行动态导入组件import
|
||||||
|
const promise = lazyContent._value();
|
||||||
|
lazyContent._status = LayStatus.Pending;
|
||||||
|
lazyContent._value = promise;
|
||||||
|
promise.then(
|
||||||
|
module => {
|
||||||
|
if (lazyContent._status === LayStatus.Pending) {
|
||||||
|
const defaultExport = module.default;
|
||||||
|
lazyContent._status = LayStatus.Fulfilled;
|
||||||
|
lazyContent._value = defaultExport;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (lazyContent._status === LayStatus.Pending) {
|
||||||
|
lazyContent._status = LayStatus.Rejected;
|
||||||
|
lazyContent._value = error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (lazyContent._status === LayStatus.Fulfilled) {
|
||||||
|
return lazyContent._value;
|
||||||
|
} else {
|
||||||
|
throw lazyContent._value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lazy<T>(promiseCtor: () => PromiseType<{default: T}>): LazyComponent<T, LazyContent<T>> {
|
||||||
|
return {
|
||||||
|
vtype: TYPE_LAZY,
|
||||||
|
_content: {
|
||||||
|
_status: LayStatus.UnProcessed,
|
||||||
|
_value: promiseCtor,
|
||||||
|
},
|
||||||
|
_load: lazyLoader,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {TYPE_MEMO} from '../utils/elementType';
|
||||||
|
|
||||||
|
export function memo<Props>(type, compare?: (oldProps: Props, newProps: Props) => boolean) {
|
||||||
|
return {
|
||||||
|
vtype: TYPE_MEMO,
|
||||||
|
type: type,
|
||||||
|
compare: compare === undefined ? null : compare,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import type {VNode} from '../../Types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
setOldContextCtx,
|
||||||
|
setContextChangeCtx,
|
||||||
|
getOldContextCtx,
|
||||||
|
resetOldContextCtx,
|
||||||
|
resetContextChangeCtx,
|
||||||
|
setOldPreviousContextCtx,
|
||||||
|
getOldPreviousContextCtx,
|
||||||
|
setVNodeOldContext,
|
||||||
|
getVNodeOldContext,
|
||||||
|
setVNodeOldPreviousContext,
|
||||||
|
getVNodeOldPreviousContext,
|
||||||
|
} from '../../ContextSaver';
|
||||||
|
|
||||||
|
const emptyObject = {};
|
||||||
|
|
||||||
|
// 判断是否是过时的context的提供者
|
||||||
|
export function isOldProvider(comp: Function): boolean {
|
||||||
|
// @ts-ignore
|
||||||
|
const childContextTypes = comp.childContextTypes;
|
||||||
|
return childContextTypes !== null && childContextTypes !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是过时的context的消费者
|
||||||
|
export function isOldConsumer(comp: Function): boolean {
|
||||||
|
// @ts-ignore
|
||||||
|
const contextTypes = comp.contextTypes;
|
||||||
|
return contextTypes !== null && contextTypes !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是旧版context提供者,则缓存两个全局变量,上一个提供者提供的context和当前提供者提供的context
|
||||||
|
export function cacheOldCtx(processing: VNode, hasOldContext: any): void {
|
||||||
|
// 每一个context提供者都会更新ctxOldContext
|
||||||
|
if (hasOldContext) {
|
||||||
|
setOldPreviousContextCtx(getOldContextCtx());
|
||||||
|
|
||||||
|
const vNodeContext = getVNodeOldContext(processing) || emptyObject;
|
||||||
|
setOldContextCtx(processing, vNodeContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前组件可以消费的context
|
||||||
|
export function getOldContext(processing: VNode, clazz: Function, ifProvider: boolean) {
|
||||||
|
const type = processing.type;
|
||||||
|
// 不是context消费者, 则直接返回空对象
|
||||||
|
if (!isOldConsumer(type)) {
|
||||||
|
return emptyObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当组件既是提供者,也是消费者时,取上一个context,不能直接取最新context,因为已经被更新为当前组件的context;
|
||||||
|
// 当组件只是消费者时,则取最新context
|
||||||
|
const parentContext = ((ifProvider && isOldProvider(clazz))) ?
|
||||||
|
getOldPreviousContextCtx() :
|
||||||
|
getOldContextCtx();
|
||||||
|
|
||||||
|
// 除非父级context更改,否则不需要重新创建子context,直接取对应节点上存的。
|
||||||
|
if (getVNodeOldPreviousContext(processing) === parentContext) {
|
||||||
|
return getVNodeOldContext(processing);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从父的context中取出子定义的context
|
||||||
|
const context = {};
|
||||||
|
for (const key in type.contextTypes) {
|
||||||
|
context[key] = parentContext[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存当前组件的context,最近祖先传递下来context,当前可消费的context
|
||||||
|
setVNodeOldPreviousContext(processing, parentContext);
|
||||||
|
setVNodeOldContext(processing, context);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置context
|
||||||
|
export function resetOldCtx(vNode: VNode): void {
|
||||||
|
resetOldContextCtx(vNode);
|
||||||
|
resetContextChangeCtx(vNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前组件是提供者,则需要合并祖先context和当前组件提供的context
|
||||||
|
function handleContext(vNode: VNode, parentContext: Object): Object {
|
||||||
|
const instance = vNode.realNode;
|
||||||
|
|
||||||
|
if (typeof instance.getChildContext !== 'function') {
|
||||||
|
return parentContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并祖先提供的context和当前组件提供的context
|
||||||
|
return {...parentContext, ...instance.getChildContext()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前组件是context提供者,更新时,需要合并祖先context和当前组件提供的context
|
||||||
|
export function updateOldContext(vNode: VNode): void {
|
||||||
|
const ctx = handleContext(vNode, getOldPreviousContextCtx());
|
||||||
|
// 更新context,给子组件用的context
|
||||||
|
setOldContextCtx(vNode, ctx);
|
||||||
|
// 标记更改
|
||||||
|
setContextChangeCtx(vNode, true);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import type {VNode, ContextType} from '../../Types';
|
||||||
|
import {getHookStage} from '../../hooks/HookStage';
|
||||||
|
import {throwNotInFuncError} from '../../hooks/BaseHook';
|
||||||
|
|
||||||
|
// 重置依赖
|
||||||
|
export function resetDepContexts(vNode: VNode): void {
|
||||||
|
vNode.depContexts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集依赖
|
||||||
|
function collectDeps<T>(vNode: VNode, context: ContextType<T>) {
|
||||||
|
const depContexts = vNode.depContexts;
|
||||||
|
if (!depContexts.length) {
|
||||||
|
vNode.isDepContextChange = false;
|
||||||
|
}
|
||||||
|
if (!depContexts.includes(context)) {
|
||||||
|
depContexts.push(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewContext<T>(vNode: VNode, ctx: ContextType<T>, isUseContext: boolean = false): T {
|
||||||
|
// 如果来自于useContext,则需要在函数组件中调用
|
||||||
|
if (isUseContext && getHookStage() === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用到这个方法,说明当前vNode依赖了这个context,所以需要收集起来
|
||||||
|
collectDeps(vNode, ctx);
|
||||||
|
|
||||||
|
return ctx.value;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type {ContextType} from '../../Types';
|
||||||
|
import {TYPE_PROVIDER, TYPE_CONTEXT} from '../../utils/elementType';
|
||||||
|
|
||||||
|
export function createContext<T>(val: T): ContextType<T> {
|
||||||
|
const context: ContextType<T> = {
|
||||||
|
vtype: TYPE_CONTEXT,
|
||||||
|
value: val,
|
||||||
|
Provider: null,
|
||||||
|
Consumer: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Provider = {
|
||||||
|
vtype: TYPE_PROVIDER,
|
||||||
|
_context: context,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Consumer = context;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import type {VNode} from '../Types';
|
||||||
|
import type {Hook} from './HookType';
|
||||||
|
|
||||||
|
let processingVNode: VNode = null;
|
||||||
|
|
||||||
|
let activatedHook: Hook<any, any> | null = null;
|
||||||
|
|
||||||
|
// 当前hook函数对应的hook对象
|
||||||
|
let currentHook: Hook<any, any> | null = null;
|
||||||
|
|
||||||
|
export function getProcessingVNode() {
|
||||||
|
return processingVNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setProcessingVNode(vNode: VNode) {
|
||||||
|
processingVNode = vNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActivatedHook() {
|
||||||
|
return activatedHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setActivatedHook(hook: Hook<any, any>) {
|
||||||
|
activatedHook = hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCurrentHook(hook: Hook<any, any>) {
|
||||||
|
currentHook = hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function throwNotInFuncError() {
|
||||||
|
throw Error(
|
||||||
|
'Hooks should be used inside function component.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建一个hook,并放到vNode.hooks中
|
||||||
|
export function createHook(state: any = null): Hook<any, any> {
|
||||||
|
const newHook: Hook<any, any> = {
|
||||||
|
state: state,
|
||||||
|
hIndex: processingVNode.hooks.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
currentHook = newHook;
|
||||||
|
processingVNode.hooks.push(newHook);
|
||||||
|
|
||||||
|
return currentHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextHook(hook: Hook<any, any>, vNode: VNode) {
|
||||||
|
return vNode.hooks[hook.hIndex + 1] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前hook函数对应的hook对象。
|
||||||
|
// processing中的hook和activated中的hook,需要同时往前走,
|
||||||
|
// 原因:1.比对hook的数量有没有变化(非必要);2.从activated中的hook获取removeEffect
|
||||||
|
export function getCurrentHook(): Hook<any, any> {
|
||||||
|
currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode) : (processingVNode.hooks[0] || null);
|
||||||
|
const activated = processingVNode.twins;
|
||||||
|
activatedHook = activatedHook !== null ? getNextHook(activatedHook, activated) : ((activated && activated.hooks[0]) || null);
|
||||||
|
|
||||||
|
if (currentHook === null) {
|
||||||
|
if (activatedHook === null) {
|
||||||
|
throw Error('Hooks are more than expected, please check whether the hook is written in the condition.');
|
||||||
|
}
|
||||||
|
|
||||||
|
createHook(activatedHook.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentHook;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const EffectConstant = {
|
||||||
|
NoEffect: 0,
|
||||||
|
DepsChange: 1, // dependence发生了改变
|
||||||
|
LayoutEffect: 2, // 同步触发的effect
|
||||||
|
Effect: 4, // 异步触发的effect
|
||||||
|
};
|
|
@ -0,0 +1,76 @@
|
||||||
|
import type {ContextType} from '../Types';
|
||||||
|
|
||||||
|
import hookMapping from './HookMapping';
|
||||||
|
import {useRefImpl} from './UseRefHook';
|
||||||
|
import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook';
|
||||||
|
import {useCallbackImpl} from './UseCallbackHook';
|
||||||
|
import {useMemoImpl} from './UseMemoHook';
|
||||||
|
import {useImperativeHandleImpl} from './UseImperativeHook';
|
||||||
|
|
||||||
|
const {
|
||||||
|
UseContextHookMapping,
|
||||||
|
UseReducerHookMapping,
|
||||||
|
UseStateHookMapping
|
||||||
|
} = hookMapping;
|
||||||
|
|
||||||
|
type BasicStateAction<S> = ((S) => S) | S;
|
||||||
|
type Dispatch<A> = (A) => void;
|
||||||
|
|
||||||
|
|
||||||
|
export function useContext<T>(
|
||||||
|
Context: ContextType<T>,
|
||||||
|
): T {
|
||||||
|
return UseContextHookMapping.val.useContext(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
|
||||||
|
return UseStateHookMapping.val.useState(initialState);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReducer<S, I, A>(
|
||||||
|
reducer: (S, A) => S,
|
||||||
|
initialArg: I,
|
||||||
|
init?: (I) => S,
|
||||||
|
): [S, Dispatch<A>] {
|
||||||
|
return UseReducerHookMapping.val.useReducer(reducer, initialArg, init);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRef<T>(initialValue: T): {current: T} {
|
||||||
|
return useRefImpl(initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEffect(
|
||||||
|
create: () => (() => void) | void,
|
||||||
|
deps?: Array<any> | null,
|
||||||
|
): void {
|
||||||
|
return useEffectImpl(create, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLayoutEffect(
|
||||||
|
create: () => (() => void) | void,
|
||||||
|
deps?: Array<any> | null,
|
||||||
|
): void {
|
||||||
|
return useLayoutEffectImpl(create, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCallback<T>(
|
||||||
|
callback: T,
|
||||||
|
deps?: Array<any> | null,
|
||||||
|
): T {
|
||||||
|
return useCallbackImpl(callback, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMemo<T>(
|
||||||
|
create: () => T,
|
||||||
|
deps?: Array<any> | null,
|
||||||
|
): T {
|
||||||
|
return useMemoImpl(create, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useImperativeHandle<T>(
|
||||||
|
ref: {current: T | null} | ((inst: T | null) => any) | null | void,
|
||||||
|
create: () => T,
|
||||||
|
deps?: Array<any> | null,
|
||||||
|
): void {
|
||||||
|
return useImperativeHandleImpl(ref, create, deps);
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import type {VNode} from '../Types';
|
||||||
|
import hookMapping from './HookMapping';
|
||||||
|
|
||||||
|
const {
|
||||||
|
UseStateHookMapping,
|
||||||
|
UseReducerHookMapping,
|
||||||
|
UseContextHookMapping,
|
||||||
|
} = hookMapping;
|
||||||
|
|
||||||
|
import {getNewContext} from '../components/context/Context';
|
||||||
|
import {
|
||||||
|
getActivatedHook,
|
||||||
|
getProcessingVNode,
|
||||||
|
setActivatedHook,
|
||||||
|
setProcessingVNode,
|
||||||
|
setCurrentHook, getNextHook
|
||||||
|
} from './BaseHook';
|
||||||
|
import {useStateImpl,} from './UseStateHook';
|
||||||
|
import {useReducerImpl,} from './UseReducerHook';
|
||||||
|
import {HookStage, setHookStage} from './HookStage';
|
||||||
|
|
||||||
|
// hook对外入口
|
||||||
|
export function exeFunctionHook(
|
||||||
|
funcComp: (props: Object, arg: Object) => any,
|
||||||
|
props: Object,
|
||||||
|
arg: Object,
|
||||||
|
activated: VNode | null,
|
||||||
|
processing: VNode,
|
||||||
|
): any {
|
||||||
|
// 重置全局变量
|
||||||
|
resetGlobalVariable();
|
||||||
|
|
||||||
|
// 初始化hook实现函数
|
||||||
|
initHookMapping();
|
||||||
|
|
||||||
|
setProcessingVNode(processing);
|
||||||
|
|
||||||
|
processing.hooks = [];
|
||||||
|
processing.effectList = [];
|
||||||
|
|
||||||
|
// 设置hook阶段
|
||||||
|
if (activated === null || !activated.hooks.length) {
|
||||||
|
setHookStage(HookStage.Init);
|
||||||
|
} else {
|
||||||
|
setHookStage(HookStage.Update);
|
||||||
|
}
|
||||||
|
|
||||||
|
let comp = funcComp(props, arg);
|
||||||
|
|
||||||
|
// 设置hook阶段为null,用于判断hook是否在函数组件中调用
|
||||||
|
setHookStage(null);
|
||||||
|
|
||||||
|
// 判断hook是否写在了if条件中,如果在if中会出现数量不对等的情况
|
||||||
|
const activatedHook = getActivatedHook();
|
||||||
|
if (activatedHook !== null) {
|
||||||
|
if (getNextHook(getActivatedHook(), activated) !== null) {
|
||||||
|
throw Error('Hooks are less than expected, please check whether the hook is written in the condition.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置全局变量
|
||||||
|
resetGlobalVariable();
|
||||||
|
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetGlobalVariable() {
|
||||||
|
setHookStage(null);
|
||||||
|
setProcessingVNode(null);
|
||||||
|
setActivatedHook(null);
|
||||||
|
setCurrentHook(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initHookMapping() {
|
||||||
|
UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)};
|
||||||
|
UseReducerHookMapping.val = {useReducer: useReducerImpl};
|
||||||
|
UseStateHookMapping.val = {useState: useStateImpl};
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* 暂时用于解决测试代码无法运行问题,估计是:测试代码会循环或者重复依赖
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
UseContextHookType,
|
||||||
|
UseReducerHookType,
|
||||||
|
UseStateHookType
|
||||||
|
} from '../Types';
|
||||||
|
|
||||||
|
const UseStateHookMapping: {val: (null | UseStateHookType)} = {val: null};
|
||||||
|
const UseReducerHookMapping: {val: (null | UseReducerHookType)} = {val: null};
|
||||||
|
const UseContextHookMapping: {val: (null | UseContextHookType)} = {val: null};
|
||||||
|
|
||||||
|
const hookMapping = {
|
||||||
|
UseStateHookMapping,
|
||||||
|
UseReducerHookMapping,
|
||||||
|
UseContextHookMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hookMapping;
|
|
@ -0,0 +1,15 @@
|
||||||
|
// hooks阶段
|
||||||
|
export enum HookStage {
|
||||||
|
Init = 1,
|
||||||
|
Update = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
let hookStage: HookStage = null;
|
||||||
|
|
||||||
|
export function getHookStage() {
|
||||||
|
return hookStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setHookStage(phase: HookStage) {
|
||||||
|
hookStage = phase;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {EffectConstant} from './EffectConstant';
|
||||||
|
import {VNode} from '../Types';
|
||||||
|
|
||||||
|
export interface Hook<S, A> {
|
||||||
|
state: Reducer<S, A> | Effect | Memo<S> | CallBack<S> | Ref<S>;
|
||||||
|
hIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Reducer<S, A> {
|
||||||
|
stateValue: S | null;
|
||||||
|
trigger: Trigger<A> | null;
|
||||||
|
reducer: ((S, A) => S) | null;
|
||||||
|
updates: Array<Update<S, A>> | null;
|
||||||
|
isUseState: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Update<S, A> = {
|
||||||
|
action: A;
|
||||||
|
didCalculated: boolean;
|
||||||
|
state: S | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EffectList = Array<Effect> | null;
|
||||||
|
|
||||||
|
export type Effect = {
|
||||||
|
effect: () => (() => void) | void;
|
||||||
|
removeEffect: (() => void) | void;
|
||||||
|
dependencies: Array<any> | null;
|
||||||
|
effectConstant: typeof EffectConstant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Memo<V> = {
|
||||||
|
result: V | null;
|
||||||
|
dependencies: Array<any> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CallBack<F> = {
|
||||||
|
func: F | null;
|
||||||
|
dependencies: Array<any> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Ref<V> = {
|
||||||
|
current: V | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Trigger<A> = (A) => void;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {
|
||||||
|
createHook,
|
||||||
|
getCurrentHook,
|
||||||
|
throwNotInFuncError
|
||||||
|
} from './BaseHook';
|
||||||
|
import {getHookStage, HookStage} from './HookStage';
|
||||||
|
import {isArrayEqual} from '../utils/compare';
|
||||||
|
|
||||||
|
export function useCallbackImpl<F>(func: F, dependencies?: Array<any> | null): F {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
let hook;
|
||||||
|
const deps = dependencies !== undefined ? dependencies : null;
|
||||||
|
if (stage === HookStage.Init) {
|
||||||
|
hook = createHook();
|
||||||
|
hook.state = {func, dependencies: deps};
|
||||||
|
} else if (stage === HookStage.Update) {
|
||||||
|
hook = getCurrentHook();
|
||||||
|
|
||||||
|
const lastState = hook.state;
|
||||||
|
// 判断dependencies是否相同,不同就不更新state
|
||||||
|
if (lastState !== null && deps !== null && isArrayEqual(deps, lastState.dependencies)) {
|
||||||
|
return lastState.func;
|
||||||
|
}
|
||||||
|
hook.state = {func, dependencies: deps};
|
||||||
|
}
|
||||||
|
|
||||||
|
return func;
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import {
|
||||||
|
createHook,
|
||||||
|
getCurrentHook,
|
||||||
|
getActivatedHook,
|
||||||
|
getProcessingVNode, throwNotInFuncError
|
||||||
|
} from './BaseHook';
|
||||||
|
import {FlagUtils} from '../vnode/VNodeFlags';
|
||||||
|
import {EffectConstant} from './EffectConstant';
|
||||||
|
import type {Effect, EffectList} from './HookType';
|
||||||
|
import {getHookStage, HookStage} from './HookStage';
|
||||||
|
import {isArrayEqual} from '../utils/compare';
|
||||||
|
|
||||||
|
export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null,): void {
|
||||||
|
// 异步触发的effect
|
||||||
|
useEffect(effectFunc, deps, EffectConstant.Effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
|
||||||
|
// 同步触发的effect
|
||||||
|
useEffect(effectFunc, deps, EffectConstant.LayoutEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useEffect(
|
||||||
|
effectFunc: () => (() => void) | void,
|
||||||
|
deps: Array<any> | void | null,
|
||||||
|
effectType: number
|
||||||
|
): void {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage === HookStage.Init) {
|
||||||
|
return useEffectForInit(effectFunc, deps, effectType);
|
||||||
|
} else if (stage === HookStage.Update) {
|
||||||
|
return useEffectForUpdate(effectFunc, deps, effectType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEffectForInit(effectFunc, deps, effectType): void {
|
||||||
|
const hook = createHook();
|
||||||
|
const nextDeps = deps !== undefined ? deps : null;
|
||||||
|
FlagUtils.markUpdate(getProcessingVNode());
|
||||||
|
|
||||||
|
// 初始阶段,设置DepsChange标记位; 构造EffectList数组,并赋值给state
|
||||||
|
hook.state = createEffect(effectFunc, undefined, nextDeps, EffectConstant.DepsChange | effectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEffectForUpdate(effectFunc, deps, effectType): void {
|
||||||
|
const hook = getCurrentHook();
|
||||||
|
const nextDeps = deps !== undefined ? deps : null;
|
||||||
|
let removeFunc;
|
||||||
|
|
||||||
|
if (getActivatedHook() !== null) {
|
||||||
|
const effect = getActivatedHook().state as Effect;
|
||||||
|
// removeEffect是通过执行effect返回的,所以需要在activated中获取
|
||||||
|
removeFunc = effect.removeEffect;
|
||||||
|
const lastDeps = effect.dependencies;
|
||||||
|
|
||||||
|
// 判断dependencies是否相同,不同就不设置DepsChange标记位
|
||||||
|
if (nextDeps !== null && isArrayEqual(nextDeps, lastDeps)) {
|
||||||
|
hook.state = createEffect(effectFunc, removeFunc, nextDeps, effectType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlagUtils.markUpdate(getProcessingVNode());
|
||||||
|
// 设置DepsChange标记位,构造Effect,并赋值给state
|
||||||
|
hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect {
|
||||||
|
const effect: Effect = {
|
||||||
|
effect: effectFunc,
|
||||||
|
removeEffect: removeFunc,
|
||||||
|
dependencies: deps,
|
||||||
|
effectConstant: effectConstant,
|
||||||
|
};
|
||||||
|
|
||||||
|
getProcessingVNode().effectList.push(effect);
|
||||||
|
|
||||||
|
return effect;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {useLayoutEffectImpl} from './UseEffectHook';
|
||||||
|
import {getHookStage} from './HookStage';
|
||||||
|
import {throwNotInFuncError} from './BaseHook';
|
||||||
|
import type {Ref} from './HookType';
|
||||||
|
|
||||||
|
export function useImperativeHandleImpl<R>(
|
||||||
|
ref: { current: R | null } | ((any) => any) | null | void,
|
||||||
|
func: () => R,
|
||||||
|
dependencies?: Array<any> | null,
|
||||||
|
): void {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null;
|
||||||
|
useLayoutEffectImpl(effectFunc.bind(null, func, ref), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotNull(object: any): boolean {
|
||||||
|
return object !== null && object !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function effectFunc<R>(
|
||||||
|
func: () => R,
|
||||||
|
ref: Ref<R> | ((any) => any) | null,
|
||||||
|
): (() => void) | void {
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
const value = func();
|
||||||
|
ref(value);
|
||||||
|
return () => {
|
||||||
|
ref(null);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotNull(ref)) {
|
||||||
|
ref.current = func();
|
||||||
|
return () => {
|
||||||
|
ref.current = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
createHook,
|
||||||
|
getCurrentHook,
|
||||||
|
throwNotInFuncError
|
||||||
|
} from './BaseHook';
|
||||||
|
import {getHookStage, HookStage} from './HookStage';
|
||||||
|
import {isArrayEqual} from '../utils/compare';
|
||||||
|
|
||||||
|
export function useMemoImpl<V>(fun: () => V, deps?: Array<any> | null,): V {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
let hook;
|
||||||
|
let result;
|
||||||
|
const nextDeps = deps === undefined ? null : deps;
|
||||||
|
|
||||||
|
if (stage === HookStage.Init) {
|
||||||
|
hook = createHook();
|
||||||
|
result = fun();
|
||||||
|
} else if (stage === HookStage.Update) {
|
||||||
|
hook = getCurrentHook();
|
||||||
|
|
||||||
|
const lastState = hook.state;
|
||||||
|
// dependencies相同,不更新state
|
||||||
|
if (lastState !== null && nextDeps !== null && isArrayEqual(nextDeps, lastState.dependencies)) {
|
||||||
|
return lastState.result;
|
||||||
|
}
|
||||||
|
result = fun();
|
||||||
|
}
|
||||||
|
|
||||||
|
hook.state = {result, dependencies: nextDeps};
|
||||||
|
return hook.state.result;
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
import type {Hook, Reducer, Trigger, Update} from './HookType';
|
||||||
|
import {
|
||||||
|
createHook,
|
||||||
|
getCurrentHook,
|
||||||
|
getProcessingVNode,
|
||||||
|
throwNotInFuncError
|
||||||
|
} from './BaseHook';
|
||||||
|
import {
|
||||||
|
launchUpdateFromVNode
|
||||||
|
} from '../TreeBuilder';
|
||||||
|
import {isSame} from '../utils/compare';
|
||||||
|
import {setStateChange} from '../render/FunctionComponent';
|
||||||
|
import {getHookStage, HookStage} from './HookStage';
|
||||||
|
import type {VNode} from '../Types';
|
||||||
|
|
||||||
|
export function useReducerImpl<S, P, A>(reducer: (S, A) => S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger<A>] {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage === HookStage.Init) {
|
||||||
|
return useReducerForInit(reducer, initArg, init, isUseState);
|
||||||
|
} else if (stage === HookStage.Update) {
|
||||||
|
// 获取当前的hook
|
||||||
|
const currentHook = getCurrentHook();
|
||||||
|
// 获取currentHook的更新数组
|
||||||
|
const currentHookUpdates = (currentHook.state as Reducer<S, A>).updates;
|
||||||
|
|
||||||
|
return updateReducerHookState(currentHookUpdates, currentHook, reducer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造新的Update数组
|
||||||
|
function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
|
||||||
|
const newUpdate: Update<S, A> = {
|
||||||
|
action,
|
||||||
|
state: null,
|
||||||
|
didCalculated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updates = (hook.state as Reducer<S, A>).updates;
|
||||||
|
// 更新updates数组,newUpdate添加至数组尾部
|
||||||
|
if (updates === null) {
|
||||||
|
updates = [newUpdate];
|
||||||
|
(hook.state as Reducer<S, A>).updates = updates;
|
||||||
|
} else {
|
||||||
|
updates.push(newUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setState, setReducer触发函数
|
||||||
|
export function TriggerAction<S, A, T>(vNode: VNode, hook: Hook<S, A>, action: A) {
|
||||||
|
const newUpdate = insertUpdate(action, hook);
|
||||||
|
const twins = vNode.twins;
|
||||||
|
|
||||||
|
// 判断是否需要刷新
|
||||||
|
if (!vNode.shouldUpdate && (twins === null || !twins.shouldUpdate)) {
|
||||||
|
const reducerObj = hook.state as Reducer<S, A>;
|
||||||
|
const { stateValue, reducer } = reducerObj;
|
||||||
|
|
||||||
|
// 在进入render阶段前reducer没有变化,可以复用state值,提升性能
|
||||||
|
newUpdate.state = reducer(stateValue, action);
|
||||||
|
// 标记为已经计算过,不需要重新计算了
|
||||||
|
newUpdate.didCalculated = true;
|
||||||
|
|
||||||
|
if (isSame(newUpdate.state, stateValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行vNode节点渲染
|
||||||
|
launchUpdateFromVNode(vNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReducerForInit<S, A>(reducer, initArg, init, isUseState?: boolean): [S, Trigger<A>] {
|
||||||
|
// 计算初始stateValue
|
||||||
|
let stateValue;
|
||||||
|
if (typeof initArg === 'function') {
|
||||||
|
stateValue = initArg();
|
||||||
|
} else if (typeof init === 'function') {
|
||||||
|
stateValue = init(initArg);
|
||||||
|
} else {
|
||||||
|
stateValue = initArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hook = createHook();
|
||||||
|
// 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState}
|
||||||
|
hook.state = {
|
||||||
|
stateValue: stateValue,
|
||||||
|
trigger: TriggerAction.bind(null, getProcessingVNode(), hook),
|
||||||
|
reducer,
|
||||||
|
updates: null,
|
||||||
|
isUseState
|
||||||
|
} as Reducer<S, A>;
|
||||||
|
|
||||||
|
return [hook.state.stateValue, hook.state.trigger];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新hook.state
|
||||||
|
function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer): [S, Trigger<A>] {
|
||||||
|
if (currentHookUpdates !== null) {
|
||||||
|
// 循环遍历更新数组,计算新的状态值
|
||||||
|
const newState = calculateNewState(currentHookUpdates, currentHook, reducer);
|
||||||
|
if (!isSame(newState, currentHook.state.stateValue)) {
|
||||||
|
setStateChange(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新hook对象状态值
|
||||||
|
currentHook.state.stateValue = newState;
|
||||||
|
// 重置更新数组为null
|
||||||
|
currentHook.state.updates = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHook.state.reducer = reducer;
|
||||||
|
return [currentHook.state.stateValue, currentHook.state.trigger];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算stateValue值
|
||||||
|
function calculateNewState<S, A>(currentHookUpdates: Array<Update<S, A>>, currentHook, reducer: (S, A) => S) {
|
||||||
|
let reducerObj = currentHook.state;
|
||||||
|
let state = reducerObj.stateValue;
|
||||||
|
|
||||||
|
// 循环遍历更新数组,计算新的状态值
|
||||||
|
currentHookUpdates.forEach(update => {
|
||||||
|
// 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState
|
||||||
|
if (update.didCalculated && reducerObj.isUseState) {
|
||||||
|
state = update.state;
|
||||||
|
} else {
|
||||||
|
const action = update.action;
|
||||||
|
state = reducer(state, action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {createHook, getCurrentHook, throwNotInFuncError} from './BaseHook';
|
||||||
|
import {getHookStage, HookStage} from './HookStage';
|
||||||
|
import type {Ref} from './HookType';
|
||||||
|
|
||||||
|
export function useRefImpl<V>(value: V): Ref<V> {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
|
let hook;
|
||||||
|
if (stage === HookStage.Init) {
|
||||||
|
hook = createHook();
|
||||||
|
hook.state = {current: value};
|
||||||
|
} else if (stage === HookStage.Update) {
|
||||||
|
hook = getCurrentHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hook.state;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import type {Trigger} from './HookType';
|
||||||
|
import {useReducerImpl} from './UseReducerHook';
|
||||||
|
|
||||||
|
function defaultReducer<S>(state: S, action: ((S) => S) | S): S {
|
||||||
|
// @ts-ignore
|
||||||
|
return typeof action === 'function' ? action(state) : action;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStateImpl<S>(initArg: (() => S) | S): [S, Trigger<((S) => S) | S>] {
|
||||||
|
return useReducerImpl(defaultReducer, initArg, undefined, true);
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* 浏览器相关实现
|
||||||
|
*/
|
||||||
|
|
||||||
|
export let now;
|
||||||
|
|
||||||
|
if (typeof performance === 'object' && typeof performance.now === 'function') {
|
||||||
|
const localPerformance = performance;
|
||||||
|
now = () => localPerformance.now();
|
||||||
|
} else {
|
||||||
|
const localDate = Date;
|
||||||
|
const initialTime = localDate.now();
|
||||||
|
now = () => localDate.now() - initialTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMessageLoopRunning = false;
|
||||||
|
let browserCallback = null;
|
||||||
|
|
||||||
|
// 默认每次只运行5ms
|
||||||
|
const runTime = 5;
|
||||||
|
let deadline = 0;
|
||||||
|
|
||||||
|
export function isOverTime() {
|
||||||
|
return now() >= deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1、设置deadline;2、回调TaskExecutor传过来的browserCallback
|
||||||
|
const callRenderTasks = () => {
|
||||||
|
if (browserCallback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = now();
|
||||||
|
// 计算deadline
|
||||||
|
deadline = currentTime + runTime;
|
||||||
|
try {
|
||||||
|
// 执行callback
|
||||||
|
const hasMoreTask = browserCallback(
|
||||||
|
currentTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasMoreTask) { // 没有更多task
|
||||||
|
isMessageLoopRunning = false;
|
||||||
|
browserCallback = null;
|
||||||
|
} else {
|
||||||
|
// 还有task,继续调用
|
||||||
|
port.postMessage(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
port.postMessage(null);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const channel = new MessageChannel();
|
||||||
|
const port = channel.port2;
|
||||||
|
channel.port1.onmessage = callRenderTasks;
|
||||||
|
|
||||||
|
export function requestBrowserCallback(callback) {
|
||||||
|
browserCallback = callback;
|
||||||
|
|
||||||
|
if (!isMessageLoopRunning) {
|
||||||
|
isMessageLoopRunning = true;
|
||||||
|
port.postMessage(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* 利用TaskExecutor的异步任务,封装一个renderQueue来执行同步的渲染callback
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {runAsync, cancelTask, ImmediatePriority} from './TaskExecutor';
|
||||||
|
|
||||||
|
type RenderCallback = () => RenderCallback | null;
|
||||||
|
|
||||||
|
let renderQueue: Array<RenderCallback> | null = null;
|
||||||
|
// 保存正在等待的异步Task,可以用于取消
|
||||||
|
let callingQueueTask: any | null = null;
|
||||||
|
// 防止重入
|
||||||
|
let isCallingRenderQueue = false;
|
||||||
|
|
||||||
|
export function pushRenderCallback(callback: RenderCallback) {
|
||||||
|
if (renderQueue === null) {
|
||||||
|
renderQueue = [callback];
|
||||||
|
// 高优先级的异步调度
|
||||||
|
callingQueueTask = runAsync(callRenderQueue, ImmediatePriority);
|
||||||
|
} else {
|
||||||
|
// 不需要调度,在syncQueue创建的时候已经调度了
|
||||||
|
renderQueue.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回一个空对象,用于区别null
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callRenderQueueImmediate() {
|
||||||
|
if (callingQueueTask !== null) {
|
||||||
|
// 取消异步调度
|
||||||
|
cancelTask(callingQueueTask);
|
||||||
|
callingQueueTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
callRenderQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行render回调
|
||||||
|
function callRenderQueue() {
|
||||||
|
if (!isCallingRenderQueue && renderQueue !== null) {
|
||||||
|
// 防止重入
|
||||||
|
isCallingRenderQueue = true;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
try {
|
||||||
|
for (; i < renderQueue.length; i++) {
|
||||||
|
let callback = renderQueue[i];
|
||||||
|
do {
|
||||||
|
callback = callback();
|
||||||
|
} while (callback !== null);
|
||||||
|
}
|
||||||
|
renderQueue = null;
|
||||||
|
} catch (error) {
|
||||||
|
// 如果有异常抛出,请将剩余的回调留在队列中
|
||||||
|
if (renderQueue !== null) {
|
||||||
|
renderQueue = renderQueue.slice(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在下一个异步中再调用
|
||||||
|
runAsync(callRenderQueueImmediate, ImmediatePriority);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
isCallingRenderQueue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/**
|
||||||
|
* 调度器的核心实现
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
requestBrowserCallback,
|
||||||
|
isOverTime,
|
||||||
|
now,
|
||||||
|
} from './BrowserAsync';
|
||||||
|
|
||||||
|
import {add, shift, first} from './TaskQueue';
|
||||||
|
|
||||||
|
const ImmediatePriority = 1;
|
||||||
|
const NormalPriority = 10;
|
||||||
|
|
||||||
|
// 用于控制插入任务的顺序
|
||||||
|
let idCounter = 1;
|
||||||
|
|
||||||
|
let currentPriorityLevel = NormalPriority;
|
||||||
|
|
||||||
|
// 正在执行task
|
||||||
|
let isProcessing = false;
|
||||||
|
|
||||||
|
// 调度中,等待浏览器回调
|
||||||
|
let isScheduling = false;
|
||||||
|
|
||||||
|
function runSync(callback, priorityLevel = NormalPriority) {
|
||||||
|
const previousPriorityLevel = currentPriorityLevel;
|
||||||
|
currentPriorityLevel = priorityLevel;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return callback();
|
||||||
|
} finally {
|
||||||
|
currentPriorityLevel = previousPriorityLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAsync(callback, priorityLevel= NormalPriority ) {
|
||||||
|
let timeout;
|
||||||
|
switch (priorityLevel) {
|
||||||
|
case ImmediatePriority:
|
||||||
|
timeout = -1;
|
||||||
|
break;
|
||||||
|
case NormalPriority:
|
||||||
|
default:
|
||||||
|
timeout = 5000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = {
|
||||||
|
id: idCounter++,
|
||||||
|
callback,
|
||||||
|
priorityLevel,
|
||||||
|
expirationTime: now() + timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
add(task);
|
||||||
|
|
||||||
|
if (!isScheduling && !isProcessing) {
|
||||||
|
isScheduling = true;
|
||||||
|
requestBrowserCallback(callTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
function callTasks(initialTime) {
|
||||||
|
isScheduling = false;
|
||||||
|
isProcessing = true;
|
||||||
|
|
||||||
|
let task = null;
|
||||||
|
const previousPriorityLevel = currentPriorityLevel;
|
||||||
|
try {
|
||||||
|
let currentTime = initialTime;
|
||||||
|
task = first();
|
||||||
|
|
||||||
|
// 循环执行task
|
||||||
|
while (task !== null) {
|
||||||
|
if (
|
||||||
|
task.expirationTime > currentTime &&
|
||||||
|
isOverTime()
|
||||||
|
) {
|
||||||
|
// 没到deadline
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = task.callback;
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
task.callback = null;
|
||||||
|
currentPriorityLevel = task.priorityLevel;
|
||||||
|
const didUserCallbackTimeout = task.expirationTime <= currentTime;
|
||||||
|
|
||||||
|
const continuationCallback = callback(didUserCallbackTimeout);
|
||||||
|
currentTime = now();
|
||||||
|
// 执行callback返回函数,重置callback
|
||||||
|
if (typeof continuationCallback === 'function') {
|
||||||
|
task.callback = continuationCallback;
|
||||||
|
} else {
|
||||||
|
if (task === first()) {
|
||||||
|
shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
task = first();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回是否还有任务,如果有,说明是被中断了
|
||||||
|
return task !== null;
|
||||||
|
} finally {
|
||||||
|
task = null;
|
||||||
|
currentPriorityLevel = previousPriorityLevel;
|
||||||
|
isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTask(task) {
|
||||||
|
task.callback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentPriorityLevel() {
|
||||||
|
return currentPriorityLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ImmediatePriority,
|
||||||
|
NormalPriority,
|
||||||
|
runSync,
|
||||||
|
runAsync,
|
||||||
|
cancelTask,
|
||||||
|
getCurrentPriorityLevel,
|
||||||
|
now,
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* 任务列表的实现
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Queue = Array<Node>;
|
||||||
|
type Node = {
|
||||||
|
id: number;
|
||||||
|
expirationTime: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 任务队列
|
||||||
|
const taskQueue: Queue = [];
|
||||||
|
|
||||||
|
export function add(node: Node): void {
|
||||||
|
// 查找第一个大于等于 value 的下标,都比 value 小则返回 -1
|
||||||
|
const idx = getBiggerIdx(node);
|
||||||
|
|
||||||
|
if (idx === 0) {
|
||||||
|
taskQueue.unshift(node);
|
||||||
|
} else if (idx === -1) {
|
||||||
|
taskQueue.push(node);
|
||||||
|
} else {
|
||||||
|
taskQueue.splice(idx, 0, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二分法查找第一个大于等于 value 的下标,都比 value 小则返回 -1,时间复杂度O(logn)
|
||||||
|
function getBiggerIdx(node: Node) {
|
||||||
|
let left = 0;
|
||||||
|
let right = taskQueue.length - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const middle = left + ((right - left) >> 1);
|
||||||
|
|
||||||
|
if (compare(taskQueue[middle], node) > 0)
|
||||||
|
right = middle - 1;
|
||||||
|
else
|
||||||
|
left = middle + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (left < taskQueue.length) ? left : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function first(): Node | null {
|
||||||
|
const val = taskQueue[0];
|
||||||
|
return val !== undefined ? val : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shift(): Node | null {
|
||||||
|
const val = taskQueue.shift();
|
||||||
|
return val !== undefined ? val : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare(a: Node, b: Node) {
|
||||||
|
// 优先先用index排序,其次用id
|
||||||
|
const diff = a.expirationTime - b.expirationTime;
|
||||||
|
return diff !== 0 ? diff : a.id - b.id;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
'use strict'
|
||||||
|
const ejs = require('ejs');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const console = require('console');
|
||||||
|
const rimRaf = require('rimRaf');
|
||||||
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
|
|
||||||
|
const libPathPrefix = '../build';
|
||||||
|
const suffix = argv.dev ? 'development.js' : 'production.js';
|
||||||
|
|
||||||
|
const readLib = (lib) => {
|
||||||
|
const libName = lib.split('.')[0];
|
||||||
|
const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`);
|
||||||
|
if (fs.existsSync(libPath)) {
|
||||||
|
return fs.readFileSync(libPath,'utf-8');
|
||||||
|
} else {
|
||||||
|
console.log(chalk.red(`Error: "${libPath}" 文件不存在\n先运行 npm run build`))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ejs.renderFile(path.resolve(__dirname, './template.ejs'), {
|
||||||
|
Horizon: readLib(`horizon.${suffix}`),
|
||||||
|
}, null, function(err, result) {
|
||||||
|
const common3rdLibPath = path.resolve(__dirname, `${libPathPrefix}/common3rdlib.min.js`)
|
||||||
|
rimRaf(common3rdLibPath, e => {
|
||||||
|
if (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
fs.writeFileSync(common3rdLibPath, result);
|
||||||
|
console.log(chalk.green(`成功生成: ${common3rdLibPath}`))
|
||||||
|
})
|
||||||
|
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const libPath = path.join(__dirname, '../../libs');
|
||||||
|
const baseConfig = {
|
||||||
|
entry: path.resolve(libPath, 'index.ts'),
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(js)|ts$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
{ loader: 'babel-loader' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts'],
|
||||||
|
alias: {
|
||||||
|
'horizon-external': path.join(libPath, './horizon-external'),
|
||||||
|
'horizon': path.join(libPath, './horizon'),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = baseConfig;
|
|
@ -0,0 +1,4 @@
|
||||||
|
const dev = require('./webpack.dev');
|
||||||
|
const pro = require('./webpack.pro');
|
||||||
|
|
||||||
|
module.exports = [...dev, ...pro];
|
|
@ -0,0 +1,43 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
|
const baseConfig = require('./webpack.base');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const mode = 'development';
|
||||||
|
const devtool = 'inline-source-map';
|
||||||
|
const filename = 'horizon.development.js';
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
new ESLintPlugin({fix: true}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': '"development"',
|
||||||
|
__DEV__: 'true',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const umd = {
|
||||||
|
...baseConfig,
|
||||||
|
mode,
|
||||||
|
devtool,
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../../build/horizon/umd'),
|
||||||
|
filename,
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
library: 'Horizon',
|
||||||
|
},
|
||||||
|
plugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cjs = {
|
||||||
|
...baseConfig,
|
||||||
|
mode,
|
||||||
|
devtool,
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../../build/horizon/cjs'),
|
||||||
|
filename,
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
},
|
||||||
|
plugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = [umd, cjs];
|
|
@ -0,0 +1,59 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const baseConfig = require('./webpack.base');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const mode = 'production';
|
||||||
|
const devtool = 'none';
|
||||||
|
const filename = 'horizon.production.js';
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
__DEV__: 'false',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const proBaseConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
mode,
|
||||||
|
devtool,
|
||||||
|
plugins,
|
||||||
|
optimization: {
|
||||||
|
minimize: true
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const umd = {
|
||||||
|
...proBaseConfig,
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../../build/horizon/umd'),
|
||||||
|
filename,
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
library: 'Horizon',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const cjs = {
|
||||||
|
...proBaseConfig,
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../../build/horizon/cjs'),
|
||||||
|
filename,
|
||||||
|
libraryTarget: 'commonjs',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
...plugins,
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../../libs/index.js'),
|
||||||
|
to: path.join(__dirname, '../../build/horizon/index.js'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../../libs/package.json'),
|
||||||
|
to: path.join(__dirname, '../../build/horizon/package.json'),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = [umd, cjs];
|
Loading…
Reference in New Issue