diff --git a/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js b/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js
index 0b5d18a8..ef17a0aa 100644
--- a/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js
+++ b/packages/inula/scripts/__tests__/ComponentTest/FunctionComponent.test.js
@@ -14,6 +14,7 @@
*/
import * as Inula from '../../../src/index';
+const { useState } = Inula;
describe('FunctionComponent Test', () => {
it('渲染无状态组件', () => {
const App = props => {
@@ -89,4 +90,39 @@ 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/src/renderer/hooks/HookMain.ts b/packages/inula/src/renderer/hooks/HookMain.ts
index 48f39f42..c2943f49 100644
--- a/packages/inula/src/renderer/hooks/HookMain.ts
+++ b/packages/inula/src/renderer/hooks/HookMain.ts
@@ -18,12 +18,19 @@ 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,
@@ -45,8 +52,14 @@ export function runFunctionWithHooks, Arg>(
setHookStage(HookStage.Update);
}
- const comp = funcComp(props, arg);
+ let comp = funcComp(props, arg);
+ if (hasUpdatedInRender) {
+ resetGlobalVariable();
+ processing.oldHooks = processing.hooks;
+ setHookStage(HookStage.Update);
+ comp = runFunctionAgain(funcComp, props, arg);
+ }
// 设置hook阶段为null,用于判断hook是否在函数组件中调用
setHookStage(null);
@@ -63,3 +76,22 @@ 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 1278e65e..fe56820b 100644
--- a/packages/inula/src/renderer/hooks/UseReducerHook.ts
+++ b/packages/inula/src/renderer/hooks/UseReducerHook.ts
@@ -21,6 +21,7 @@ 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 {
@@ -64,8 +65,13 @@ export function TriggerAction(vNode: VNode, hook: Hook, isUseState:
}
}
- // 执行vNode节点渲染
- launchUpdateFromVNode(vNode);
+ if (vNode === getProcessingVNode()) {
+ // 绑定的VNode就是当前渲染的VNode时,就是在函数组件体内触发setState
+ markUpdatedInRender();
+ } else {
+ // 执行vNode节点渲染
+ launchUpdateFromVNode(vNode);
+ }
}
export function useReducerForInit(reducer, initArg, init, isUseState?: boolean): [S, Trigger] {