fix(runtime): fix #590, use an init set to check first time render

This patch use a more solid way to check init state setup, instead of render count.
This commit is contained in:
Yanzhen Yu 2022-09-04 22:55:29 +08:00
parent 8e59b852ec
commit c84d94a89a
4 changed files with 31 additions and 15 deletions

View File

@ -7,7 +7,7 @@ import { useRuntimeFunctions } from './hooks/useRuntimeFunctions';
import { getSlotElements } from './hooks/useSlotChildren';
import { useGlobalHandlerMap } from './hooks/useGlobalHandlerMap';
import { useEleRef } from './hooks/useEleMap';
import { initStateAndMethod } from '../../../utils/initStateAndMethod';
import { initSingleComponentState } from '../../../utils/initStateAndMethod';
import ComponentErrorBoundary from '../ComponentErrorBoundary';
export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps>(
@ -23,7 +23,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
// This code is to init dynamic generated components
// because they have not be initialized before
if (!stateManager.store[c.id]) {
initStateAndMethod(registry, stateManager, [c]);
initSingleComponentState(registry, stateManager, c);
}
const { eleRef, onRef, onRecoverFromError } = useEleRef(props);

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { RuntimeTraitSchema } from '@sunmao-ui/core';
import { ImplWrapperMain } from './ImplWrapperMain';
import { useRuntimeFunctions } from './hooks/useRuntimeFunctions';
@ -12,8 +12,6 @@ export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperPr
const { component: c, services, slotContext } = props;
const { stateManager, registry } = services;
const { executeTrait } = useRuntimeFunctions(props);
const renderCount = useRef(0);
renderCount.current++;
const slotKey = slotContext?.slotKey || '';
@ -39,9 +37,13 @@ export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperPr
const traitChangeCallback = useCallback(
(trait: RuntimeTraitSchema, properties: Record<string, unknown>) => {
const result = executeTrait(trait, properties);
const prevIsHidden = isHidden;
setIsHidden(!!result.unmount);
if (result.unmount) {
const isLastRender = slotContext ? slotContext.renderSet.size === 0 : true;
if (result.unmount && isLastRender) {
// Every component's state is initialized in initStateAnd Method.
// So if if it should not render, we should remove it from store.
@ -49,19 +51,17 @@ export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperPr
* prevIsHidden: Only clear the store when the component is not
* hidden before this check.
*
* renderCount: Currently we call initStateAndMethod to setup the
* state store, and let here to teardown the hidden components'
* state. If a component is hidden forever, it still need to teardown
* at the first time it rendered.
* Not a perfect solution, and we should have better lifecycle
* management for the state store.
* initSet: when init set has the component's id, it means there
* is an initial state setup by us. If a component is hidden
* forever, it still need to teardown at the first time it rendered.
*/
if (!prevIsHidden || renderCount.current === 1) {
if (!prevIsHidden || stateManager.initSet.has(c.id)) {
delete stateManager.store[c.id];
stateManager.initSet.delete(c.id);
}
}
},
[c.id, executeTrait, stateManager, isHidden]
[c.id, executeTrait, stateManager, isHidden, slotContext]
);
useEffect(() => {

View File

@ -45,6 +45,18 @@ export class StateManager {
store = reactive<Record<string, any>>({});
slotStore = reactive<Record<string, any>>({});
/**
* In `initStateAndMethod`, we setup all components' state with a
* default value.
*
* If some components were unmounted later, the `UnmountImplWrapper`
* will teardown the state.
*
* So we provide this flag set for the `UnmountImplWrapper`, let
* it know whether the component's state is setup by the init process.
*/
initSet = new Set<string>();
dependencies: Record<string, unknown>;
mute = true;

View File

@ -15,7 +15,10 @@ export function initStateAndMethod(
stateManager: StateManagerInterface,
components: RuntimeComponentSchema[]
) {
components.forEach(c => initSingleComponentState(registry, stateManager, c));
components.forEach(c => {
stateManager.initSet.add(c.id);
initSingleComponentState(registry, stateManager, c);
});
}
export function initSingleComponentState(
@ -26,6 +29,7 @@ export function initSingleComponentState(
if (stateManager.store[c.id]) {
return false;
}
let state = {};
c.traits.forEach(t => {
const tSpec = registry.getTrait(t.parsedType.version, t.parsedType.name).spec;