重新处理了didMount和unMount顺序不同步的bug

This commit is contained in:
Xu Chang 2024-08-06 14:19:51 +08:00
parent 6b1458891e
commit c9f8d5ad9d
20 changed files with 102 additions and 80 deletions

View File

@ -2,12 +2,12 @@ import { ED, OakAbsAttrDef } from '../../types/AbstractComponent';
import { ReactComponentProps } from '../../types/Page';
import { Breakpoint } from 'antd';
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
column?: number | Record<Breakpoint, number> | undefined;
column?: number | Record<Breakpoint, number>;
entity: T2;
attributes: OakAbsAttrDef[];
data: Partial<ED2[T2]["Schema"]>;
title?: string | undefined;
bordered?: boolean | undefined;
layout?: "horizontal" | "vertical" | undefined;
data: Partial<ED2[T2]['Schema']>;
title?: string;
bordered?: boolean;
layout?: 'horizontal' | 'vertical';
}>) => React.ReactElement;
export default _default;

View File

@ -3,8 +3,8 @@ import { ReactComponentProps } from '../../types/Page';
import { ECode } from '../../types/ErrorPage';
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
code: ECode;
title?: string | undefined;
desc?: string | undefined;
title?: string;
desc?: string;
children?: React.ReactNode;
icon?: React.ReactNode;
}>) => React.ReactElement;

View File

@ -10,18 +10,13 @@ declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactCompo
data: RowWithActions<ED2, T2>[];
loading: boolean;
tablePagination?: React.ReactNode | false;
rowSelection?: import("antd/es/table/interface").TableRowSelection<RowWithActions<ED2, T2>> | undefined;
hideHeader?: boolean | undefined;
disableSerialNumber?: boolean | undefined;
size?: "small" | "large" | "middle" | undefined;
scroll?: ({
x?: string | number | true | undefined;
y?: string | number | undefined;
} & {
scrollToFirstRowOnChange?: boolean | undefined;
}) | undefined;
rowSelection?: TableProps<RowWithActions<ED2, T2>>['rowSelection'];
hideHeader?: boolean;
disableSerialNumber?: boolean;
size?: 'large' | 'middle' | 'small';
scroll?: TableProps<RowWithActions<ED2, T2>>['scroll'];
empty?: React.ReactNode;
opWidth?: number | undefined;
ellipsis?: boolean | undefined;
opWidth?: number;
ellipsis?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -1,21 +1,20 @@
/// <reference types="react" />
import { ReactComponentProps } from '../../types/Page';
import { ED } from '../../types/AbstractComponent';
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
style?: import("react").CSSProperties | undefined;
className?: string | undefined;
style?: React.CSSProperties;
className?: string;
title?: React.ReactNode;
showBack?: boolean | undefined;
onBack?: (() => void) | undefined;
showBack?: boolean;
onBack?: () => void;
backIcon?: React.ReactNode;
delta?: number | undefined;
delta?: number;
extra?: React.ReactNode;
subTitle?: React.ReactNode;
contentMargin?: boolean | undefined;
contentStyle?: import("react").CSSProperties | undefined;
contentClassName?: string | undefined;
contentMargin?: boolean;
contentStyle?: React.CSSProperties;
contentClassName?: string;
tags?: React.ReactNode;
children?: React.ReactNode;
showHeader?: boolean | undefined;
showHeader?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -1,23 +1,22 @@
/// <reference types="react" />
import { ReactComponentProps } from '../../types/Page';
import { ED } from '../../types/AbstractComponent';
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
style?: import("react").CSSProperties | undefined;
className?: string | undefined;
showHeader?: boolean | undefined;
showBack?: boolean | undefined;
onBack?: (() => void) | undefined;
style?: React.CSSProperties;
className?: string;
showHeader?: boolean;
showBack?: boolean;
onBack?: () => void;
backIcon?: React.ReactNode;
delta?: number | undefined;
delta?: number;
title?: React.ReactNode;
subTitle?: React.ReactNode;
tags?: React.ReactNode;
extra?: React.ReactNode;
children?: React.ReactNode;
content: React.ReactNode;
contentStyle?: import("react").CSSProperties | undefined;
contentClassName?: string | undefined;
bodyStyle?: import("react").CSSProperties | undefined;
bodyClassName?: string | undefined;
contentStyle?: React.CSSProperties;
contentClassName?: string;
bodyStyle?: React.CSSProperties;
bodyClassName?: string;
}>) => React.ReactElement;
export default _default;

View File

@ -1,13 +1,12 @@
/// <reference types="react" />
import { ReactComponentProps } from '../../types/Page';
import { ED } from '../../types/AbstractComponent';
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
entity: T2;
style?: import("react").CSSProperties | undefined;
className?: string | undefined;
showQuickJumper?: boolean | undefined;
size?: "small" | "default" | undefined;
showSizeChanger?: boolean | undefined;
showTotal?: ((total: number, range: [number, number]) => React.ReactNode) | undefined;
style?: React.CSSProperties;
className?: string;
showQuickJumper?: boolean;
size?: 'default' | 'small';
showSizeChanger?: boolean;
showTotal?: (total: number, range: [number, number]) => React.ReactNode;
}>) => React.ReactElement;
export default _default;

View File

@ -4,7 +4,7 @@ declare const _default: <ED2 extends ED, T2 extends keyof ED2, T3 extends string
helps: Record<string, string>;
entity: T2;
attributes: OakAbsAttrUpsertDef<ED2, T2, T3>[];
data: ED2[T2]["Schema"];
data: ED2[T2]['Schema'];
layout: 'horizontal' | 'vertical';
mode: 'default' | 'card';
}>) => React.ReactElement;

2
es/page.common.d.ts vendored
View File

@ -15,5 +15,5 @@ opers?: Array<{
entity: T;
operation: ED[T]['Operation'];
}>): Promise<void>;
export declare function destroyNode<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, isPage: boolean): void;
export declare function destroyNode<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, isPage: boolean, path: string): void;
export declare function getFreshValue<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, path: string): Partial<ED[keyof ED]["Schema"]> | Partial<ED[keyof ED]["Schema"]>[] | undefined;

View File

@ -584,9 +584,8 @@ opers) {
this.setMessage(messageData);
}
}
export function destroyNode(isPage) {
assert(this.state.oakFullpath);
this.features.runningTree.destroyNode(this.state.oakFullpath, isPage);
export function destroyNode(isPage, path) {
this.features.runningTree.destroyNode(path, isPage);
unset(this.state, ['oakFullpath', 'oakEntity']);
}
export function getFreshValue(path) {

View File

@ -842,7 +842,7 @@ export function createComponent(option, features) {
},
detached() {
this.unsubscribeAll();
this.state.oakFullpath && destroyNode.call(this, this.iAmThePage());
this.state.oakFullpath && destroyNode.call(this, this.iAmThePage(), this.state.oakFullpath);
detached && detached.call(this);
this.oakLifetime = 'detached';
},

15
es/page.react.d.ts vendored
View File

@ -24,6 +24,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
componentWillUnmount(): void;
componentDidUpdate(prevProps: Record<string, any>, prevState: Record<string, any>): Promise<void>;
render(): React.ReactNode;
oakFullpath: string;
featuresSubscribed: {
name: string;
callback: (args?: any) => void;
@ -48,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
redirectTo<T2_1 extends keyof ED>(options: {
url: string;
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
}, path?: string | undefined): string;
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
})[], path?: string | undefined): string[];
removeItem(id: string, path?: string | undefined): void;
removeItems(ids: string[], path?: string | undefined): void;
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
recoverItem(id: string, path?: string | undefined): void;
recoverItems(ids: string[], path?: string | undefined): void;
resetItem(id: string, path?: string | undefined): void;
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
remove(path?: string | undefined): void;
isCreation(path?: string | undefined): boolean;
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
@ -76,7 +77,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];
}[] | undefined;

View File

@ -4,6 +4,7 @@ import { get, pull } from 'oak-domain/lib/utils/lodash';
import { OakException } from 'oak-domain/lib/types';
import { onPathSet, reRender, refresh, loadMore, execute, destroyNode, getFreshValue, } from './page.common';
class OakComponentBase extends React.PureComponent {
oakFullpath = '';
featuresSubscribed = [];
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
@ -634,6 +635,11 @@ export function createComponent(option, features) {
const { oakPath } = this.props;
if (oakPath || path) {
const pathState = onPathSet.call(this, this.oakOption, this.iAmThePage());
/**
* 实际中出现了setState后先调willUnmount析构而callback不被调用的情况此时willUnmount需要将runningTree上的node正确析构
* by Xc 20240806
*/
this.oakFullpath = pathState.oakFullpath;
this.setState(pathState, async () => {
if (this.oakLifetime === 'detached') {
return;
@ -674,7 +680,8 @@ export function createComponent(option, features) {
}
componentWillUnmount() {
this.unsubscribeAll();
this.state.oakFullpath && destroyNode.call(this, this.iAmThePage());
// 这里用this.oakFullpath去析构见上面didMount的注释
this.oakFullpath && destroyNode.call(this, this.iAmThePage(), this.oakFullpath);
lifetimes?.detached && lifetimes.detached.call(this);
this.oakLifetime = 'detached';
}
@ -683,6 +690,9 @@ export function createComponent(option, features) {
// oakPath如果是用变量初始化在这里再执行onPathSet如果有entity的结点在此执行ready
assert(this.props.oakPath && this.oakOption.entity);
const pathState = onPathSet.call(this, this.oakOption, this.iAmThePage());
if (pathState.oakFullpath) {
this.oakFullpath = pathState.oakFullpath;
}
this.setState(pathState, async () => {
if (this.oakLifetime === 'detached') {
return;

View File

@ -15,5 +15,5 @@ opers?: Array<{
entity: T;
operation: ED[T]['Operation'];
}>): Promise<void>;
export declare function destroyNode<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, isPage: boolean): void;
export declare function destroyNode<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, isPage: boolean, path: string): void;
export declare function getFreshValue<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(this: ComponentFullThisType<ED, T>, path: string): Partial<ED[keyof ED]["Schema"]> | Partial<ED[keyof ED]["Schema"]>[] | undefined;

View File

@ -592,9 +592,8 @@ opers) {
}
}
exports.execute = execute;
function destroyNode(isPage) {
(0, assert_1.assert)(this.state.oakFullpath);
this.features.runningTree.destroyNode(this.state.oakFullpath, isPage);
function destroyNode(isPage, path) {
this.features.runningTree.destroyNode(path, isPage);
(0, lodash_1.unset)(this.state, ['oakFullpath', 'oakEntity']);
}
exports.destroyNode = destroyNode;

View File

@ -845,7 +845,7 @@ function createComponent(option, features) {
},
detached() {
this.unsubscribeAll();
this.state.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage());
this.state.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage(), this.state.oakFullpath);
detached && detached.call(this);
this.oakLifetime = 'detached';
},

15
lib/page.react.d.ts vendored
View File

@ -24,6 +24,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
componentWillUnmount(): void;
componentDidUpdate(prevProps: Record<string, any>, prevState: Record<string, any>): Promise<void>;
render(): React.ReactNode;
oakFullpath: string;
featuresSubscribed: {
name: string;
callback: (args?: any) => void;
@ -48,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
redirectTo<T2_1 extends keyof ED>(options: {
url: string;
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
}, path?: string | undefined): string;
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
})[], path?: string | undefined): string[];
removeItem(id: string, path?: string | undefined): void;
removeItems(ids: string[], path?: string | undefined): void;
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
recoverItem(id: string, path?: string | undefined): void;
recoverItems(ids: string[], path?: string | undefined): void;
resetItem(id: string, path?: string | undefined): void;
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
remove(path?: string | undefined): void;
isCreation(path?: string | undefined): boolean;
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
@ -76,7 +77,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];
}[] | undefined;

View File

@ -9,6 +9,7 @@ const lodash_1 = require("oak-domain/lib/utils/lodash");
const types_1 = require("oak-domain/lib/types");
const page_common_1 = require("./page.common");
class OakComponentBase extends react_1.default.PureComponent {
oakFullpath = '';
featuresSubscribed = [];
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
@ -639,6 +640,11 @@ function createComponent(option, features) {
const { oakPath } = this.props;
if (oakPath || path) {
const pathState = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage());
/**
* 实际中出现了setState后先调willUnmount析构而callback不被调用的情况此时willUnmount需要将runningTree上的node正确析构
* by Xc 20240806
*/
this.oakFullpath = pathState.oakFullpath;
this.setState(pathState, async () => {
if (this.oakLifetime === 'detached') {
return;
@ -679,7 +685,8 @@ function createComponent(option, features) {
}
componentWillUnmount() {
this.unsubscribeAll();
this.state.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage());
// 这里用this.oakFullpath去析构见上面didMount的注释
this.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage(), this.oakFullpath);
lifetimes?.detached && lifetimes.detached.call(this);
this.oakLifetime = 'detached';
}
@ -688,6 +695,9 @@ function createComponent(option, features) {
// oakPath如果是用变量初始化在这里再执行onPathSet如果有entity的结点在此执行ready
(0, assert_1.assert)(this.props.oakPath && this.oakOption.entity);
const pathState = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage());
if (pathState.oakFullpath) {
this.oakFullpath = pathState.oakFullpath;
}
this.setState(pathState, async () => {
if (this.oakLifetime === 'detached') {
return;

View File

@ -724,9 +724,8 @@ export async function execute<
export function destroyNode<
ED extends EntityDict & BaseEntityDict,
T extends keyof ED
>(this: ComponentFullThisType<ED, T>, isPage: boolean) {
assert(this.state.oakFullpath);
this.features.runningTree.destroyNode(this.state.oakFullpath, isPage);
>(this: ComponentFullThisType<ED, T>, isPage: boolean, path: string) {
this.features.runningTree.destroyNode(path, isPage);
unset(this.state, ['oakFullpath', 'oakEntity']);
}

View File

@ -1174,7 +1174,7 @@ export function createComponent<
},
detached() {
this.unsubscribeAll();
this.state.oakFullpath && destroyNode.call(this as any, this.iAmThePage());
this.state.oakFullpath && destroyNode.call(this as any, this.iAmThePage(), this.state.oakFullpath);
detached && detached.call(this);
this.oakLifetime = 'detached';
},

View File

@ -44,6 +44,7 @@ abstract class OakComponentBase<
ComponentProps<ED, T, TProperty>,
ComponentData<ED, T, FormedData, TData>
> {
oakFullpath = '';
abstract features: FD &
BasicFeatures<ED>;
abstract oakOption: OakComponentOption<
@ -971,6 +972,11 @@ export function createComponent<
const { oakPath } = this.props;
if (oakPath || path) {
const pathState = onPathSet.call(this as any, this.oakOption as any, this.iAmThePage());
/**
* setState后先调willUnmount析构callback不被调用的情况willUnmount需要将runningTree上的node正确析构
* by Xc 20240806
*/
this.oakFullpath = pathState.oakFullpath!;
this.setState(pathState as any, async () => {
if (this.oakLifetime === 'detached') {
return;
@ -1017,9 +1023,11 @@ export function createComponent<
componentWillUnmount() {
this.unsubscribeAll();
this.state.oakFullpath && destroyNode.call(
// 这里用this.oakFullpath去析构见上面didMount的注释
this.oakFullpath && destroyNode.call(
this as any,
this.iAmThePage()
this.iAmThePage(),
this.oakFullpath
);
lifetimes?.detached && lifetimes.detached.call(this);
this.oakLifetime = 'detached';
@ -1030,6 +1038,9 @@ export function createComponent<
// oakPath如果是用变量初始化在这里再执行onPathSet如果有entity的结点在此执行ready
assert(this.props.oakPath && this.oakOption.entity);
const pathState = onPathSet.call(this as any, this.oakOption as any, this.iAmThePage());
if (pathState.oakFullpath) {
this.oakFullpath = pathState.oakFullpath;
}
this.setState(pathState as any, async () => {
if (this.oakLifetime === 'detached') {
return;