重构了subscribeFeature的写法

This commit is contained in:
Xu Chang 2023-12-01 12:32:52 +08:00
parent fc8455d2eb
commit c7daaa2c2e
26 changed files with 324 additions and 205 deletions

View File

@ -293,6 +293,5 @@ export declare class RunningTree<ED extends EntityDict & BaseEntityDict, Cxt ext
}>;
clean(path: string): void;
getRoot(): Record<string, SingleNode<ED, keyof ED, Cxt, FrontCxt, AD> | ListNode<ED, keyof ED, Cxt, FrontCxt, AD> | VirtualNode<ED, Cxt, FrontCxt, AD>>;
subscribeNode(callback: (path: string) => any, path: string): () => void;
}
export {};

View File

@ -1470,6 +1470,9 @@ export class RunningTree extends Feature {
assert(!parent && !this.root[path]);
this.root[path] = node;
}
node.subscribe(() => {
this.publish(fullPath);
});
return node;
}
checkSingleNodeIsModiNode(node) {
@ -1537,6 +1540,7 @@ export class RunningTree extends Feature {
unset(this.root, path);
}
node.destroy();
node.clearSubscribes();
}
}
getFreshValue(path) {
@ -1840,17 +1844,4 @@ export class RunningTree extends Feature {
getRoot() {
return this.root;
}
subscribeNode(callback, path) {
const node = this.findNode(path);
/**
* 当list上的结点更新路径时会重复subscribe多条子路径结点目前的数据结构不能支持在didUpdate的时候进行unsbscribe
* 这里先将path返回由结点自主判定是否需要reRender
* by Xc 20230219
* 原先用的clearSubscribes是假设没有结点共用路径目前看来这个假设不成立
*/
// node.clearSubscribes();
return node.subscribe(() => {
callback(path);
});
}
}

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

@ -4,7 +4,9 @@ import { OakComponentOption, ComponentFullThisType, OakComponentData } from './t
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { MessageProps } from './types/Message';
export declare function onPathSet<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>): Partial<OakComponentData<ED, T>>;
export declare function onPathSet<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt> & {
addFeatureSub: (name: string, callback: (args?: any) => void) => void;
}, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>): Partial<OakComponentData<ED, T>>;
export declare function reRender<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>, extra?: Record<string, any>): void;
export declare function refresh<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>): Promise<void>;
export declare function loadMore<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>): Promise<void>;

View File

@ -79,12 +79,12 @@ export function onPathSet(option) {
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
});
this.subscribed.push(features.runningTree.subscribeNode((path2) => {
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
}, oakPath2));
});
// 确保SetState生效这里改成异步
return {
oakEntity: entity2,
@ -96,11 +96,12 @@ export function onPathSet(option) {
features.runningTree.createNode({
path: oakPath2,
});
this.subscribed.push(features.runningTree.subscribeNode((path2) => {
if (path2 === this.state.oakFullpath) {
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
}, oakPath2));
});
return {
oakFullpath: oakPath2,
};

View File

@ -1,7 +1,7 @@
/// <reference path="../node_modules/@types/wechat-miniprogram/index.d.ts" />
import { assert } from 'oak-domain/lib/utils/assert';
import { onPathSet, reRender, refresh, loadMore, execute, destroyNode, } from './page.common';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
import { cloneDeep, pull } from 'oak-domain/lib/utils/lodash';
const OakProperties = {
oakId: '',
oakPath: '',
@ -25,9 +25,6 @@ const oakBehavior = Behavior({
t(key, params) {
return this.features.locales.t(key, params);
},
unsubscribeAll() {
this.subscribed.forEach((ele) => ele());
},
iAmThePage() {
const pages = getCurrentPages();
if (pages[pages.length - 1] === this) {
@ -601,6 +598,32 @@ export function createComponent(option, features) {
: this.loadMore());
}
},
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
},
removeFeatureSub(name, callback) {
const f = this.featuresSubscribed.find(ele => ele.callback === callback && ele.name === name);
pull(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
},
unsubscribeAll() {
this.featuresSubscribed.forEach(ele => {
assert(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
});
},
subscribedAll() {
this.featuresSubscribed.forEach(ele => {
assert(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
});
},
...restMethods,
},
observers,
@ -609,10 +632,12 @@ export function createComponent(option, features) {
const { show } = this.oakOption.lifetimes || {};
this.reRender();
show && show.call(this);
this.subscribedAll();
},
hide() {
const { hide } = this.oakOption.lifetimes || {};
hide && hide.call(this);
this.unsubscribedAll();
},
resize(size) {
const { resize } = this.oakOption.lifetimes || {};
@ -635,7 +660,7 @@ export function createComponent(option, features) {
};
this.oakOption = option;
this.features = features;
this.subscribed = [];
this.featuresSubscribed = [];
created && created.call(this);
},
attached() {
@ -668,19 +693,19 @@ export function createComponent(option, features) {
}
}
this.umounted = false;
this.subscribed.push(features.locales.subscribe(() => this.reRender()));
this.addFeatureSub('locales', () => this.reRender());
if (option.entity) {
this.subscribed.push(features.cache.subscribe(() => this.reRender()));
this.addFeatureSub('cache', () => this.reRender());
}
if (option.features) {
option.features.forEach((ele) => {
if (typeof ele === 'string') {
this.subscribed.push(features[ele].subscribe(() => this.reRender()));
this.addFeatureSub(ele, () => this.reRender());
}
else {
assert(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(features[feature].subscribe(() => {
this.addFeatureSub(feature, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
@ -692,7 +717,7 @@ export function createComponent(option, features) {
return;
}
}
}));
});
}
});
}

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

@ -25,8 +25,15 @@ 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;
subscribed: (() => void)[];
featuresSubscribed: {
name: string;
callback: (args?: any) => void;
unsubHandler?: (() => void) | undefined;
}[];
addFeatureSub(name: string, callback: (args?: any) => void): void;
removeFeatureSub(name: string, callback: (args?: any) => void): void;
unsubscribeAll(): void;
subscribedAll(): void;
subEvent(type: string, callback: Function): void;
unsubEvent(type: string, callback: Function): void;
pubEvent(type: string, options?: any): void;

View File

@ -1,11 +1,34 @@
import { assert } from 'oak-domain/lib/utils/assert';
import React from 'react';
import { get } from 'oak-domain/lib/utils/lodash';
import { get, pull } from 'oak-domain/lib/utils/lodash';
import { onPathSet, reRender, refresh, loadMore, execute, destroyNode, } from './page.common';
class OakComponentBase extends React.PureComponent {
subscribed = [];
featuresSubscribed = [];
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
}
removeFeatureSub(name, callback) {
const f = this.featuresSubscribed.find(ele => ele.callback === callback && ele.name === name);
pull(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
}
unsubscribeAll() {
this.subscribed.forEach(ele => ele());
this.featuresSubscribed.forEach(ele => {
assert(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
});
}
subscribedAll() {
this.featuresSubscribed.forEach(ele => {
assert(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
});
}
subEvent(type, callback) {
this.features.eventBus.sub(type, callback);
@ -519,9 +542,9 @@ export function createComponent(option, features) {
!oakDisablePulldownRefresh);
}
async componentDidMount() {
this.subscribed.push(features.locales.subscribe(() => this.reRender()));
this.addFeatureSub('locale', () => this.reRender);
if (option.entity) {
this.subscribed.push(features.cache.subscribe(() => this.reRender()));
this.addFeatureSub('cache', () => this.reRender());
}
lifetimes?.attached && lifetimes.attached.call(this);
const { oakPath } = this.props;
@ -551,12 +574,12 @@ export function createComponent(option, features) {
if (option.features) {
option.features.forEach(ele => {
if (typeof ele === 'string') {
this.subscribed.push(features[ele].subscribe(() => this.reRender()));
this.addFeatureSub(ele, () => this.reRender());
}
else {
assert(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(features[feature].subscribe(() => {
this.addFeatureSub(feature, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
@ -568,13 +591,13 @@ export function createComponent(option, features) {
return;
}
}
}));
});
}
});
}
}
componentWillUnmount() {
this.subscribed.forEach(ele => ele());
this.unsubscribeAll();
this.state.oakFullpath && (this.iAmThePage() || this.props.oakAutoUnmount) && destroyNode.call(this);
lifetimes?.detached && lifetimes.detached.call(this);
this.unmounted = true;

View File

@ -1,8 +1,8 @@
export declare abstract class Feature {
callbacks: Array<() => any>;
callbacks: Array<(arg?: any) => any>;
constructor();
subscribe(callback: () => any): () => void;
protected publish(): void;
subscribe(callback: (arg?: any) => any): () => void;
protected publish(arg?: any): void;
clearSubscribes(): void;
}
/**

View File

@ -12,8 +12,8 @@ export class Feature {
pull(this.callbacks, callback);
};
}
publish() {
this.callbacks.forEach(ele => ele());
publish(arg) {
this.callbacks.forEach(ele => ele(arg));
}
clearSubscribes() {
this.callbacks = [];

3
es/types/Page.d.ts vendored
View File

@ -82,7 +82,6 @@ export type ReactComponentProps<ED extends EntityDict & BaseEntityDict, T extend
};
export type ComponentData<ED extends EntityDict & BaseEntityDict, T extends keyof ED, FormedData extends DataOption, TData extends DataOption> = TData & FormedData & OakComponentData<ED, T>;
export type ComponentPublicThisType<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, IsList extends boolean, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends MethodOption = {}, EMethod extends Record<string, Function> = {}> = {
subscribed: Array<() => void>;
features: FD & BasicFeatures<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;
state: ComponentData<ED, T, FormedData, TData>;
props: Readonly<ComponentProps<ED, T, IsList, TProperty>>;
@ -90,7 +89,6 @@ export type ComponentPublicThisType<ED extends EntityDict & BaseEntityDict, T ex
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
} & TMethod & EMethod & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
export type ComponentFullThisType<ED extends EntityDict & BaseEntityDict, T extends keyof ED, IsList extends boolean, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> = {
subscribed: Array<() => void>;
features: BasicFeatures<ED, Cxt, FrontCxt, CommonAspectDict<ED, Cxt>>;
state: OakComponentData<ED, T>;
props: ComponentProps<ED, T, IsList, {}>;
@ -135,7 +133,6 @@ export type OakNavigateToParameters<ED extends EntityDict & BaseEntityDict, T ex
[k: string]: any;
};
export type OakCommonComponentMethods<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
unsubscribeAll: () => void;
subEvent: (type: string, callback: Function) => void;
unsubEvent: (type: string, callback: Function) => void;
pubEvent: (type: string, options?: any) => void;

View File

@ -293,6 +293,5 @@ export declare class RunningTree<ED extends EntityDict & BaseEntityDict, Cxt ext
}>;
clean(path: string): void;
getRoot(): Record<string, SingleNode<ED, keyof ED, Cxt, FrontCxt, AD> | ListNode<ED, keyof ED, Cxt, FrontCxt, AD> | VirtualNode<ED, Cxt, FrontCxt, AD>>;
subscribeNode(callback: (path: string) => any, path: string): () => void;
}
export {};

View File

@ -1473,6 +1473,9 @@ class RunningTree extends Feature_1.Feature {
(0, assert_1.assert)(!parent && !this.root[path]);
this.root[path] = node;
}
node.subscribe(() => {
this.publish(fullPath);
});
return node;
}
checkSingleNodeIsModiNode(node) {
@ -1540,6 +1543,7 @@ class RunningTree extends Feature_1.Feature {
(0, lodash_1.unset)(this.root, path);
}
node.destroy();
node.clearSubscribes();
}
}
getFreshValue(path) {
@ -1843,18 +1847,5 @@ class RunningTree extends Feature_1.Feature {
getRoot() {
return this.root;
}
subscribeNode(callback, path) {
const node = this.findNode(path);
/**
* 当list上的结点更新路径时会重复subscribe多条子路径结点目前的数据结构不能支持在didUpdate的时候进行unsbscribe
* 这里先将path返回由结点自主判定是否需要reRender
* by Xc 20230219
* 原先用的clearSubscribes是假设没有结点共用路径目前看来这个假设不成立
*/
// node.clearSubscribes();
return node.subscribe(() => {
callback(path);
});
}
}
exports.RunningTree = RunningTree;

View File

@ -4,7 +4,9 @@ import { OakComponentOption, ComponentFullThisType, OakComponentData } from './t
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { MessageProps } from './types/Message';
export declare function onPathSet<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>): Partial<OakComponentData<ED, T>>;
export declare function onPathSet<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt> & {
addFeatureSub: (name: string, callback: (args?: any) => void) => void;
}, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>): Partial<OakComponentData<ED, T>>;
export declare function reRender<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>, option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>, extra?: Record<string, any>): void;
export declare function refresh<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>): Promise<void>;
export declare function loadMore<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>>(this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>): Promise<void>;

View File

@ -82,12 +82,12 @@ function onPathSet(option) {
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
});
this.subscribed.push(features.runningTree.subscribeNode((path2) => {
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
}, oakPath2));
});
// 确保SetState生效这里改成异步
return {
oakEntity: entity2,
@ -99,11 +99,12 @@ function onPathSet(option) {
features.runningTree.createNode({
path: oakPath2,
});
this.subscribed.push(features.runningTree.subscribeNode((path2) => {
if (path2 === this.state.oakFullpath) {
this.addFeatureSub('runningTree', (path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
}, oakPath2));
});
return {
oakFullpath: oakPath2,
};

View File

@ -28,9 +28,6 @@ const oakBehavior = Behavior({
t(key, params) {
return this.features.locales.t(key, params);
},
unsubscribeAll() {
this.subscribed.forEach((ele) => ele());
},
iAmThePage() {
const pages = getCurrentPages();
if (pages[pages.length - 1] === this) {
@ -604,6 +601,32 @@ function createComponent(option, features) {
: this.loadMore());
}
},
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
},
removeFeatureSub(name, callback) {
const f = this.featuresSubscribed.find(ele => ele.callback === callback && ele.name === name);
(0, lodash_1.pull)(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
},
unsubscribeAll() {
this.featuresSubscribed.forEach(ele => {
(0, assert_1.assert)(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
});
},
subscribedAll() {
this.featuresSubscribed.forEach(ele => {
(0, assert_1.assert)(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
});
},
...restMethods,
},
observers,
@ -612,10 +635,12 @@ function createComponent(option, features) {
const { show } = this.oakOption.lifetimes || {};
this.reRender();
show && show.call(this);
this.subscribedAll();
},
hide() {
const { hide } = this.oakOption.lifetimes || {};
hide && hide.call(this);
this.unsubscribedAll();
},
resize(size) {
const { resize } = this.oakOption.lifetimes || {};
@ -638,7 +663,7 @@ function createComponent(option, features) {
};
this.oakOption = option;
this.features = features;
this.subscribed = [];
this.featuresSubscribed = [];
created && created.call(this);
},
attached() {
@ -671,19 +696,19 @@ function createComponent(option, features) {
}
}
this.umounted = false;
this.subscribed.push(features.locales.subscribe(() => this.reRender()));
this.addFeatureSub('locales', () => this.reRender());
if (option.entity) {
this.subscribed.push(features.cache.subscribe(() => this.reRender()));
this.addFeatureSub('cache', () => this.reRender());
}
if (option.features) {
option.features.forEach((ele) => {
if (typeof ele === 'string') {
this.subscribed.push(features[ele].subscribe(() => this.reRender()));
this.addFeatureSub(ele, () => this.reRender());
}
else {
(0, assert_1.assert)(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(features[feature].subscribe(() => {
this.addFeatureSub(feature, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
@ -695,7 +720,7 @@ function createComponent(option, features) {
return;
}
}
}));
});
}
});
}

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

@ -25,8 +25,15 @@ 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;
subscribed: (() => void)[];
featuresSubscribed: {
name: string;
callback: (args?: any) => void;
unsubHandler?: (() => void) | undefined;
}[];
addFeatureSub(name: string, callback: (args?: any) => void): void;
removeFeatureSub(name: string, callback: (args?: any) => void): void;
unsubscribeAll(): void;
subscribedAll(): void;
subEvent(type: string, callback: Function): void;
unsubEvent(type: string, callback: Function): void;
pubEvent(type: string, options?: any): void;

View File

@ -8,9 +8,32 @@ const react_1 = tslib_1.__importDefault(require("react"));
const lodash_1 = require("oak-domain/lib/utils/lodash");
const page_common_1 = require("./page.common");
class OakComponentBase extends react_1.default.PureComponent {
subscribed = [];
featuresSubscribed = [];
addFeatureSub(name, callback) {
const unsubHandler = this.features[name].subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
}
removeFeatureSub(name, callback) {
const f = this.featuresSubscribed.find(ele => ele.callback === callback && ele.name === name);
(0, lodash_1.pull)(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
}
unsubscribeAll() {
this.subscribed.forEach(ele => ele());
this.featuresSubscribed.forEach(ele => {
(0, assert_1.assert)(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
});
}
subscribedAll() {
this.featuresSubscribed.forEach(ele => {
(0, assert_1.assert)(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
});
}
subEvent(type, callback) {
this.features.eventBus.sub(type, callback);
@ -524,9 +547,9 @@ function createComponent(option, features) {
!oakDisablePulldownRefresh);
}
async componentDidMount() {
this.subscribed.push(features.locales.subscribe(() => this.reRender()));
this.addFeatureSub('locale', () => this.reRender);
if (option.entity) {
this.subscribed.push(features.cache.subscribe(() => this.reRender()));
this.addFeatureSub('cache', () => this.reRender());
}
lifetimes?.attached && lifetimes.attached.call(this);
const { oakPath } = this.props;
@ -556,12 +579,12 @@ function createComponent(option, features) {
if (option.features) {
option.features.forEach(ele => {
if (typeof ele === 'string') {
this.subscribed.push(features[ele].subscribe(() => this.reRender()));
this.addFeatureSub(ele, () => this.reRender());
}
else {
(0, assert_1.assert)(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(features[feature].subscribe(() => {
this.addFeatureSub(feature, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
@ -573,13 +596,13 @@ function createComponent(option, features) {
return;
}
}
}));
});
}
});
}
}
componentWillUnmount() {
this.subscribed.forEach(ele => ele());
this.unsubscribeAll();
this.state.oakFullpath && (this.iAmThePage() || this.props.oakAutoUnmount) && page_common_1.destroyNode.call(this);
lifetimes?.detached && lifetimes.detached.call(this);
this.unmounted = true;

View File

@ -1,8 +1,8 @@
export declare abstract class Feature {
callbacks: Array<() => any>;
callbacks: Array<(arg?: any) => any>;
constructor();
subscribe(callback: () => any): () => void;
protected publish(): void;
subscribe(callback: (arg?: any) => any): () => void;
protected publish(arg?: any): void;
clearSubscribes(): void;
}
/**

View File

@ -15,8 +15,8 @@ class Feature {
(0, lodash_1.pull)(this.callbacks, callback);
};
}
publish() {
this.callbacks.forEach(ele => ele());
publish(arg) {
this.callbacks.forEach(ele => ele(arg));
}
clearSubscribes() {
this.callbacks = [];

3
lib/types/Page.d.ts vendored
View File

@ -82,7 +82,6 @@ export type ReactComponentProps<ED extends EntityDict & BaseEntityDict, T extend
};
export type ComponentData<ED extends EntityDict & BaseEntityDict, T extends keyof ED, FormedData extends DataOption, TData extends DataOption> = TData & FormedData & OakComponentData<ED, T>;
export type ComponentPublicThisType<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, IsList extends boolean, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends MethodOption = {}, EMethod extends Record<string, Function> = {}> = {
subscribed: Array<() => void>;
features: FD & BasicFeatures<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;
state: ComponentData<ED, T, FormedData, TData>;
props: Readonly<ComponentProps<ED, T, IsList, TProperty>>;
@ -90,7 +89,6 @@ export type ComponentPublicThisType<ED extends EntityDict & BaseEntityDict, T ex
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
} & TMethod & EMethod & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
export type ComponentFullThisType<ED extends EntityDict & BaseEntityDict, T extends keyof ED, IsList extends boolean, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> = {
subscribed: Array<() => void>;
features: BasicFeatures<ED, Cxt, FrontCxt, CommonAspectDict<ED, Cxt>>;
state: OakComponentData<ED, T>;
props: ComponentProps<ED, T, IsList, {}>;
@ -135,7 +133,6 @@ export type OakNavigateToParameters<ED extends EntityDict & BaseEntityDict, T ex
[k: string]: any;
};
export type OakCommonComponentMethods<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
unsubscribeAll: () => void;
subEvent: (type: string, callback: Function) => void;
unsubEvent: (type: string, callback: Function) => void;
pubEvent: (type: string, options?: any) => void;

View File

@ -1805,6 +1805,9 @@ export class RunningTree<
this.root[path] = node;
}
node.subscribe(() => {
this.publish(fullPath);
})
return node;
}
@ -1875,6 +1878,7 @@ export class RunningTree<
unset(this.root, path);
}
node.destroy();
node.clearSubscribes();
}
}
@ -2287,18 +2291,4 @@ export class RunningTree<
getRoot() {
return this.root;
}
subscribeNode(callback: (path: string) => any, path: string): () => void {
const node = this.findNode(path)!;
/**
* list上的结点更新路径时subscribe多条子路径结点didUpdate的时候进行unsbscribe
* path返回reRender
* by Xc 20230219
* clearSubscribes
*/
// node.clearSubscribes();
return node.subscribe(() => {
callback(path);
});
}
}

View File

@ -30,7 +30,9 @@ export function onPathSet<
T extends keyof ED,
Cxt extends AsyncContext<ED>,
FrontCxt extends SyncContext<ED>>(
this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt>,
this: ComponentFullThisType<ED, T, any, Cxt, FrontCxt> & {
addFeatureSub: (name: string, callback: (args?: any) => void) => void;
},
option: OakComponentOption<any, ED, T, Cxt, FrontCxt, any, any, any, {}, {}, {}>): Partial<OakComponentData<ED, T>> {
const { props, state } = this;
const { oakPath, oakId } = props as ComponentProps<ED, T, true, {}>;
@ -120,17 +122,12 @@ export function onPathSet<
cascadeActions: cascadeActions && (() => cascadeActions.call(this)),
getTotal: getTotal2,
});
this.subscribed.push(
features.runningTree.subscribeNode(
(path2) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
},
oakPath2!
)
);
this.addFeatureSub('runningTree', (path2: string) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
});
// 确保SetState生效这里改成异步
return {
@ -143,16 +140,12 @@ export function onPathSet<
features.runningTree.createNode({
path: oakPath2 as string,
});
this.subscribed.push(
features.runningTree.subscribeNode(
(path2) => {
if (path2 === this.state.oakFullpath) {
this.reRender();
}
},
oakPath2!
)
);
this.addFeatureSub('runningTree', (path2: string) => {
// 父结点改变,子结点要重渲染
if (this.state.oakFullpath?.includes(path2)) {
this.reRender();
}
});
return {
oakFullpath: oakPath2,

View File

@ -24,7 +24,7 @@ import { MessageProps } from './types/Message';
import { NotificationProps } from './types/Notification';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
import { cloneDeep, pull } from 'oak-domain/lib/utils/lodash';
const OakProperties = {
@ -86,7 +86,6 @@ const oakBehavior = Behavior<
ADD & CommonAspectDict<EDD, Cxt>
> &
FDD;
subscribed: Array<() => void>;
oakOption: OakComponentOption<
boolean,
EDD,
@ -107,10 +106,6 @@ const oakBehavior = Behavior<
return this.features.locales.t(key, params);
},
unsubscribeAll() {
this.subscribed.forEach((ele) => ele());
},
iAmThePage() {
const pages = getCurrentPages();
if (pages[pages.length - 1] === (this as any)) {
@ -851,6 +846,11 @@ export function createComponent<
umounted: Boolean;
prevState: Record<string, any>;
state: Record<string, any>;
featuresSubscribed: Array<{
name: string;
callback: () => void;
unsubHandler?: () => void;
}>
props: {
oakId?: string;
oakPath?: string;
@ -867,7 +867,6 @@ export function createComponent<
AD & CommonAspectDict<ED, Cxt>
> &
FD;
subscribed: Array<() => void>;
oakOption: OakComponentOption<
IsList,
ED,
@ -882,7 +881,7 @@ export function createComponent<
TMethod
>;
}
>({
>({
externalClasses,
behaviors: [oakBehavior],
data:
@ -927,6 +926,42 @@ export function createComponent<
}
},
addFeatureSub(name: string, callback: (args?: any) => void) {
const unsubHandler = this.features[name]!.subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
},
removeFeatureSub(name: string, callback: (args?: any) => void) {
const f = this.featuresSubscribed.find(
ele => ele.callback === callback && ele.name === name
)!;
pull(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
},
unsubscribeAll() {
this.featuresSubscribed.forEach(
ele => {
assert(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
}
);
},
subscribedAll() {
this.featuresSubscribed.forEach(
ele => {
assert(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
}
);
},
...restMethods,
},
observers,
@ -935,10 +970,12 @@ export function createComponent<
const { show } = this.oakOption.lifetimes || {};
this.reRender();
show && show.call(this);
this.subscribedAll();
},
hide() {
const { hide } = this.oakOption.lifetimes || {};
hide && hide.call(this);
this.unsubscribedAll();
},
resize(size: WechatMiniprogram.Page.IResizeOption) {
const { resize } = this.oakOption.lifetimes || {};
@ -961,7 +998,7 @@ export function createComponent<
};
this.oakOption = option;
this.features = features;
this.subscribed = [];
this.featuresSubscribed = [];
created && created.call(this);
},
attached() {
@ -993,39 +1030,31 @@ export function createComponent<
}
}
this.umounted = false;
this.subscribed.push(
features.locales.subscribe(() => this.reRender())
);
this.addFeatureSub('locales', () => this.reRender());
if (option.entity) {
this.subscribed.push(
features.cache.subscribe(() => this.reRender())
);
this.addFeatureSub('cache', () => this.reRender());
}
if (option.features) {
option.features.forEach((ele) => {
if (typeof ele === 'string') {
this.subscribed.push(
features[ele].subscribe(() => this.reRender())
);
this.addFeatureSub(ele, () => this.reRender());
} else {
assert(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(
features[feature].subscribe(() => {
switch (behavior) {
case 'reRender': {
this.reRender();
return;
}
default: {
assert(behavior === 'refresh');
this.refresh();
return;
}
this.addFeatureSub(feature, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
return;
}
})
);
default: {
assert(behavior === 'refresh');
this.refresh();
return;
}
}
});
}
});
}

View File

@ -1,6 +1,6 @@
import { assert } from 'oak-domain/lib/utils/assert';
import React from 'react';
import { get } from 'oak-domain/lib/utils/lodash';
import { get, pull } from 'oak-domain/lib/utils/lodash';
import { CommonAspectDict } from 'oak-common-aspect';
import { Action, Aspect, CheckerType, EntityDict, OpRecord, SubDataDef } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
@ -60,11 +60,45 @@ abstract class OakComponentBase<
TProperty,
TMethod
>;
subscribed: Array<() => void> = [];
featuresSubscribed: Array<{
name: string;
callback: (args?: any) => void;
unsubHandler?: () => void;
}> = []
addFeatureSub(name: string, callback: (args?: any) => void) {
const unsubHandler = this.features[name]!.subscribe(callback);
this.featuresSubscribed.push({
name,
callback,
unsubHandler,
});
}
removeFeatureSub(name: string, callback: (args?: any) => void) {
const f = this.featuresSubscribed.find(
ele => ele.callback === callback && ele.name === name
)!;
pull(this.featuresSubscribed, f);
f.unsubHandler && f.unsubHandler();
}
unsubscribeAll() {
this.subscribed.forEach(
ele => ele()
this.featuresSubscribed.forEach(
ele => {
assert(ele.unsubHandler);
ele.unsubHandler();
ele.unsubHandler = undefined;
}
);
}
subscribedAll() {
this.featuresSubscribed.forEach(
ele => {
assert(!ele.unsubHandler);
ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
}
);
}
@ -828,13 +862,9 @@ export function createComponent<
async componentDidMount() {
this.subscribed.push(
features.locales.subscribe(() => this.reRender())
);
this.addFeatureSub('locale', () => this.reRender);
if (option.entity) {
this.subscribed.push(
features.cache.subscribe(() => this.reRender())
);
this.addFeatureSub('cache', () => this.reRender());
}
lifetimes?.attached && lifetimes.attached.call(this);
const { oakPath } = this.props;
@ -867,32 +897,24 @@ export function createComponent<
option.features.forEach(
ele => {
if (typeof ele === 'string') {
this.subscribed.push(
features[ele].subscribe(
() => this.reRender()
)
);
this.addFeatureSub(ele, () => this.reRender());
}
else {
assert(typeof ele === 'object');
const { feature, behavior } = ele;
this.subscribed.push(
features[feature].subscribe(
() => {
switch (behavior) {
case 'reRender': {
this.reRender();
return;
}
default: {
assert(behavior === 'refresh');
this.refresh();
return;
}
}
this.addFeatureSub(feature as string, () => {
switch (behavior) {
case 'reRender': {
this.reRender();
return;
}
)
);
default: {
assert(behavior === 'refresh');
this.refresh();
return;
}
}
});
}
}
);
@ -900,9 +922,7 @@ export function createComponent<
}
componentWillUnmount() {
this.subscribed.forEach(
ele => ele()
);
this.unsubscribeAll();
this.state.oakFullpath && (this.iAmThePage() || this.props.oakAutoUnmount) && destroyNode.call(this as any);
lifetimes?.detached && lifetimes.detached.call(this);
this.unmounted = true;

View File

@ -4,22 +4,22 @@ const mCallbacks: Array<() => any> = [];
let mActionStackDepth = 0;
export abstract class Feature {
callbacks: Array<() => any>;
callbacks: Array<(arg?: any) => any>;
constructor() {
this.callbacks = [];
}
subscribe(callback: () => any) {
subscribe(callback: (arg?: any) => any) {
this.callbacks.push(callback);
return () => {
pull(this.callbacks, callback);
};
}
protected publish() {
protected publish(arg?: any) {
this.callbacks.forEach(
ele => ele()
ele => ele(arg)
);
}

View File

@ -218,8 +218,7 @@ export type ComponentPublicThisType<
TProperty extends DataOption = {},
TMethod extends MethodOption = {},
EMethod extends Record<string, Function> = {},
> = {
subscribed: Array<() => void>;
> = {
features: FD & BasicFeatures<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;
state: ComponentData<ED, T, FormedData, TData>;
props: Readonly<ComponentProps<ED, T, IsList, TProperty>>;
@ -241,7 +240,6 @@ export type ComponentFullThisType<
Cxt extends AsyncContext<ED>,
FrontCxt extends SyncContext<ED>
> = {
subscribed: Array<() => void>;
features: BasicFeatures<ED, Cxt, FrontCxt, CommonAspectDict<ED, Cxt>>;
state: OakComponentData<ED, T>;
props: ComponentProps<ED, T, IsList, {}>;
@ -324,8 +322,7 @@ export type OakCommonComponentMethods<
ED extends EntityDict & BaseEntityDict,
T extends keyof ED
> = {
unsubscribeAll: () => void;
// 这几个也待删除
subEvent: (type: string, callback: Function) => void;
unsubEvent: (type: string, callback: Function) => void;
pubEvent: (type: string, options?: any) => void;