mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
fix: fork model
This commit is contained in:
parent
4d0ebc10a4
commit
aa4ea33698
@ -7,191 +7,209 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, define, observable } from '@formily/reactive';
|
import { action, define, observable } from '@formily/reactive';
|
||||||
import type { IModelComponentProps } from '../types';
|
import type { IModelComponentProps } from '../types';
|
||||||
import { FlowModel } from './flowModel';
|
import { FlowModel } from './flowModel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ForkFlowModel 作为 FlowModel 的轻量代理实例:
|
* ForkFlowModel 作为 FlowModel 的轻量代理实例:
|
||||||
* - 共享 master(原始 FlowModel)上的所有业务数据与方法
|
* - 共享 master(原始 FlowModel)上的所有业务数据与方法
|
||||||
* - 仅在 props 层面拥有本地覆盖(localProps),其余字段全部透传到 master
|
* - 仅在 props 层面拥有本地覆盖(localProps),其余字段全部透传到 master
|
||||||
* - 透传的函数中 this 指向 fork 实例,而非 master,确保正确的上下文
|
* - 透传的函数中 this 指向 fork 实例,而非 master,确保正确的上下文
|
||||||
* - 使用 Object.create 创建临时上下文,确保 this.constructor 指向正确的类(避免异步竞态条件)
|
* - 使用 Object.create 创建临时上下文,确保 this.constructor 指向正确的类(避免异步竞态条件)
|
||||||
* - setter 方法中的 this 也指向 fork 实例,保持一致的上下文行为
|
* - setter 方法中的 this 也指向 fork 实例,保持一致的上下文行为
|
||||||
* - 不会被注册到 FlowEngine.modelInstances 中,保持 uid → master 唯一性假设
|
* - 不会被注册到 FlowEngine.modelInstances 中,保持 uid → master 唯一性假设
|
||||||
*/
|
*/
|
||||||
export class ForkFlowModel<TMaster extends FlowModel = FlowModel> {
|
export class ForkFlowModel<TMaster extends FlowModel = FlowModel> {
|
||||||
/** 与 master 相同的 UID,用于日志调试 */
|
/** 与 master 相同的 UID,用于日志调试 */
|
||||||
public readonly uid: string;
|
public readonly uid: string;
|
||||||
/** 调试标识,便于在日志或断言中快速识别 */
|
/** 调试标识,便于在日志或断言中快速识别 */
|
||||||
public readonly isFork = true;
|
public readonly isFork = true;
|
||||||
|
|
||||||
/** 本地覆盖的 props,fork 层面的 UI/状态 */
|
/** 本地覆盖的 props,fork 层面的 UI/状态 */
|
||||||
public localProps: IModelComponentProps;
|
public localProps: IModelComponentProps;
|
||||||
|
|
||||||
/** master 引用 */
|
/** master 引用 */
|
||||||
private master: TMaster;
|
private master: TMaster;
|
||||||
|
|
||||||
/** 是否已被释放 */
|
/** 是否已被释放 */
|
||||||
private disposed = false;
|
private disposed = false;
|
||||||
|
|
||||||
/** fork 在 master.forks 中的索引 */
|
/** fork 在 master.forks 中的索引 */
|
||||||
public readonly forkId: number;
|
public readonly forkId: number;
|
||||||
|
|
||||||
constructor(master: TMaster, initialProps: IModelComponentProps = {}, forkId = 0) {
|
/** 用于共享上下文的对象,存储跨 fork 的共享数据 */
|
||||||
this.master = master;
|
private _sharedContext: Record<string, any> = {};
|
||||||
this.uid = master.uid;
|
|
||||||
this.localProps = { ...initialProps };
|
constructor(master: TMaster, initialProps: IModelComponentProps = {}, forkId = 0) {
|
||||||
this.forkId = forkId;
|
this.master = master;
|
||||||
|
this.uid = master.uid;
|
||||||
define(this, {
|
this.localProps = { ...initialProps };
|
||||||
localProps: observable,
|
this.forkId = forkId;
|
||||||
setProps: action,
|
|
||||||
});
|
define(this, {
|
||||||
|
localProps: observable,
|
||||||
// 返回代理对象,实现自动透传
|
setProps: action,
|
||||||
return new Proxy(this, {
|
});
|
||||||
get: (target: any, prop: PropertyKey, receiver: any) => {
|
|
||||||
// disposed check
|
// 返回代理对象,实现自动透传
|
||||||
if (prop === 'disposed') return target.disposed;
|
return new Proxy(this, {
|
||||||
|
get: (target: any, prop: PropertyKey, receiver: any) => {
|
||||||
// 特殊处理 constructor,应该返回 master 的 constructor
|
// disposed check
|
||||||
if (prop === 'constructor') {
|
if (prop === 'disposed') return target.disposed;
|
||||||
return target.master.constructor;
|
|
||||||
}
|
// 特殊处理 constructor,应该返回 master 的 constructor
|
||||||
if (prop === 'props') {
|
if (prop === 'constructor') {
|
||||||
// 对 props 做合并返回
|
return target.master.constructor;
|
||||||
return { ...target.master.getProps(), ...target.localProps };
|
}
|
||||||
}
|
if (prop === 'props') {
|
||||||
// fork 自身属性 / 方法优先
|
// 对 props 做合并返回
|
||||||
if (prop in target) {
|
return { ...target.master.getProps(), ...target.localProps };
|
||||||
return Reflect.get(target, prop, receiver);
|
}
|
||||||
}
|
// fork 自身属性 / 方法优先
|
||||||
|
if (prop in target) {
|
||||||
// 默认取 master 上的值
|
return Reflect.get(target, prop, receiver);
|
||||||
const value = (target.master as any)[prop];
|
}
|
||||||
|
|
||||||
// 如果是函数,需要绑定到 fork 实例,让 this 指向 fork
|
// 默认取 master 上的值
|
||||||
// 使用闭包捕获正确的 constructor,避免异步方法中的竞态条件
|
const value = (target.master as any)[prop];
|
||||||
if (typeof value === 'function') {
|
|
||||||
const masterConstructor = target.master.constructor;
|
// 如果是函数,需要绑定到 fork 实例,让 this 指向 fork
|
||||||
return function (this: any, ...args: any[]) {
|
// 使用闭包捕获正确的 constructor,避免异步方法中的竞态条件
|
||||||
// 创建一个临时的 this 对象,包含正确的 constructor
|
if (typeof value === 'function') {
|
||||||
const contextThis = Object.create(this);
|
const masterConstructor = target.master.constructor;
|
||||||
Object.defineProperty(contextThis, 'constructor', {
|
return function (this: any, ...args: any[]) {
|
||||||
value: masterConstructor,
|
// 创建一个临时的 this 对象,包含正确的 constructor
|
||||||
configurable: true,
|
const contextThis = Object.create(this);
|
||||||
enumerable: false,
|
Object.defineProperty(contextThis, 'constructor', {
|
||||||
writable: false,
|
value: masterConstructor,
|
||||||
});
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
return value.apply(contextThis, args);
|
writable: false,
|
||||||
}.bind(receiver);
|
});
|
||||||
}
|
|
||||||
return value;
|
return value.apply(contextThis, args);
|
||||||
},
|
}.bind(receiver);
|
||||||
set: (target: any, prop: PropertyKey, value: any, receiver: any) => {
|
}
|
||||||
if (prop === 'props') {
|
return value;
|
||||||
return true;
|
},
|
||||||
}
|
set: (target: any, prop: PropertyKey, value: any, receiver: any) => {
|
||||||
|
if (prop === 'props') {
|
||||||
// 如果 fork 自带字段,则写到自身(例如 localProps)
|
return true;
|
||||||
if (prop in target) {
|
}
|
||||||
return Reflect.set(target, prop, value, receiver);
|
|
||||||
}
|
// 如果 fork 自带字段,则写到自身(例如 localProps)
|
||||||
|
if (prop in target) {
|
||||||
// 其余写入 master,但需要确保 setter 中的 this 指向 fork
|
return Reflect.set(target, prop, value, receiver);
|
||||||
// 检查 master 上是否有对应的 setter
|
}
|
||||||
const descriptor = this.getPropertyDescriptor(target.master, prop);
|
|
||||||
if (descriptor && descriptor.set) {
|
// 其余写入 master,但需要确保 setter 中的 this 指向 fork
|
||||||
// 如果有 setter,直接用 receiver(fork 实例)作为 this 调用
|
// 检查 master 上是否有对应的 setter
|
||||||
// 这样 setter 中的 this 就指向 fork,可以正确调用 fork 的方法
|
const descriptor = this.getPropertyDescriptor(target.master, prop);
|
||||||
descriptor.set.call(receiver, value);
|
if (descriptor && descriptor.set) {
|
||||||
return true;
|
// 如果有 setter,直接用 receiver(fork 实例)作为 this 调用
|
||||||
} else {
|
// 这样 setter 中的 this 就指向 fork,可以正确调用 fork 的方法
|
||||||
// 没有 setter,直接赋值到 master
|
descriptor.set.call(receiver, value);
|
||||||
(target.master as any)[prop] = value;
|
return true;
|
||||||
return true;
|
} else {
|
||||||
}
|
// 没有 setter,直接赋值到 master
|
||||||
},
|
(target.master as any)[prop] = value;
|
||||||
});
|
return true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
/**
|
});
|
||||||
* 获取对象及其原型链上的属性描述符
|
}
|
||||||
*/
|
|
||||||
private getPropertyDescriptor(obj: any, prop: PropertyKey): PropertyDescriptor | undefined {
|
public setSharedContext(ctx: Record<string, any>) {
|
||||||
let current = obj;
|
this._sharedContext = ctx;
|
||||||
while (current) {
|
}
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
|
||||||
if (descriptor) {
|
public getSharedContext() {
|
||||||
return descriptor;
|
if (this.async || !this.parent) {
|
||||||
}
|
return this._sharedContext;
|
||||||
current = Object.getPrototypeOf(current);
|
}
|
||||||
}
|
return {
|
||||||
return undefined;
|
...this.parent?.getSharedContext(),
|
||||||
}
|
...this._sharedContext, // 当前实例的 context 优先级最高
|
||||||
|
};
|
||||||
/**
|
}
|
||||||
* 修改局部 props,仅影响当前 fork
|
|
||||||
*/
|
/**
|
||||||
setProps(key: string | IModelComponentProps, value?: any): void {
|
* 获取对象及其原型链上的属性描述符
|
||||||
if (this.disposed) return;
|
*/
|
||||||
|
private getPropertyDescriptor(obj: any, prop: PropertyKey): PropertyDescriptor | undefined {
|
||||||
if (typeof key === 'string') {
|
let current = obj;
|
||||||
this.localProps[key] = value;
|
while (current) {
|
||||||
} else {
|
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
|
||||||
this.localProps = { ...this.localProps, ...key };
|
if (descriptor) {
|
||||||
}
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
current = Object.getPrototypeOf(current);
|
||||||
/**
|
}
|
||||||
* render 依旧使用 master 的方法,但合并后的 props 需要透传
|
return undefined;
|
||||||
*/
|
}
|
||||||
render() {
|
|
||||||
if (this.disposed) return null;
|
/**
|
||||||
// 将 master.render 以 fork 作为 this 调用,使其读取到合并后的 props
|
* 修改局部 props,仅影响当前 fork
|
||||||
const mergedProps = { ...this.master.getProps(), ...this.localProps };
|
*/
|
||||||
// 临时替换 this.props
|
setProps(key: string | IModelComponentProps, value?: any): void {
|
||||||
const originalProps = (this as any).props;
|
if (this.disposed) return;
|
||||||
(this as any).props = mergedProps;
|
|
||||||
try {
|
if (typeof key === 'string') {
|
||||||
return (this.master.render as any).call(this);
|
this.localProps[key] = value;
|
||||||
} finally {
|
} else {
|
||||||
(this as any).props = originalProps;
|
this.localProps = { ...this.localProps, ...key };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 释放 fork:从 master.forks 中移除自身并断开引用
|
* render 依旧使用 master 的方法,但合并后的 props 需要透传
|
||||||
*/
|
*/
|
||||||
dispose() {
|
render() {
|
||||||
if (this.disposed) return;
|
if (this.disposed) return null;
|
||||||
this.disposed = true;
|
// 将 master.render 以 fork 作为 this 调用,使其读取到合并后的 props
|
||||||
if (this.master && (this.master as any).forks) {
|
const mergedProps = { ...this.master.getProps(), ...this.localProps };
|
||||||
(this.master as any).forks.delete(this as any);
|
// 临时替换 this.props
|
||||||
}
|
const originalProps = (this as any).props;
|
||||||
// 从 master 的 forkCache 中移除自己
|
(this as any).props = mergedProps;
|
||||||
if (this.master && (this.master as any).forkCache) {
|
try {
|
||||||
const forkCache = (this.master as any).forkCache;
|
return (this.master.render as any).call(this);
|
||||||
for (const [key, fork] of forkCache.entries()) {
|
} finally {
|
||||||
if (fork === this) {
|
(this as any).props = originalProps;
|
||||||
forkCache.delete(key);
|
}
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
/**
|
||||||
}
|
* 释放 fork:从 master.forks 中移除自身并断开引用
|
||||||
// @ts-ignore
|
*/
|
||||||
this.master = null;
|
dispose() {
|
||||||
}
|
if (this.disposed) return;
|
||||||
|
this.disposed = true;
|
||||||
/**
|
if (this.master && (this.master as any).forks) {
|
||||||
* 获取合并后的 props(master + localProps,local 优先)
|
(this.master as any).forks.delete(this as any);
|
||||||
*/
|
}
|
||||||
getProps(): IModelComponentProps {
|
// 从 master 的 forkCache 中移除自己
|
||||||
return { ...this.master.getProps(), ...this.localProps };
|
if (this.master && (this.master as any).forkCache) {
|
||||||
}
|
const forkCache = (this.master as any).forkCache;
|
||||||
}
|
for (const [key, fork] of forkCache.entries()) {
|
||||||
|
if (fork === this) {
|
||||||
// 类型断言:让 ForkFlowModel 可以被当作 FlowModel 使用
|
forkCache.delete(key);
|
||||||
export interface ForkFlowModel<TMaster extends FlowModel = FlowModel> extends FlowModel {}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
this.master = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取合并后的 props(master + localProps,local 优先)
|
||||||
|
*/
|
||||||
|
getProps(): IModelComponentProps {
|
||||||
|
return { ...this.master.getProps(), ...this.localProps };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型断言:让 ForkFlowModel 可以被当作 FlowModel 使用
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface ForkFlowModel<TMaster extends FlowModel = FlowModel> extends FlowModel {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user