diff --git a/README.md b/README.md index a2fe80bb..7b99c861 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ### 核心能力 -#### 响应式API +#### 响应式API(实验性功能,可在reactive分支查看代码或使用npm仓中experiment版本体验) * openInula 通过最小化重新渲染的范围,从而进行高效的UI渲染。这种方式避免了虚拟 DOM 的开销,使得 openInula 在性能方面表现出色。 * openInula 通过比较变化前后的 JavaScript 对象以细粒度的依赖追踪机制来实现响应式更新,无需用户过度关注性能优化。 diff --git a/packages/inula-router/src/connect-router/connectedRouter.tsx b/packages/inula-router/src/connect-router/connectedRouter.tsx index a526663a..8ce5a383 100644 --- a/packages/inula-router/src/connect-router/connectedRouter.tsx +++ b/packages/inula-router/src/connect-router/connectedRouter.tsx @@ -16,7 +16,7 @@ import Inula from 'openinula'; import { useLayoutEffect, useRef, reduxAdapter, InulaNode } from 'openinula'; import { connect, ReactReduxContext } from 'react-redux'; -import { Store } from 'redux'; +import type { Store } from 'redux'; import { History, Location, Router } from '../router'; import { Action, DefaultStateType, Navigation } from '../history/types'; import { ActionMessage, onLocationChanged } from './actions'; @@ -130,9 +130,14 @@ function getConnectedRouter(type: StoreType) { ); }; + const ConnectedHRouterWithContext = (props: any) => { + const { store, ...rest } = props; + return ; + }; + // 针对不同的Store类型,使用对应的connect函数 if (type === 'InulaXCompat') { - return hConnect(null as any, mapDispatchToProps)(ConnectedRouterWithContext as any); + return hConnect(null as any, mapDispatchToProps)(ConnectedHRouterWithContext as any); } if (type === 'Redux') { return connect(null, mapDispatchToProps)(ConnectedRouterWithContext); diff --git a/packages/inula-router/src/history/hashHistory.ts b/packages/inula-router/src/history/hashHistory.ts index c44a85bb..f1cf421d 100644 --- a/packages/inula-router/src/history/hashHistory.ts +++ b/packages/inula-router/src/history/hashHistory.ts @@ -107,7 +107,7 @@ export function createHashHistory(option: HashHistoryOptio warning(state !== undefined, 'Hash history does not support state, it will be ignored'); const action = Action.push; - const location = createLocation(history.location, to, undefined, ''); + const location = createLocation(history.location, to, state, ''); transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => { if (!isJump) { @@ -132,7 +132,7 @@ export function createHashHistory(option: HashHistoryOptio function replace(to: To, state?: S) { warning(state !== undefined, 'Hash history does not support state, it will be ignored'); const action = Action.replace; - const location = createLocation(history.location, to, undefined, ''); + const location = createLocation(history.location, to, state, ''); transitionManager.confirmJumpTo(location, action, getUserConfirmation, isJump => { if (!isJump) { diff --git a/packages/inula-router/src/history/utils.ts b/packages/inula-router/src/history/utils.ts index 27447967..8a671372 100644 --- a/packages/inula-router/src/history/utils.ts +++ b/packages/inula-router/src/history/utils.ts @@ -28,8 +28,8 @@ export function createPath(path: Partial): string { } export function parsePath(url: string): Partial { + let pathname = url || '/'; const parsedPath: Partial = { - pathname: url || '/', search: '', hash: '', }; @@ -38,16 +38,16 @@ export function parsePath(url: string): Partial { if (hashIdx > -1) { const hash = url.substring(hashIdx); parsedPath.hash = hash === '#' ? '' : hash; - url = url.substring(0, hashIdx); + pathname = pathname.substring(0, hashIdx); } const searchIdx = url.indexOf('?'); if (searchIdx > -1) { const search = url.substring(searchIdx); parsedPath.search = search === '?' ? '' : search; - url = url.substring(0, searchIdx); + pathname = pathname.substring(0, searchIdx); } - parsedPath.pathname = url; + parsedPath.pathname = pathname; return parsedPath; } @@ -116,12 +116,12 @@ export function stripBasename(path: string, prefix: string): string { export function createMemoryRecord(initVal: S, fn: (arg: S) => T) { let visitedRecord: T[] = [fn(initVal)]; - function getDelta(to: S, form: S): number { - let toIdx = visitedRecord.lastIndexOf(fn(to)); + function getDelta(toKey: S, formKey: S): number { + let toIdx = visitedRecord.lastIndexOf(fn(toKey)); if (toIdx === -1) { toIdx = 0; } - let fromIdx = visitedRecord.lastIndexOf(fn(form)); + let fromIdx = visitedRecord.lastIndexOf(fn(formKey)); if (fromIdx === -1) { fromIdx = 0; } diff --git a/packages/inula-router/src/router/NavLink.tsx b/packages/inula-router/src/router/NavLink.tsx index 37cbbeca..9a836e71 100644 --- a/packages/inula-router/src/router/NavLink.tsx +++ b/packages/inula-router/src/router/NavLink.tsx @@ -24,27 +24,42 @@ import { parsePath } from '../history/utils'; type NavLinkProps = { to: Partial | string | ((location: Location) => string | Partial); - isActive?: (match: Matched | null, location: Location) => boolean; + isActive?

(match: Matched

| null, location: Location): boolean; + exact?: boolean; + strict?: boolean; + sensitive?: boolean; + className?: string | ((isActive: boolean) => string); + activeClassName?: string; [key: string]: any; -} & LinkProps; +} & Omit; type Page = 'page'; function NavLink

(props: P) { - const { to, isActive, ...rest } = props; + const { to, isActive, exact, strict, sensitive, className, activeClassName, ...rest } = props; const context = useContext(Context); const toLocation = typeof to === 'function' ? to(context.location) : to; const { pathname } = typeof toLocation === 'string' ? parsePath(toLocation) : toLocation; - const match = pathname ? matchPath(context.location.pathname, pathname) : null; + const match = pathname ? matchPath(context.location.pathname, pathname, { + exact: exact, + strictMode: strict, + caseSensitive: sensitive, + }) : null; - const isLinkActive = match && isActive ? isActive(match, context.location) : false; + const isLinkActive = !!(isActive ? isActive(match, context.location) : match); + + let classNames = typeof className === 'function' ? className(isLinkActive) : className; + if (isLinkActive) { + classNames = [activeClassName, classNames].filter(Boolean).join(''); + } const page: Page = 'page'; const otherProps = { - 'aria-current': isLinkActive ? page : false, + className: classNames, + 'aria-current': isLinkActive ? page : undefined, ...rest, }; diff --git a/packages/inula-router/src/router/Route.tsx b/packages/inula-router/src/router/Route.tsx index fdf2db7f..ddd7c195 100644 --- a/packages/inula-router/src/router/Route.tsx +++ b/packages/inula-router/src/router/Route.tsx @@ -43,15 +43,19 @@ export type RouteProps

= {}, Path extends string = function Route = GetURLParams>(props: RouteProps) { const context = useContext(RouterContext); - const { computed, location, path } = props; - let { children, component, render } = props; + const { computed, location, path, component, render, strict, sensitive, exact } = props; + let { children } = props; let match: Matched

| null; const routeLocation = location || context.location; if (computed) { match = computed; } else if (path) { - match = matchPath

(routeLocation.pathname, path); + match = matchPath

(routeLocation.pathname, path, { + strictMode: strict, + caseSensitive: sensitive, + exact: exact, + }); } else { match = context.match; } diff --git a/packages/inula-router/src/router/Router.tsx b/packages/inula-router/src/router/Router.tsx index dd9791a1..1dcdf7e6 100644 --- a/packages/inula-router/src/router/Router.tsx +++ b/packages/inula-router/src/router/Router.tsx @@ -29,22 +29,27 @@ function Router

(props: P) { const { history, children = null } = props; const [location, setLocation] = useState(props.history.location); const pendingLocation = useRef(null); + const unListen = useRef void)>(null); + const isMount = useRef(false); // 在Router加载时就监听history地址变化,以保证在始渲染时重定向能正确触发 - const unListen = useRef void)>( - history.listen(arg => { + if (unListen.current === null) { + unListen.current = history.listen(arg => { pendingLocation.current = arg.location; - }), - ); + }); + } // 模拟componentDidMount和componentWillUnmount useLayoutEffect(() => { + isMount.current = true; if (unListen.current) { unListen.current(); } // 监听history中的位置变化 unListen.current = history.listen(arg => { - setLocation(arg.location); + if (isMount.current) { + setLocation(arg.location); + } }); if (pendingLocation.current) { @@ -53,6 +58,7 @@ function Router

(props: P) { return () => { if (unListen.current) { + isMount.current = false; unListen.current(); unListen.current = null; pendingLocation.current = null; diff --git a/packages/inula-router/src/router/matcher/parser.ts b/packages/inula-router/src/router/matcher/parser.ts index 0598066c..ea75e4e5 100644 --- a/packages/inula-router/src/router/matcher/parser.ts +++ b/packages/inula-router/src/router/matcher/parser.ts @@ -40,7 +40,7 @@ export type Matched

= { const defaultOption: Required = { // url匹配时是否大小写敏感 - caseSensitive: true, + caseSensitive: false, // 是否严格匹配url结尾的/ strictMode: false, // 是否完全精确匹配 diff --git a/packages/inula-router/src/router/withRouter.tsx b/packages/inula-router/src/router/withRouter.tsx index 3d7f58bd..04c588c8 100644 --- a/packages/inula-router/src/router/withRouter.tsx +++ b/packages/inula-router/src/router/withRouter.tsx @@ -20,10 +20,11 @@ import RouterContext from './context'; function withRouter(Component: C) { function ComponentWithRouterProp(props: any) { + const { wrappedComponentRef, ...rest } = props; const { history, location, match } = useContext(RouterContext); const routeProps = { history: history, location: location, match: match }; - return ; + return ; } return ComponentWithRouterProp; diff --git a/packages/inula/package.json b/packages/inula/package.json index 2eb04d4b..54ae4908 100644 --- a/packages/inula/package.json +++ b/packages/inula/package.json @@ -24,5 +24,13 @@ "watch-test": "yarn test --watch --dev" }, "files": ["build/**/*", "README.md"], - "types": "./build/@types/index.d.ts" + "types": "./build/@types/index.d.ts", + "exports": { + ".": { + "default": "./index.js" + }, + "./package.json":"./package.json", + "./jsx-runtime": "./jsx-runtime.js", + "./jsx-dev-runtime": "./jsx-dev-runtime.js" + } } diff --git a/packages/inula/scripts/__tests__/ComponentTest/FragmentComponent.test.js b/packages/inula/scripts/__tests__/ComponentTest/FragmentComponent.test.js index c91c5dac..6ac08b89 100644 --- a/packages/inula/scripts/__tests__/ComponentTest/FragmentComponent.test.js +++ b/packages/inula/scripts/__tests__/ComponentTest/FragmentComponent.test.js @@ -473,4 +473,61 @@ describe('Fragment', () => { expect(LogUtils.getNotClear()).toEqual([]); expect(container.textContent).toBe('1'); }); + + it('Fragment 设置相同的key不会重新渲染组件', () => { + const { useState, useRef, act, Fragment } = Inula; + let setFn; + const didMount = jest.fn(); + const didUpdate = jest.fn(); + + const App = () => { + const [list, setList] = useState([ + { text: 'Apple', id: 1 }, + { text: 'Banana', id: 2 }, + ]); + + setFn = setList; + + return ( + <> + {list.map(item => { + return ( + + + + ); + })} + + ); + }; + + const Child = ({ val }) => { + const mount = useRef(false); + useEffect(() => { + if (!mount.current) { + didMount(); + mount.current = true; + } else { + didUpdate(); + } + }); + + return

{val}
; + }; + + act(() => Inula.render(, container)); + + expect(didMount).toHaveBeenCalledTimes(2); + act(() => { + setFn([ + { text: 'Apple', id: 1 }, + { text: 'Banana', id: 2 }, + { text: 'Grape', id: 3 }, + ]); + }); + + // 数组前两项Key不变子组件更新,第三个子组件会挂载 + expect(didMount).toHaveBeenCalledTimes(3); + expect(didUpdate).toHaveBeenCalledTimes(2); + }); }); diff --git a/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js b/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js index ef17a0aa..0b5d18a8 100644 --- a/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js +++ b/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js @@ -14,7 +14,6 @@ */ import * as Inula from '../../../src/index'; -const { useState } = Inula; describe('FunctionComponent Test', () => { it('渲染无状态组件', () => { const App = props => { @@ -90,39 +89,4 @@ describe('FunctionComponent Test', () => { Inula.render(, container); expect(container.querySelector('div').style['_values']['--max-segment-num']).toBe(10); }); - - it('函数组件渲染中重新发生setState不触发整个应用重新更新', () => { - function CountLabel({ count }) { - const [prevCount, setPrevCount] = useState(count); - const [trend, setTrend] = useState(null); - if (prevCount !== count) { - setPrevCount(count); - setTrend(count > prevCount ? 'increasing' : 'decreasing'); - } - return ( - <> -

{count}

- - {trend &&

The count is {trend}

} - - ); - } - - let count = 0; - function Child({ trend }) { - count++; - return
{trend}
; - } - - let update; - function App() { - const [count, setCount] = useState(0); - update = setCount; - return ; - } - - Inula.render(, container); - update(1); - expect(count).toBe(2); - }) }); diff --git a/packages/inula/scripts/__tests__/ComponentTest/StrictMode.test.js b/packages/inula/scripts/__tests__/ComponentTest/StrictMode.test.js new file mode 100644 index 00000000..a12fbed4 --- /dev/null +++ b/packages/inula/scripts/__tests__/ComponentTest/StrictMode.test.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import * as Inula from '../../../src/index'; +import { getLogUtils } from '../jest/testUtils'; + +describe('StrictMode Component test', () => { + const LogUtils = getLogUtils(); + const { useState, useEffect, useRef, render, act } = Inula; + it('StrictMode is same to Fragment', () => { + const Parent = () => { + const [, setS] = useState('1'); + return ( + + + + + ); + }; + + const Child = () => { + const isMount = useRef(false); + + useEffect(() => { + if (!isMount.current) { + LogUtils.log('didMount'); + isMount.current = true; + } else { + LogUtils.log('didUpdate'); + } + }); + + return null; + }; + + act(() => render(, container)); + // 子组件初始化,会挂载一次 + expect(LogUtils.getAndClear()).toStrictEqual(['didMount']); + const button = container.querySelector('#btn'); + // 父组件State更新,子组件也会更新一次 + act(() => button.click()); + expect(LogUtils.getAndClear()).toStrictEqual(['didUpdate']); + }); +}); diff --git a/packages/inula/src/dom/DOMExternal.ts b/packages/inula/src/dom/DOMExternal.ts index 076709e9..12dea7b1 100644 --- a/packages/inula/src/dom/DOMExternal.ts +++ b/packages/inula/src/dom/DOMExternal.ts @@ -21,6 +21,7 @@ import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils'; import { listenSimulatedDelegatedEvents } from '../event/EventBinding'; import { Callback } from '../renderer/Types'; import { InulaNode } from '../types'; +import { EVENT_KEY } from './DOMInternalKeys'; function createRoot(children: any, container: Container, callback?: Callback) { // 清空容器 @@ -89,7 +90,7 @@ function findDOMNode(domOrEle?: Element): null | Element | Text { // 情况根节点监听器 function removeRootEventLister(container: Container) { - const events = (container as any).$EV; + const events = (container as any)[EVENT_KEY]; if (events) { Object.keys(events).forEach(event => { const listener = events[event]; diff --git a/packages/inula/src/dom/DOMInternalKeys.ts b/packages/inula/src/dom/DOMInternalKeys.ts index a8727261..c4f1824f 100644 --- a/packages/inula/src/dom/DOMInternalKeys.ts +++ b/packages/inula/src/dom/DOMInternalKeys.ts @@ -22,9 +22,12 @@ import type { Container, Props } from './DOMOperator'; import { DomComponent, DomText, TreeRoot } from '../renderer/vnode/VNodeTags'; -const INTERNAL_VNODE = '_inula_VNode'; -const INTERNAL_PROPS = '_inula_Props'; -const INTERNAL_NONDELEGATEEVENTS = '_inula_NonDelegatedEvents'; +const randomKey = Math.random().toString(16).slice(2); +const INTERNAL_VNODE = `_inula_VNode_${randomKey}`; +const INTERNAL_PROPS = `_inula_Props_${randomKey}`; +const INTERNAL_NONDELEGATEEVENTS = `_inula_nonDelegatedEvents_${randomKey}`; +export const HANDLER_KEY = `_inula_valueChangeHandler_${randomKey}`; +export const EVENT_KEY = `_inula_ev_${randomKey}`; // 通过 VNode 实例获取 DOM 节点 export function getDom(vNode: VNode): Element | Text | null { diff --git a/packages/inula/src/dom/valueHandler/ValueChangeHandler.ts b/packages/inula/src/dom/valueHandler/ValueChangeHandler.ts index cd07c35c..262dc837 100644 --- a/packages/inula/src/dom/valueHandler/ValueChangeHandler.ts +++ b/packages/inula/src/dom/valueHandler/ValueChangeHandler.ts @@ -18,7 +18,7 @@ * 只有值发生变化时才会触发change事件。 */ -const HANDLER_KEY = '_valueChangeHandler'; +import { HANDLER_KEY } from '../DOMInternalKeys'; // 判断是否是 check 类型 function isCheckType(dom: HTMLInputElement): boolean { diff --git a/packages/inula/src/event/EventBinding.ts b/packages/inula/src/event/EventBinding.ts index f3e9f58e..52bfffcd 100644 --- a/packages/inula/src/event/EventBinding.ts +++ b/packages/inula/src/event/EventBinding.ts @@ -18,7 +18,7 @@ */ import { allDelegatedInulaEvents, portalDefaultDelegatedEvents, simulatedDelegatedEvents } from './EventHub'; import { isDocument } from '../dom/utils/Common'; -import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys'; +import { EVENT_KEY, getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys'; import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder'; import { handleEventMain } from './InulaEventMain'; import { decorateNativeEvent } from './EventWrapper'; @@ -73,8 +73,8 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) { const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent; // 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听 - currentRoot.realNode.$EV = currentRoot.realNode.$EV ?? {}; - const events = currentRoot.realNode.$EV; + currentRoot.realNode[EVENT_KEY] = currentRoot.realNode[EVENT_KEY] ?? {}; + const events = currentRoot.realNode[EVENT_KEY]; if (!events[nativeFullName]) { events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); diff --git a/packages/inula/src/event/InulaEventMain.ts b/packages/inula/src/event/InulaEventMain.ts index cff523fc..817618f4 100644 --- a/packages/inula/src/event/InulaEventMain.ts +++ b/packages/inula/src/event/InulaEventMain.ts @@ -142,7 +142,13 @@ function triggerInulaEvents( const target = nativeEvent.target || nativeEvent.srcElement!; // 触发普通委托事件 - const listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture); + const listenerList: ListenerUnitList = getCommonListeners( + nativeEvtName, + vNode, + nativeEvent as MouseEvent, + target, + isCapture + ); let mouseEnterListeners: ListenerUnitList = []; if (inulaEventToNativeMap.get('onMouseEnter')!.includes(nativeEvtName)) { diff --git a/packages/inula/src/inulax/adapters/redux.ts b/packages/inula/src/inulax/adapters/redux.ts index 97ee92f1..99e57748 100644 --- a/packages/inula/src/inulax/adapters/redux.ts +++ b/packages/inula/src/inulax/adapters/redux.ts @@ -91,6 +91,10 @@ function mergeData(state, data) { } export function createStore(reducer: Reducer, preloadedState?: any, enhancers?: StoreEnhancer): ReduxStoreHandler { + if (typeof preloadedState === 'function' && typeof enhancers === 'undefined') { + enhancers = preloadedState; + preloadedState = undefined; + } const store = createStoreX({ id: 'defaultStore', state: { stateWrapper: preloadedState }, @@ -107,6 +111,7 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?: return; } // NOTE: reducer should never return undefined, in this case, do not change state state.stateWrapper = result; + return action; }, }, options: { diff --git a/packages/inula/src/renderer/diff/nodeDiffComparator.ts b/packages/inula/src/renderer/diff/nodeDiffComparator.ts index 8de6f392..e7161174 100644 --- a/packages/inula/src/renderer/diff/nodeDiffComparator.ts +++ b/packages/inula/src/renderer/diff/nodeDiffComparator.ts @@ -15,7 +15,7 @@ import type { VNode } from '../Types'; import { FlagUtils } from '../vnode/VNodeFlags'; -import { TYPE_COMMON_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL } from '../../external/JSXElementType'; +import { TYPE_COMMON_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL, TYPE_STRICT_MODE } from '../../external/JSXElementType'; import { DomText, DomPortal, Fragment, DomComponent } from '../vnode/VNodeTags'; import { updateVNode, @@ -35,9 +35,9 @@ enum DiffCategory { ARR_NODE = 'ARR_NODE', } -// 检查是不是被 FRAGMENT 包裹 -function isNoKeyFragment(child: any) { - return child != null && child.type === TYPE_FRAGMENT && child.key === null; +// 检查是不是被 FRAGMENT 或 StrictMode 包裹 +function isNoKeyFragmentOrStrictMode(child: any) { + return child != null && (child.type === TYPE_FRAGMENT || child.type === TYPE_STRICT_MODE) && child.key === null; } // 清除单个节点 @@ -159,7 +159,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { const key = oldNode !== null ? oldNode.key : newChild.key; resultNode = createFragmentVNode(key, newChild.props.children); } else { - resultNode = updateVNode(oldNode, newChild); + resultNode = updateVNode(oldNode, newChild.props.children); } break; } @@ -631,7 +631,7 @@ export function createChildrenByDiff( newChild: any, isComparing: boolean ): VNode | null { - const isFragment = isNoKeyFragment(newChild); + const isFragment = isNoKeyFragmentOrStrictMode(newChild); newChild = isFragment ? newChild.props.children : newChild; // 1. 没有新节点,直接把vNode标记为删除 diff --git a/packages/inula/src/renderer/hooks/HookMain.ts b/packages/inula/src/renderer/hooks/HookMain.ts index c2943f49..48f39f42 100644 --- a/packages/inula/src/renderer/hooks/HookMain.ts +++ b/packages/inula/src/renderer/hooks/HookMain.ts @@ -18,19 +18,12 @@ import type { VNode } from '../Types'; import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook'; import { HookStage, setHookStage } from './HookStage'; -const NESTED_UPDATE_LIMIT = 50; -// state updated in render phrase -let hasUpdatedInRender = false; function resetGlobalVariable() { setHookStage(null); setLastTimeHook(null); setCurrentHook(null); } -export function markUpdatedInRender() { - hasUpdatedInRender = true; -} - // hook对外入口 export function runFunctionWithHooks, Arg>( funcComp: (props: Props, arg: Arg) => any, @@ -52,14 +45,8 @@ export function runFunctionWithHooks, Arg>( setHookStage(HookStage.Update); } - let comp = funcComp(props, arg); + const comp = funcComp(props, arg); - if (hasUpdatedInRender) { - resetGlobalVariable(); - processing.oldHooks = processing.hooks; - setHookStage(HookStage.Update); - comp = runFunctionAgain(funcComp, props, arg); - } // 设置hook阶段为null,用于判断hook是否在函数组件中调用 setHookStage(null); @@ -76,22 +63,3 @@ export function runFunctionWithHooks, Arg>( return comp; } - -function runFunctionAgain, Arg>( - funcComp: (props: Props, arg: Arg) => any, - props: Props, - arg: Arg -) { - let reRenderTimes = 0; - let childElements; - while (hasUpdatedInRender) { - reRenderTimes++; - if (reRenderTimes > NESTED_UPDATE_LIMIT) { - throw new Error('Too many setState called in function component'); - } - hasUpdatedInRender = false; - childElements = funcComp(props, arg); - } - - return childElements; -} diff --git a/packages/inula/src/renderer/hooks/UseReducerHook.ts b/packages/inula/src/renderer/hooks/UseReducerHook.ts index e0ae2f3a..190d185f 100644 --- a/packages/inula/src/renderer/hooks/UseReducerHook.ts +++ b/packages/inula/src/renderer/hooks/UseReducerHook.ts @@ -21,7 +21,6 @@ import { setStateChange } from '../render/FunctionComponent'; import { getHookStage, HookStage } from './HookStage'; import type { VNode } from '../Types'; import { getProcessingVNode } from '../GlobalVar'; -import { markUpdatedInRender } from './HookMain'; // 构造新的Update数组 function insertUpdate(action: A, hook: Hook): Update {